Skip to content

Creating a Module

Edmund edited this page Jan 28, 2019 · 49 revisions

Learn more about Synergy modules

A Synergy module is essentially a UI component that's been broken up into the following areas of concern:

Each area of concern is to be handled independently without having strict dependencies on each other; i.e if you remove the styles aspect of a Synergy module, you should still be able to render a functional, but styleless, UI component. Likewise, if you remove the interactions aspect of a Synergy module, you should still be able to render a styled, but functionless component etc. This is to simply allow for portability; if your project used React and decided to switch to another framework at some point, it would be very easy to port your Synergy UI modules over as you would only have to change the interface aspect.

This is an opinionated guide to building a Synergy module using Cell for styles

Cell was built to tackle the styles area of concern with regard to a Synergy module, using Sass as the technology. An alternative is Polymorph, which solves the same problem only sing JavaScript as the technology.

This guide will walk through how to create a Synergy module using Cell for styling. For the bigger picture, see the Creating a Module page for the Synergy repository.

Example Structure

As we are using Cell, our styles for the example module will be handled with an scss file. For interactions we will use plain JavaScript. For the interface, we will use React. For configuration we will use JSON, so it can be shared between the assets (jsx, js and scss files). This might leave us with a setup like this:

modules/
|    |- accordion/
|    |    |- accordion.js
|    |    |- accordion.json
|    |    |- accordion.jsx
|    |    |- accordion.scss

An alternative approach might be structuring the accordion files like so:

modules/
|    |- accordion/
|    |    |- assets/
|    |    |    |- interactions.js
|    |    |    |- configuration.json
|    |    |    |- styles.scss
|    |    |- accordion.jsx

Interface (React - JSX)

Learn more about Synergy module interfaces

The interface in a Synergy module is essentially the markup; the place where interactions and styles converge to create the end result that a user interacts with. This could be achieved using various methods and technologies (e.g. Handlebars, plain HTML, PHP, Web Components etc.), but for this example we will use React.

Lucid is a React library for rendering Synergy modules

modules/accordion/accordion.jsx
// Import Lucid library
import { Module, Component } from '@onenexus/lucid';

// Import module assets
import configuration from './assets/configuration.json';
import interactions from './assets/interactions.js';

export default const Accordion = ({ name = configuration.name, panels }) => (
    <Module name={name}>
        {panels.map(({title, content}, index) => (
            <Component name="panel" key={index}>
                <Component name="title" onClick={interactions.toggle}>
                    {title}
                </Component>

                <Component name="content">{content}</Component>
            </Component>
        ))}
    </Module>
);

When this React Component is rendered, it will generate the following markup:

<div class="accordion">
    <div class="accordion_panel">
        <div class="accordion_title">foo</div>
        <div class="accordion_content">bar</div>
    </div>
    <div class="accordion_panel">
        <div class="accordion_title">fizz</div>
        <div class="accordion_content">buzz</div>
    </div>
</div>

This markup will be styled with the CSS generated by Cell.

Styles (Cell - Sass)

From our interface we can identify the following components:

  • panel
  • title
  • content

...which allows for the foundation of the Sass file:

@include module('accordion') {
    @include component('panel') {
        ...
    }

    @include component('title') {
        ...
    }

    @include component('content') {
        ...
    }
}

Make reusable and configurable

To make this module reusable and configurable, it needs to be included within a mixin:

@mixin accordion($options: ()) {
    @include module('accordion') {
        @include component('panel') {
            ...
        }

        @include component('title') {
            ...
        }

        @include component('content') {
            ...
        }
    }
}
  • An $options argument is suppled to the mixin to provide configuration
Usage
@include accordion((
    // options
));

Adding Default Options

Learn more about module configuration

@mixin accordion($custom: ()) {
    $options: config((
        // default options
    ), $custom);

    @include module('accordion') {
        @include component('panel') {
            ...
        }

        @include component('title') {
            ...
        }

        @include component('content') {
            ...
        }
    }
}
  • $options argument has been renamed to $custom
  • $options is now defined inside the mixin, where default options can be supplied
  • The config() function is called and is passed the default configuration along with any custom configuration
  • this() can now be called to retrieve a value from the merged configuration

Moving configuration to JSON

See the JSON Configuration page to learn more about using JSON for configuration for Cell modules

modules/accordion/assets/configuration.json
{
    "accordion": {
        // options
    }
}
modules/accordion/assets/styles.scss
// configuration will now exist under `$accordion`
@import 'assets/configuration.json';

@mixin accordion($custom: ()) {
    $options: config($accordion, $custom);

    @include module('accordion') {
        @include component('panel') {
            ...
        }

        @include component('title') {
            ...
        }

        @include component('content') {
            ...
        }
    }
}

Isolating Configurable Styles

So far we have used the Module and Component mixins, as well as the config function. The accordion.scss file is where will we keep fundamental style properties for the accordion. Fundamental style properties include:

  • Properties that determine layout/structure
  • Properties that are unlikely to ever change
  • Properties that should not be configurable

One of the benefits of Synergy is the ability to isolate configurable styles from your module's source code. The idea is that if a CSS property exists only for cosmetic effect, then it does not need to be hard coded in the module. A general rule of thumb is that CSS properties which account for continuous data can be configurable (but don't have to be) and will typically be responsible for cosmetic properties; properties which account for discrete data cannot be configurable and will generally not be responsible for cosmetic properties.

Learn more about discrete vs continuous and fundamental vs cosmetic CSS properties

Adding all the discrete data (fundamental styles) to our Accordion styles.scss file might leave us with something like:

modules/accordion/assets/styles.scss
@import 'assets/configuration.json';

@mixin accordion($custom: ()) {
    $config: config($accordion, $custom);

    @include module {
        @include component('panel') {
            @include modifier('active') {
                @include component('content') {
                    display: block;
                }
            }
        }

        @include component('title', (
            'display': block,
            'cursor': pointer
        ));

        @include component('content', (
            'display': none
        ));
    }       
}
  • We have introduced the modifier mixin to toggle visibility with the presence of an active modifier

For the continuous data, which will effectively be the accordion's cosmetic data, we will allow this to be configurable, so it will be added to the accordion's configuration.

Configuration (JSON)

Learn more about module configuration

This file is to be used to contain all configurable aspects of the module, regardless of whether or not they relate to styles or interactions. As Cell is able to automagically match configuration property keys to CSS properties, this allows you to effectively make cosmetic changes to your module without touching any source code (by just modifying the configuration.json file).

See the JSON Configuration page to get setup using JSON for configuration with Cell

modules/accordion/assets/configuration.json
{
    "accordion": {
        "name": "accordion",
        "panel": {
            "-active": {
                "_title": {
                    "background": "#2E3882",
                    "border-color": "#2E3882",
                    "color": "white"
                }
            }
        },
        "title": {
            "background": "transparent",
            "color": "#444444",
            "border": "1px solid rgba(black, 0.15)",
            "padding": "1em",
            "transition": "0.4s",
            ":hover": {
                "background": "#384BC9",
                "border-color": "#384BC9",
                "color": "white"
            }
        },
        "content": {
            "background": "white",
            "color": "#444444",
            "border": "1px solid rgba(black, 0.15)",
            "padding": "1.5em"
        }
    }
}

Interactions (JavaScript)

Learn more about module interactions

Earlier we saw in the module interface the importing of interactions.js and use of interactions.toggle on the title component - this file might look something like:

modules/accordion/assets/interactions.js
import configuration from './configuration.json';

export default {
    toggle
}

export function toggle(event) {
    const toggleClass = `${configuration.name}_panel-active`;
    const toggleElement = event.target.closest(`.${configuration.name}_panel`);

    toggleElement.classList.toggle(toggleClass);
}
Using sQuery

Learn More about sQuery

export default {
    toggle
}

export function toggle(event) {
    event.target.parent('panel').modifier('active', 'toggle');
}

Demo

It's now possible to render a complete UI accordion using what we've created.

Sass

Import the accordion Sass module and include it in your project's Sass

@import '/modules/accordion/accordion';

@include accordion();
With Custom Options
@include accordion((
    'title': (
        'background': #06D2FF,
        'color': white
    )
));
CSS Output
[class*="accordion_panel-"][class*="-active"] .accordion_content, 
[class*="accordion_panel-"][class*="-active"] [class*="accordion_content-"] {
  display: block;
}

[class*="accordion_panel-"][class*="-active"] .accordion_title, 
[class*="accordion_panel-"][class*="-active"] [class*="accordion_title-"] {
  background: #2E3882;
  border-color: #2E3882;
  color: white;
}

.accordion_title, [class*="accordion_title-"] {
  display: block;
  cursor: pointer;
  background: #06D2FF;
  color: white;
  border: 1px solid rgba(0, 0, 0, 0.15);
  padding: 1em;
  transition: 0.4s;
}

.accordion_title:hover, [class*="accordion_title-"]:hover {
  background: #384BC9;
  border-color: #384BC9;
  color: white;
}

.accordion_content, [class*="accordion_content-"] {
  display: none;
  background: white;
  color: #444444;
  border: 1px solid rgba(0, 0, 0, 0.15);
  padding: 1.5em;
}

JavaScript

<!-- Container to render the Accordion -->
<div id="demo"></div>

Checkout Polymorph to style your Synergy modules using JavaScript

import React from 'react';
import ReactDOM from 'react-dom';

import Accordion from './modules/accordion/accordion.jsx';

ReactDOM.render(
    <Accordion panels={[
        {title: 'foo', content: 'bar'},
        {title: 'fizz', content: 'buzz'},
    ]} />, 

    document.getElementById('demo')
);

And that's it! Providing the page where you rendered the React Component/Synergy Module has the CSS loaded that's all that's needed.

Clone this wiki locally