@rexlabs-spicerhaart/element-styles v0.21.2
✂️ 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
- Usage
- Styles objects
- Component selectors
- Supported CSS-in-JS libraries
- FAQ
- Developing
- Credits
- License
Overview
| Utility | Description |
|---|---|
styled | Higher-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. |
styles | A 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. |
StylesProvider | A 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
- Wrap the component in
styled, providing a styles object (defaultStyles) in first call - 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
stylesprop - 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
styleandclassNameprops.
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():
- Find available styles objects from:
defaultStylesgiven as argument tostyledstylesprop given to components created bystyled- Component matches from
StylesProvider
- When
stylesis called with name(s), it: - Looks into available styles objects
- Finds the style object or classname associated with the name(s)
- Creates a temporary object from each styles object, with defined
styleand/orclassNameproperties - Joins
classNamestrings from all temporary objects - Assigns all
styleobjects into a new style object, with a prioritised assign order The order isassign({}, defaultTheme, propTheme, providerTheme). - 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:
- The
styledNameprop supplied to a renderedstyledcomponent - The name of the Wrapped Component's
classname - The
displayNameproperty 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:
- Global CSS
- Style objects
- React Native's
StyleSheet - CSS Modules
- React Style
- JSS
- Aphrodite
- Glamor
- Fela
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 buildDeveloping
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:watchCredits
Technical inspiration:
License
The MIT License (MIT)
Copyright © 2018 Rex Software
4 years ago