Configuring a Module

Edmund edited this page Jun 18, 2018 · 21 revisions

Overview

One of the most powerful aspects of Synergy is the ability to configure modules. Synergy allows you to to isolate and abstract all configurable aspects of your UI from the source code and have them stored as configuration, allowing you to modify and update your UI without having to touch any code.

Configuration can be contained at a Sass/JavaScript level, with each module's .scss and .js files having their own configuration, or you can store a module's configuration in a JSON file, allowing it to be shared between the Sass and JavaScript (which opens up the possibilities of themes).

Sass-Level Configuration

Default values are defined in the module's .scss file

@mixin header($custom: ()) {
    $header: config((
        'bg-color': black,
        'top': 50px
    ), $custom);

    @include module('header') {
        display: block;
        background-color: this('bg-color');
        margin-top: this('top');
    }
}

Custom values are passed when including the module

@include header((
    'bg-color': blue,
    'top': 30px
));

JavaScript-Level Configuration

Default values are defined in the module's .js file

function header(custom) {
    const defaults = {
        sticky: true
        top: '50px'
    }

    Synergy('header', (header, options) => {
        console.log(options.sticky);
        console.log(options.top);
        ...
    }, defaults, custom);
}

Custom values are passed when including the module

header({
    sticky: false,
    top: '30px'
});

Using React

Instead of config we can use React's defaultProps method

const Header = props => (
    <Module name={props.name}>{props.children}</Module>
);

Header.defaultProps = {
    name: 'header',
    sticky: true
};

Custom values are passed when including the module

<Header sticky={false}>...</Header>

Shared Configuration

Configuration will now have a single source of truth in a .json file; hence the below examples have had their ability to pass custom configuration when including them removed. There is nothing stopping you from combining the ability to have a default configuration file whilst also allowing custom options to be passed when calling the module, or from any other source.

Learn about creating themes with Synergy

header.json

{
    "header": {
        "name": "header",
        "bg-color": "black",
        "top": "50px",
        "sticky": true
    }
}

Sass (header.scss)

@import 'header.json';

@mixin header() {
    $header: config($header);

    @include module(this('name')) {
        display: block;
        background-color: this('bg-color');
        margin-top: this('top');
    }
}

JavaScript (header.js)

import config from './header.json';

function header() {
    Synergy(config.name, header => {
        console.log(config.sticky);
        console.log(config.top);
        ...
    });
}

React/JSX (header.jsx)

import config from './header.json';

const Header = props => (
    <Module name={config.name} sticky={config.sticky}>{props.children}</Module>
);

Usage

[Module | Component | Modifier]: {
    [Component]: {...},
    [Modifier]: {...},
    [CSS Property]: "...",
    [Event Handler]: "...",
    [Custom Property]: *
}

Pass Custom CSS to Modules

CSS can be passed straight to modules, components and modifiers when including them, allowing you to keep your module's source code free from any trivial CSS styles. Module configuration properties are tested against a list of known CSS properties, and if a match is found, the option is treated as a CSS property.

{
    "buttons": {
        "letter-spacing": "-1px",
        "text-transform": "uppercase"
    }
}
CSS Output
.button, [class*="button-"] {
    letter-spacing: -1px;
    text-transform: uppercase;
}

Pass Custom CSS to Components

There are two ways to pass CSS to a component using configuration. The first way is by preprending the key of your property with the component glue (default is '_'):

{
    "buttons": {
        "_wrapper": {
            "overflow": "hidden",
            "margin-bottom": "10px"
        }
    }
}
CSS Output
.button_wrapper, [class*="button_wrapper-"] {
    overflow: hidden;
    margin-bottom: 10px;
}

The second way involves passing the same key (e.g. _wrapper) without the prepended glue:

{
    "buttons": {
        "wrapper": {
            "overflow": "hidden",
            "margin-bottom": "10px"
        }
    }
}

The above styles will only be output if the source buttons() mixin includes the wrapper component:

@mixin buttons($custom: ()) {

    $buttons: config((
        // default config
    ), $custom);

    @include module {
        
        ...

        @include component('wrapper');

        // Or if you need to include default `wrapper` styles:
        @include component('wrapper') {
            display: block;
            ...
        }

    }

}
CSS Output
.button, [class*="button-"] {
    ...
}
.button_wrapper, [class*="button_wrapper-"] {
    display: block;
    overflow: hidden;
    margin-bottom: 10px;
}

Pass Custom CSS to Modifiers

Pass CSS to a modifier by prepending the key of they property with the modifier glue (default is '-'):

{
    "buttons": {
        "-foo": {
            "text-transform": "uppercase"
        }
    }
}
CSS Output
[class*='button-'][class*='-foo'] {
    text-transform: uppercase;
}

You can target modules and components to an infinite depth:

{
    "buttons": {
        "_foo": {
            "content": "alpha",
            "-bar": {
                "content": "beta",
                "-baz": {
                    "content": "gamma",
                    "_fizz": {
                        "content": "delta"
                    }
                }
            }
        }
    }
}
CSS Output
.button, [class*="button-"] {
    ...
}
.button_foo, [class*='button_foo-'] {
    content: 'alpha';
}
[class*='button_foo-'][class*='-bar'] {
    content: 'beta';
}
[class*='button_foo-'][class*='-bar'][class*='-baz'] {
    content: 'gamma';
}
[class*='button_foo-'][class*='-bar'][class*='-baz'] .buttons_foo_fizz,
[class*='button_foo-'][class*='-bar'][class*='-baz'] [class*='button_foo_fizz-'] {
    content: 'delta';
}

Configuration Keywords

Certain keywords unlock special behaviour for configuration.

Name

By passing a value to the name property, you do not need to pass a parameter to the module mixin (the value set by name will be used instead) (learn more).

{
    "header": {
        "name": "header"
    }
}
@import 'header.json';

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

    @include module {
        ...
    }
}

Hover

Apply CSS to the hovered state of a module/component by passing CSS keys and values to the hover option:

{
    "buttons": {
        "hover": {
            "background": "blue"
        }
    }
}
CSS Output
.button:hover, [class*="button-"]:hover {
    background: blue;
}

Active

Apply CSS to a module/component with an applied active modifier:

<div class="button-active">Active Button</div>
{
    "buttons": {
        "active": {
            "background": "blue"
        }
    }
}
CSS Output
[class*="button-"][class*="-active"] {
    background: blue;
}

Custom Property

Naturally you can add any key:value pair you like and use it however you want in your module.

{
    "buttons": {
        "myProp": "hello"
    }
}
@import 'header.json';

@mixin header() {
    $header: config($header);

    @include module('header') {
        @if this('myProp') == 'hello' {
            // add styles
        }
    }
}
import config from './header.json';

function header() {
    Synergy('header', header => {
        if (config.myProp === 'hello') {
            // do something
        }
    });
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.