-
Notifications
You must be signed in to change notification settings - Fork 1
Creating a Module
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.
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
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
// 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.
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') {
...
}
}
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
@include accordion((
// 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
See the JSON Configuration page to learn more about using JSON for configuration for Cell modules
{
"accordion": {
// options
}
}
// 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') {
...
}
}
}
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:
@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 anactive
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.
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
{
"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"
}
}
}
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:
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);
}
Learn More about sQuery
export default {
toggle
}
export function toggle(event) {
event.target.parent('panel').modifier('active', 'toggle');
}
It's now possible to render a complete UI accordion using what we've created.
Import the accordion Sass module and
include
it in your project's Sass
@import '/modules/accordion/accordion';
@include accordion();
@include accordion((
'title': (
'background': #06D2FF,
'color': white
)
));
[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;
}
<!-- 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.