Make your own gadgets

Make your own gadgets

gadget

We will create a smiley widget that displays a satisfaction rating from a value between 0 (unhappy) and 100 (happy). To do this, you have to create a new smiley folder in the widgets folder of your app, which will contain 2 files:

File Content
widgets/smiley/widget.js Class of the widget.
widgets/smiley/styles.js Styles CSS of the widget.

The skeleton of our file widgets/smiley/widget.js should contain this:

import React from 'react';
import Widget from 'goblin-laboratory/widgets/widget';
import * as styles from './styles';

export default class Smiley extends Widget {
  constructor() {
    super(...arguments);
    this.styles = styles;
  }

  render() {
    return null;
  }
}

The beginning (step 1)

To get started simply, let’s create a widget that shows a yellow circle:

render() {
    return <div className={this.styles.classNames.smiley} />
}

The widgets/smiley/styles.js file must contain the CSS style named smiley:

export default function styles() {
  const smiley = {
    width: '200px',
    height: '200px',
    borderRadius: '200px',
    border: '10px solid black',
    background: 'yellow',
  };

  return {
    smiley,
  };
}

The value of 200px for the radius is not an error, but a simplification. Logically, we should give half the diameter (100px) and adding the thickness of the border (10px), but luckily, CSS truncates the value if it is too large, and we therefore get a circle.

The styles management uses a cache for more efficiency, with the lib aphrodite.

In the parent widget that instantiates the smiley, you have to write this:

import Smiley from 'goblin-gadgets/widgets/smiley/widget';

...
render() {
    return (
        <Container>
            ...
            <Smiley />
            ...
        </Container>
    )
}

And unsurprisingly, we get the following result:

step 1

With eyes and a smile (step 2)

Let’s add two eyes and a smile in widgets/smiley/widget.js:

render() {
    return (
        <div className={this.styles.classNames.smiley}>
            <div className={this.styles.classNames.leftEye} />
            <div className={this.styles.classNames.rightEye} />
            <div className={this.styles.classNames.smile} />
        </div>
    )
}

In widgets/smiley/styles.js, we need to add 3 new CSS styles leftEye, rightEye and smile:

export default function styles() {
  const smiley = {
    position: 'relative',
    width: '200px',
    height: '200px',
    borderRadius: '200px',
    border: '10px solid black',
    background: 'yellow',
  };

  const _eye = {
    position: 'absolute',
    top: '60px',
    width: '20px',
    height: '20px',
    borderRadius: '20px',
    backgroundColor: 'black',
    transform: 'scaleY(2)',
  };

  const leftEye = {
    ..._eye,
    left: '70px',
  };

  const rightEye = {
    ..._eye,
    right: '70px',
  };

  const smile = {
    position: 'absolute',
    bottom: '40px',
    left: '30px',
    right: '30px',
    height: '60px',
    borderRadius: '0 0 150px 150px',
    border: '10px solid black',
    borderTop: 0,
  };

  return {
    smiley,
    leftEye,
    rightEye,
    smile,
  };
}

It’s a good practice to name the first style (that of the root div) with the same name as the widget (here smiley). This will make it easier to find the widget when you inspect the DOM. The styles of the children (here leftEye, rightEye and smile) can be named freely.

We obtain the following magnificent result:

step 2

Nice gradient (step 3)

Adding a gradient in CSS with the property background is child’s play. First color #f80 is orange at top, and second color #ff0 is yellow at bottom:

export default function styles() {
  const smiley = {
    position: 'relative',
    width: '200px',
    height: '200px',
    borderRadius: '200px',
    border: '10px solid black',
    background: 'linear-gradient(180deg, #f80, #ff0)',
  };

step 3

Add reflexion (step 4)

Let’s add a <div> reflexion1 in widgets/smiley/widget.js. This element is above the circle, but below the eyes:

render() {
    return (
        <div className={this.styles.classNames.smiley}>
            <div className={this.styles.classNames.reflexion1} />
            <div className={this.styles.classNames.leftEye} />
            <div className={this.styles.classNames.rightEye} />
            <div className={this.styles.classNames.smile} />
        </div>
    )
}

And create a new style CSS to draw an ellipse in widgets/smiley/styles.js:

export default function styles() {
  const reflexion1 = {
      position: 'absolute',
      left: '50px',
      right: '50px',
      top: '10px',
      bottom: '80px',
      borderRadius: '200px',
      background: 'linear-gradient(180deg, white, #fc0)',
      transform: 'scaleX(1.5)',
      opacity: 0.8,
  };

We see here the ellipse bordered by a red line. Absolute positioning with left, right, top and bottom gives a circle. It is the transformation with scaleX(1.5) that gives an ellipse: step 4

step 4

One more reflexion (step 5)

Let’s add a other <div> reflexion2 in widgets/smiley/widget.js:

render() {
    return (
        <div className={this.styles.classNames.smiley}>
            <div className={this.styles.classNames.reflexion1} />
            <div className={this.styles.classNames.reflexion2} />
            <div className={this.styles.classNames.leftEye} />
            <div className={this.styles.classNames.rightEye} />
            <div className={this.styles.classNames.smile} />
        </div>
    )
}

And create a new style CSS in widgets/smiley/styles.js:

export default function styles() {
  const reflexion2 = {
      position: 'absolute',
      left: 0,
      right: 0,
      top: 0,
      bottom: 0,
      borderStyle: 'solid',
      borderRadius: '200px',
      borderWidth: '5px',
      borderColor: 'transparent white white transparent',
      transform: 'rotate(45deg)',
      opacity: 0.8,
  };

This element draws a semicircle, drawing a circle that has only the right (in red below) and bottom (in cyan below) borders, rotated 45 degrees CW. We see here the element before the rotation: step 5 In the final version drawn in the smiley face, these two borders are white, of course.

step 5

The properties (step 6)

Our smiley has a fixed size of 200px. It would be cool to be able to specify its size using a property, and allow this to be written in the parent widget which instantiates the smiley:

render() {
    return <Smiley size="500px" />
}

For that, we must improve widgets/smiley/styles.js by declaring the size property:

export const propNames = ['size'];

export default function styles(theme, props) {
  const smiley = {
    width: props.size,
    height: props.size,
    borderRadius: props.size,
    border: '10px solid black',
    background: 'yellow',
  };

  return {
    smiley,
  };
}

If the size property is omitted, it is useful to have a default value. We can write this:

export const propNames = ['size'];

export default function styles(theme, props) {
  const {size = '200px'} = props;

  const smiley = {
    width: size,
    height: size,
    borderRadius: size,
    border: '10px solid black',
    background: 'yellow',
  };

  return {
    smiley,
  };
}

All other absole values px for eyes, smile and reflexions have to be calculated from the diameter (property size), which takes a lot of work!

And more… (step 7)

With a little bit of work in the CSS, you can achieve this:

step 7

In the final widget, it will be possible to add a satisfaction property (we see here the results with the values 100, 75, 50, 25 and 0):

step 8

Why not a color property?

step 9

To obtain these variants, just write:

render() {
    return (
        <Container>
            <Smiley size="200px" satisfaction={100} mainColor="#0ff" />
            <Smiley size="200px" satisfaction={100} mainColor='#f0f' topColor='#0ff' />
            <Smiley size="200px" satisfaction={75} mainColor='#08f' topColor='#0ff' />
            <Smiley size="200px" satisfaction={25} mainColor='#0a0' topColor='#ff0' />
            <Smiley size="200px" satisfaction={0} mainColor='#fa0' topColor='#f00' />
        </Container>
    )
}

You will find the definitive widget in goblin-gadgets/widgets/smiley. It is possible to test it with WidgetDoc. The XCRAFT.CH section allows you to test the different steps (1 to 7) described above:

WidgetDoc