Idyll is already rich with many built-it components. That said, the default components may not be enough to suit all of one’s needs. Many of the examples in the gallery page have components that are custom written, such as the components used in this Beat Basics article. Much of the creativity Idyll allows is through writing one’s own custom components.
This article serves as an introductory tutorial for writing your own custom components (in React) in Idyll, and is an extension to Idyll’s current docs with custom components.
Idyll components are React components under the hood, and it may be good to be familiar with the basics of React. Their docs are well documented, and serve as a great starting point for learning React.
Very important to an Idyll document’s interactivity is its interaction with variables. This is important for writing custom components that have interactivity with users, as well as other components.
In Idyll, a variable can be bound to a component’s property (prop
). If this variable is updated, all
components bound to this variable also update with the new value.
In particular, for all custom components, Idyll injects an updateProps
method into the props
object of a custom component. We pass our variables
into a custom component’s props
, and the component can updateProps
, which
facilitates interaction between components--when a variable’s value gets updated, it will affect any other components bound to it as well!
This is very powerful.
I demonstrate this by interacting with a small basic custom component, TwoColors
.
Try clicking on a colored section below, and notice how the color selected text changes.
The color clicked is not yet chosen
To make this, I defined my own custom component, TwoColors
, in a file called two-colors.js
in my components/ folder, letting Idyll know about it.
The contents start out with a basic React template:
import React from 'react';
class TwoColors extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
// ... rendered output here
}
}
export default TwoColors;
Note: The export default TwoColors;
statement allows Idyll to find the component TwoColors
.
See export docs for more info.
Now, let’s render two squares of 100x100 pixels. We will render them as div
s, and wrap them inside a container div
.
So now, render()
looks like:
render() {
return (
<div>
<div
className="square-green"
style={{
backgroundColor: 'green',
width: '100px',
height: '100px',
textAlign: 'center',
cursor: 'pointer',
}}
>
Green
</div>
<div
className="square-orange"
style={{
backgroundColor: 'orange',
width: '100px',
height: '100px',
textAlign: 'center',
cursor: 'pointer',
}}
>
Orange
</div>
</div>
);
}
I injected CSS styling into the square div
’s style
property.
Now, inside our index.idyll
file, we can create a TwoColors
component via:
[TwoColors /]
and we get the output:
Great! Now, let’s add some interactivity!
Inside index.idyll
, we define a variable that will track the last clicked color.
Because we initially haven’t clicked on a color, we’ll initialize the variable’s value to null
.
[var name:"lastClickedColor" value:null /]
Then, we’ll bind this variable to a property of TwoColors
! Let’s call this property selectedColor
, just to
differentiate between our variable and the prop.
[var name:"lastClickedColor" value:null /]
[TwoColors selectedColor:lastClickedColor /]
Inside TwoColors
, we’ll add a click event to each square, and upon clicking, the square will update its selectedColor
prop,
which will update our lastClickedColor
variable. Let’s add onClick
events to both squares. Our updated render()
looks like:
render() {
return (
<div>
<div
className="square-green"
style={{
backgroundColor: 'green',
width: '100px',
height: '100px',
textAlign: 'center',
cursor: 'pointer',
}}
onClick={() => this.changePropsColor('green')} // Added this
>
Green
</div>
<div
className="square-orange"
style={{
backgroundColor: 'orange',
width: '100px',
height: '100px',
textAlign: 'center',
cursor: 'pointer',
}}
onClick={() => this.changePropsColor('orange')} // Added this
>
Orange
</div>
</div>
);
}
Let’s also define the changePropsColor
function! It will accept a color
parameter,
and then updateProps()
to the clicked color! (Notice how we passed green
and orange
in the previous code block).
constructor(props) {
super(props);
}
changePropsColor(color) {
this.props.updateProps({ // This uses the `updateProps` method that Idyll injects into components
currentColor: color
});
}
Now, when we click on a square, the changePropsColor
function is called, and the selectedColor
prop updates to the color clicked,
and then the lastClickedColor
variable becomes updated to the color selected (“green” or “orange”).
Finally, let’s now display our lastClickedColor
to see it update live!
We’ll use Idyll’s default Display component to display the variable.
[var name:"lastClickedColor" value:null /]
[TwoColors selectedColor:lastClickedColor /]
The color clicked is [Display value:`lastClickedColor ? lastClickedColor : "not yet chosen"` /]
Which results in what we saw before:
The color clicked is not yet chosen
Let’s work on a more interesting component. Below, we have two range sliders, and a balloon. The first slider controls the size of the balloon. The second slider controls the scale of the balloon’s deflation.
I’ve constructed this so that when we click on the balloon, it will deflate by the scale from the second slider.
Try changing the size of the balloon with the first slider, and also try deflating it by clicking it. (Note: values of zero are not allowed by choice).
0.50 Controls the size of the balloon
0.10 Controls the deflation scale
Let’s go over how we can do this.
We introduce two variables binded to each range slider:
[var name:"balloonSize" value:0.5 /]
[var name:"deflationSize" value:0.1 /]
// The div and styling are added so that we can still see these over the balloon
[div style:`{position:'relative', zIndex:100}`]
[Range min:0.1 max:1 value:balloonSize step:0.1 /]
[Display value:balloonSize /]
Controls the size of the balloon
[Range min:0.1 max:balloonSize value:deflationSize step:0.1 /]
[Display value:deflationSize /]
Controls the deflation scale
[/div]
Now, we will render a balloon component that will take our two variables as props, so that its size can be updated based off them.
As such, this balloon will take two props, size
and deflationSize
.
[Balloon size:balloonSize deflationSize:deflationSize /]
We want to be able to click on the balloon, and have its size changed (deflated). So, here’s what our implementation will look like:
import React from 'react';
// props: size and deflationSize
class Balloon extends React.PureComponent {
constructor(props) {
super(props);
}
deflate() {
... TODO
}
render() {
... TODO
}
}
export default Balloon;
The deflate
method will be called when we click on the balloon.
To implement the deflate
method, we want to subtract the deflation size from the balloon size, and use the injected updateProps
method to update the balloon size.
(I added a few details to handle floating point subtraction and also disallowing our size to get to 0).
deflate() {
const updatedSize = (this.props.size - this.props.deflationSize).toFixed(2); // handling floating point subtraction
if (updatedSize > 0) {
this.props.updateProps({
size: updatedSize,
});
}
}
Now, let’s put the balloon in our render
method!
This balloon is actually a custom SVG.
Matthew Conlen, Idyll’s creator, helped make it for me.
There are lots of details about the SVG, but we will focus on the part that changes its size based off our balloonSize
, which is in the <g>
tag.
render() {
return (
<div onClick={() => this.deflate}>
<svg width="100%" height="auto" viewBox="0 0 400 500" style={{overflow: 'visible'}} >
<path d="M101.124805,255 C100.146564,233.732769 103.780451,291.312889 116.550101,326.561293 C129.319752,361.809698 147.686331,348.668032 144.832964,375.648791 C141.979596,402.62955 110.037653,406.908957 106.501018,421.583026 C102.964383,436.257095 123.051823,445.402516 130.601671,491.110588" id="Path-3" stroke="#000000" strokeWidth="4" fill="none"></path>
<g id="Group" transform={`translate(${194/2 * (1-Math.sqrt(this.props.size))},${255* (1-Math.sqrt(this.props.size))}) scale(${Math.sqrt(this.props.size)})`}>
<path d="M101.124805,255 C155.402086,255 210.611891,140.073889 188.585412,77.1065481 C166.558932,14.1392068 127.46589,5.68434189e-14 101.124805,5.68434189e-14 C74.7066494,5.68434189e-14 15.5948675,12.267268 2.1949056,77.1065481 C-11.2050563,141.945828 46.8475228,255 101.124805,255 Z" id="Path" fill="#D0021B"></path>
<path d="M28.9961639,135 L60,36 C43.3256612,48.9474492 32.9910492,61.8445106 28.9961639,74.6911843 C25.0012787,87.537858 25.0012787,107.640797 28.9961639,135 Z" id="Path-2" fill="#FE8B99"></path>
</g>
</svg>
</div>
);
}
In the <g>
tag:
<g id="Group" transform={`translate(${194/2 * (1-Math.sqrt(this.props.size))},${255* (1-Math.sqrt(this.props.size))}) scale(${Math.sqrt(this.props.size)})`}>
Essentially, to transform
the SVG, we translate
and scale
it based off this.props.size
.
Because this.props.size
is binded to our balloonSize
, changing the first slider will change the size of the balloon.
The container <div onClick={this.deflate}>
over the SVG allows us to click on the balloon, and have the balloon be deflated.
As a final note, we also want to limit the deflationSize
to be no greater than the balloonSize
, as otherwise we would get a negative balloonSize
if we kept deflating.
So, as a last addition, we will check that when the deflationSize
is bigger than the balloonSize
, we update the deflationSize
to the balloonSize
.
This is added to our render
method, before the return.
render() {
if (this.props.deflationSize > this.props.size) {
this.props.updateProps({
deflationSize: this.props.size,
});
}
return ...
}
And that’s it! Check out the full balloon component source code here.
To summarize, we introduced variables that are bounded to the props of components. The components will updateProps
to update the variables,
and all components bounded to the variables will also receive their updated values.
Idyll grants a lot of flexibility and interaction between custom components through its use of bounded variables!
Feel free to check out the source code for this tutorial here. And lastly, for any questions, feedback, and/or comments regarding Idyll, do drop by its Gitter page!