-
Notifications
You must be signed in to change notification settings - Fork 10
Creating a Module
Taking the 60 second example from the Wiki homepage as a starting point, we can see how Synergy can be further utilised.
Synergy uses Lucid for rendering React Components and styling them with JavaScript
Want to use Sass to style your module instead of JavaScript? See the Using Sass with Synergy page
import React, { useState } from 'react';
import { Module, Component } from '@onenexus/synergy';
const styles = {
fontFamily: 'sans-serif',
heading: ({ context }) => ({
backgroundColor: '#1E90FF',
color: 'white',
padding: '1em',
cursor: 'pointer',
...(context.panel.open && {
backgroundColor: '#00FFB2'
}),
':hover': {
backgroundColor: '#01BFFF'
}
}),
content: ({ context }) => ({
padding: '1em',
color: '#444444',
display: context.panel.open ? 'block' : 'none'
})
}
const Accordion = ({ panels, ...props }) => {
const [current, toggle] = useState(0);
return (
<Module name='Accordion' styles={styles} { ...props }>
{panels.map(({ heading, content }, index) => (
<Component name='panel' open={index === current}>
<Component name='heading' onClick={() => toggle(index === current ? -1 : index)}>
{heading}
</Component>
<Component name='content' content={content} />
</Component>
))}
</Module>
);
}
export default Accordion;
import React from 'react';
import ReactDOM from 'react-dom';
import Accordion from './modules/Accordion';
const data = [
{
heading: 'accordion heading 1',
content: 'lorem ipsum'
},
{
heading: 'accordion heading 2',
content: <p>foo bar</p>
}
];
const Screen = () => (
<Accordion panels={data} />
);
ReactDOM.render(<Screen />, document.getElementById('app'));
Breaking down what we know so far:
- A Synergy Module is a React Function Component that returns a
<Module>
instance - The
<Module>
instance is passed aname
prop and astyles
prop - The
name
prop is optional but ensures a more readable DOM output - The
styles
prop uses a plain JavaScript Object that will style the<Module>
and child<Component>
s - Everything else is normal React/JavaScript
The example App will render an Accordion Module, ultimately making use of configuration and themes later on.
This is an optional step but for this example the Module's styles will be isolated to a separate file, leaving the current structure as:
|-- modules
| |-- Accordion
| | |-- index.jsx
| | |-- styles.js
|-- app.js
import React, { useState } from 'react';
import { Module, Component } from '@onenexus/synergy';
import styles from './styles';
const Accordion = ({ panels, ...props }) => {
const [current, toggle] = useState(0);
return (
<Module name='Accordion' styles={styles} { ...props }>
{panels.map(({ heading, content }, index) => (
<Component name='panel' open={index === current}>
<Component name='heading' onClick={() => toggle(index === current ? -1 : index)}>
{heading}
</Component>
<Component name='content' content={content} />
</Component>
))}
</Module>
);
}
export default Accordion;
-
styles.js
is renamed tolayout.js
, as stylistic control (as in cosmetic control) will be handled byconfig.js
|-- modules
| |-- Accordion
| | |-- index.jsx
| | |-- config.js
| | |-- layout.js
|-- app.js
- Cosmetic/configurable information is moved to
config.js
, which will be parsed as Cell Query, allowing Modules/Components to be styled via configuration (this is due UI Component configuration information being directly mappable to CSS properties in many cases - learn more about this convention)
export default () => ({
fontFamily: 'sans-serif',
heading: {
backgroundColor: '#1E90FF',
color: 'white',
padding: '1em',
'panel-is-open': {
backgroundColor: '#00FFB2'
},
':hover': {
backgroundColor: '#01BFFF'
}
},
content: {
padding: '1em',
color: '#444444'
}
});
- Now that all cosmetic/configurable properties have been removed, only functional/layout properties remain, hence the rename to
layout.js
export default {
heading: {
cursor: 'pointer'
},
content: ({ context }) => ({
display: context.panel.open ? 'block' : 'none'
})
}
-
React's
defaultProps
is made use of
import React, { useState } from 'react';
import { Module, Component } from '@onenexus/synergy';
import config from './styles';
import styles from './styles';
const Accordion = ({ panels, ...props }) => {
const [current, toggle] = useState(0);
return (
<Module { ...props }>
{panels.map(({ heading, content }, index) => (
<Component name='panel' open={index === current}>
<Component name='heading' onClick={() => toggle(index === current ? -1 : index)}>
{heading}
</Component>
<Component name='content' content={content} />
</Component>
))}
</Module>
);
}
Accordion.defaultProps = { name: 'Accordion', config, styles }
export default Accordion;
|-- modules
| |-- Accordion
| | |-- index.jsx
| | |-- config.js
| | |-- layout.js
|-- themes
| |-- myTheme.js
|-- app.js
export default {
spacing: '1em',
colors: {
primary: '#1E90FF',
secondary: '#00FFB2',
tertiary: '#01BFFF'
},
typography: {
primaryFont: 'sans-serif',
textColor: '#444444'
}
}
- The theme is imported and passed to the newly imported
<Provider>
Element
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from '@onenexus/synergy';
import theme from './themes/myTheme.js';
import Accordion from './modules/Accordion';
const data = [
{
heading: 'accordion heading 1',
content: 'lorem ipsum'
},
{
heading: 'accordion heading 2',
content: <p>foo bar</p>
}
];
const Screen = () => (
<Provider theme={theme}>
<Accordion panels={data} />
</Provider>
);
ReactDOM.render(<Screen />, document.getElementById('app'));
- The theme is now exposed to the Module's configuration
export default (theme) => ({
fontFamily: theme.typography.primaryFont,
heading: {
backgroundColor: theme.colors.primary,
color: 'white',
padding: theme.spacing,
'panel-is-open': {
backgroundColor: theme.colors.secondary,
},
':hover': {
backgroundColor: theme.colors.tertiary,
}
},
content: {
padding: theme.spacing,
color: theme.typography.textColor
}
});
- The theme can also be exposed to your Module's
layout.js
export default ({ theme }) => {
// do something with `theme`...
return {
heading: {
cursor: 'pointer'
},
content: ({ context }) => ({
display: context.panel.open ? 'block' : 'none'
})
}
}
- The theme can also be exposed to your Module's
index.jsx
import React, { useState } from 'react';
import { Module, Component, useTheme } from '@onenexus/synergy';
import styles from './styles';
import config from './condig';
const Accordion = ({ panels, ...props }) => {
const [current, toggle] = useState(0);
const theme = useTheme();
// do someting with `theme`...
return (
<Module { ...props }>
{panels.map(({ heading, content }, index) => (
<Component name='panel' open={index === current}>
<Component name='heading' onClick={() => toggle(index === current ? -1 : index)}>
{heading}
</Component>
<Component name='content' content={content} />
</Component>
))}
</Module>
);
}
Accordion.defaultProps = { name: 'Accordion', config, styles }
export default Accordion;
Using <Container>
allows for a few things to transpire, but the main things are:
- Attach React to
window
so it can be accessed without being explicitly imported - Attach Module, Component and SubComponent to
window
so they can be accessed without being explicitly imported - Attach your Modules to
window
so they can be accessed without being explicitly imported - Provide a theme to your Modules
Given the frequency that these things would be otherwise imported (perhaps even 90%+ of files), making them available in the global scope by attaching them to window
can help simplify the development of your Component Library/Design System.
import React from 'react';
import ReactDOM from 'react-dom';
import { Container } from '@onenexus/synergy';
import * as modules from './modules';
import theme from './themes/myTheme.js';
const App = () => (
...
);
ReactDOM.render((
<Container {...{ theme, modules, globals: { React } }}>
<App />
</Container>
), document.getElementById('app'));
We can imagine that that within our App above, we handle some routing to render different screens. Within these screens we would now have implicit access to our Synergy toolkit (including <Module>
, <Component>
and any existing Modules):
export default () => (
<Module name='screen'>
<Accordion data={...} />
<Group>
<Button>Login</Button>
<Button>Sign up</Button>
</Group>
<div className='fizzBuzz'>Just a regular div</div>
</Module>
);
As you can see no imports have been neccessery, which is a valuable tradeoff when your Design System is well documented and you know what Modules are available to use (similar to knowing which HTML5 tags are available to use without having to explicitly declare/import them). Modules being abstract and generic is what warrants their implicit existence.