Skip to content
esr360 edited this page Feb 14, 2020 · 37 revisions

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

Re-Cap

/modules/Accordion/index.jsx
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;
Usage (/app.js)
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 a name prop and a styles 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.

Isolate the Styles

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
/modules/Accordion/index.jsx
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;

Add Configuration

  • styles.js is renamed to layout.js, as stylistic control (as in cosmetic control) will be handled by config.js
|-- modules
|   |-- Accordion
|   |   |-- index.jsx
|   |   |-- config.js
|   |   |-- layout.js
|-- app.js
/modules/Accordion/config.jsx
  • 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'
  }
});
/modules/Accordion/layout.js
  • 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'
  })
}
/modules/Accordion/index.jsx
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;

Create a Theme

|-- modules
|   |-- Accordion
|   |   |-- index.jsx
|   |   |-- config.js
|   |   |-- layout.js
|-- themes
|   |-- myTheme.js
|-- app.js
/themes/myTheme.js
export default {
  spacing: '1em',
  colors: {
    primary: '#1E90FF',
    secondary: '#00FFB2',
    tertiary: '#01BFFF'
  },
  typography: {
    primaryFont: 'sans-serif',
    textColor: '#444444'
  }
}
/app.js
  • 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'));
/modules/Accordion/config.js
  • 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
  }
});
/modules/Accordion/layout.js
  • 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'
    })
  }
}
/modules/Accordion/index.jsx
  • 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>

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.

/app.js
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):

Inside Some Screen
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.