@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 styled 3. 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
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
andclassName
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()
:
- Find available styles objects from:
defaultStyles
given as argument tostyled
styles
prop given to components created bystyled
- Component matches from
StylesProvider
- When
styles
is 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
style
and/orclassName
properties - Joins
className
strings from all temporary objects - Assigns all
style
objects 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
styledName
prop supplied to a renderedstyled
component - The name of the Wrapped Component's
class
name - 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:
- 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 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
4 years ago