Skip to content

UI Component Architecture

pospi edited this page Mar 28, 2017 · 5 revisions

We want UI elements to be built as self-contained modules which can easily be published separately to the main app and used in other projects.

Authoring guidelines

  • Create each UI view component in its own folder.
  • Keep all dependencies (styles, images, fonts) bundled in the same folder as the component.
  • Include tests for the component within the folder as well (test framework still needs to be finalised- see #7)
  • Include a base version of the component that can be included without styles, a version that can be easily themed by overriding some control variables, and a version that has a full theme pre-bundled with it.

Here's a blueprint for how external public imports should map to your component folder:

  • import 'components/Component'/index.js- a fully styled component is the default export. The folder name will be the name of your component as included by other files. index.js should simply take the compiled class name mappings from index.css and apply the theme to Component.js (see src/components/themed.js helper).
  • import 'components/Component/Component'/Component.js- an unstyled component which accepts a react-themeable themes key to enable customisable styling at a 'replace all' level.
  • @import "components/Component/Component.css"/Component.css- base cssnext stylesheet, without configuration vars. This allows child themes to reuse the base styles but include their own variable overrides for simple theming.

Other than these exports, you're free to pretty much make up your own internal structure, but we find setting up a simple inheritance hierarchy makes future-proofing easier and more intuitive.

  • /index.css- full theme styles for the component. Usually just loads base theme variables & component-specific variables before bringing in styles from Component.css.
    • Components without styles don't need any CSS files. In these cases instead add a package.json with a main field pointing to the component's unstyled version.
  • /vars.css- if your component has theme-related configuration variables, separate them into their own file here so that child themes can copy or override and include their own versions. You should think of this file as a mapping between your base set of theme variables and mixins and a component-specific style language. It enables customisable styling from 'tweak global variables' down to 'tweak local component variables'.
  • /Component.spec.js- unit tests for the component. At minimum a single 'it renders' test should be provided.

:TODO: document react-storybook config

Implementation concerns

It's useful to document what we feel is important for our component implementation as opinions do differ...

Theming should be done via the style layer. Against use of React context:

[...] putting everything into your single point of context is just like making a big global variable which is pretty much what all the new CSS/CSS-in-JS solutions are trying to fix.

https://github.com/elementalui/elemental/issues/53#issuecomment-143745987

Given this, a factory pattern for injecting styles seems the only viable approach. Rethemeable and react-css-themr are common solutions which use context for theming, but passing themes in this fashion should be achievable with some creating scripting of the node module API.

Theming should output real CSS stylesheets instead of inline styles. Against inline JS styles:

  • No style fallbacks can be used (you can't define the same property twice). We want something like autoprefixer to run automatically as well.
  • Media & element queries don't work well.
  • CSS classes are faster than inline styles.
  • Stylehsheets lever browser caching, download parallelisation and reduce FOUC. Inline styles break caching & make initial download times longer.

Custom themes should be easily injectable:

React-themeable currently seems like the best approach for achieving this.

Default theme should be easily customisable:

We need to split up the style layer so that we can inject configuration into it to easily change pallete styles & other simple reconfigurations.

Should be agnostic to style language / CSS preprocessor:

The app currently implements cssnext via PostCSS plugins, but could easily support SASS, LESS or a combination thereof via additional PostCSS plugins. We've tried to configure things in such a way as to allow use of other preprocessors in future if desirable.

Further reading