0.21.2 • Published 4 years ago

@rexlabs-spicerhaart/element-styles v0.21.2

Weekly downloads
-
License
MIT
Repository
-
Last release
4 years ago

✂️ element styles

Rationale

Too often are UI components written with hard-coded styling constraints – this is early optimisation!

Styling and theming constraints should be a deferred decision, as is often the case with iterative development, branding choices, and design systems.

When creating reusable component's we shouldn't constrain & isolate styling, and instead allow the styles of any element in the component to be overridden. This way, other theming systems (eg. shared variables) can be put in place by the developer to suit their own approach.

The system that element-styles employ's can be compared to CSS Selectors, but instead of global access to all elements of components, we're only exposed to API when utilising our individual components.

Content

Overview

UtilityDescription
styledHigher-Order Component that passes all props, as well as a styles utility prop, to the wrapped component.It combines three sources of styles for the styles utility to then consume when called.Layers:1. defaultStyles given as argument to styled 2. styles prop given to components created by styled3. Matches from StylesProvider.
stylesA prop available within styled components.When called with names of element styles ,generates an object with style and/or className properties (style primitives).The generated object should be spread onto an element.
StylesProviderA React Component to be used in Applications or slices of Application hierarchy. Allows styled components to use an additional styles object, referenced by their styledName prop, or class name or React displayName.

Usage

Writing a styled component

  1. Wrap the component in styled, providing a styles object (defaultStyles) in first call
  2. Spread result of this.props.styles(...names) onto React elements
// Writing a component, with default styles.
// todo-item.js

import { styled } from '@rexlabs/element-styles'

const defaultStyles = {
  container: { background: 'white', ... },
  text: { fontSize: '1em', ... }
  button: { border: 'solid 1px black', ... }
}

class TodoItem extends Component {
  render () {
    const styles = this.props.styles
    return (
      <li {...styles('container')}>
        <span {...styles('text')}>{this.props.title}</span>
        <button {...styles('button')}>Done</button>
      </li>
    )
  }
}

export default styled(defaultStyles)(TodoItem)

Adding to a styled component

Given a component was wrapped with styled:

  • Add a new styles object to the styles prop
  • The styles object should target elements in the component, by re-using style names referenced by styles(...names)
// Applying other styles, via `theme` prop.
// todo-list.js

import TodoItem from './todo-item'

const listItemsStyles = {
  container: { paddingTop: 20, ... }
};

// ...
  <TodoItem styles={listItemsStyles} />
// ...

Application-wide styles with StylesProvider

The StylesProvider component has a components prop, accepting an object with individual styles objects keyed by component names of styled components, or Component Selectors.

// Applying other styles from top of Application tree.
// app.js

import { StylesProvider } from '@rexlabs/themed-components'

const appStyles = {
  'TodoItem': {
    text: { color: 'royalblue' }
  }
}

class App extends Component {
  render () {
    return (
      <div>
        <StylesProvider components={appStyles}>
          {/*...*/}
        </StylesProvider>
      </div>
    )
  }
}

Styles objects

const styles = {
  'foo': 'classNameFoo',
  'bar': { color: '#fff' }
}

A styles object is concerned with mapping styling primitives to reference names.

'styling primitives' are the values used for style and className props.
eg. an object of css properties/values, or a classname string

Styles objects are the singular source for element styles in styled components. They can be provided to the styled HOC when first called, or by the styles prop on component usage, as well as being the value for selectors on the StylesProvider component.

Resolving styles

When using styles(), the style primitives from several different styles objects need to be resolved. It is important to understand the order of resolution, as well as how style and className prop values are finally merged.

The styled HOC provides the styles function, as well as the internal reconciler for the available styles objects.

It takes the following steps to create a prop object, from calling styles():

  1. Find available styles objects from:
  • defaultStyles given as argument to styled
  • styles prop given to components created by styled
  • Component matches from StylesProvider
  1. When styles is called with name(s), it:
  2. Looks into available styles objects
  3. Finds the style object or classname associated with the name(s)
  4. Creates a temporary object from each styles object, with defined style and/or className properties
  5. Joins className strings from all temporary objects
  6. Assigns all style objects into a new style object, with a prioritised assign order The order is assign({}, defaultTheme, propTheme, providerTheme).
  7. Creates a final object, with the merged classnames and style objects, and returns from styles()

Component selectors

The StylesProvider gives additional styles objects for styled components to reconcile, via the components config prop.

The components config supports a syntax much like CSS Selectors, where the names of Styled Components can be used with Descendant and Child Selector expressions.

const stylesProviderConfig = {
  // Give a new background to the container of TodoItem, when it's:
  //   a) A descendant of TodoList
  //   b) It's hierarchal TodoList is a child of UpcomingTodoList
  'UpcomingTodoList > TodoList TodoItem': {
    'container': {
      backgroundColor: 'lightGrey'
    }
  }
}

A component name is derived from:

  1. The styledName prop supplied to a rendered styled component
  2. The name of the Wrapped Component's class name
  3. The displayName property of the Wrapped Component

It's intended that you use the Selector's feature for edge cases when theming a collection of Components. eg. In an App.

Supported CSS-in-JS libraries

As long as your preferred CSS-in-JS library deals in style primitives (style or className props), it works with element-styles.

This non-exhaustive list of popular CSS-in-JS are known to work:

Classname decorators

Libraries like Aphrodite, glamor and Fela take a style ruleset object, and return a classname. To enable better interop with these kinds of libraries, you can pass a classname decorator function anywhere that styles are given:

export default styled([defaultTheme, glamor.css])(BarComponent)

<BarComponent styles={[propTheme, aphrodite.css]}

<StylesProvider components={{
  'BarComponent': [barProviderStyles, felaRenderer.renderRule]
}} />

FAQ

I need to use the className and style props given to my component, on the top-most element...

Where a developer decides to use the style or className prop inside of a Component has always been at their discretion. So, styled HOC and styles utility don't do anything smart with these props.

However you can use the styles.with utility for adding prop objects to the styles-resolved prop object:

<div { ...styles.with(...names)(this.props) }>
What CSS-in-JS solutions shouldn't you use with element-styles?

As long as you can add style and/or className props to React elements, you can use element-styles.

However, you may not want to use element-styles alongside libraries that roll their own 'primitives' system, like styled-component or glamorous (although you can!).

How do I disable the data-styletrace prop from being generated?

By default, the styles() util given to styled components will get a 'data-styletrace' property on the resulting props object; it is a string describing the Component > segment anatomy of the application of styles on elements in a page/screen.

To disable this, set the following; tools like webpack & uglify will eliminate the relevant code during builds.

// From inside a js application
process.env.NODE_ENV = 'production';
# When running a command in the shell
NODE_ENV=production yarn build

Developing

Installing peer dependencies

A postinstall script should install peer dependencies like react. If it doesn't work, you'll need to install them manually with yarn or npm.

Testing

The goal is to have full coverage, as well as all (or most) use cases being tested.

Documentation

There are two main docs; readme.md and docs/api.md. Be sure to keep the api docs strictly to function signatures and usage concerns. Everything else goes in the readme.

npm run test:watch

Credits

Technical inspiration:

License

The MIT License (MIT)

Copyright © 2018 Rex Software