Last active
April 8, 2017 00:43
-
-
Save theengineear/6e6e1337d996feb25eb11fccbe27c3fc to your computer and use it in GitHub Desktop.
Standardized method of styling React components to promote more flexibility in reusable components.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react'; | |
import { StyleSheet, css } from 'aphrodite'; | |
class MyComponent extends Component { | |
computeStyle() { | |
return { | |
// Dynamically generated style that cannot be simply enumerated | |
// in a sheet. | |
}; | |
} | |
render() { | |
// We *always* accept `sheets` and `styles` props which are arrays of | |
// custom sheets and styles, respectively, that the caller can supply. | |
const { sheets: customSheets, styles: customStyles } = this.props; | |
// The common thing to do is merge some props from the same sheets or | |
// the same styles. The goal here is two force ourselves to always | |
// consider the caller's styling needs. | |
const sheets = [sheet, ...customSheets]; | |
const styles = [this.computeStyle(), ...customStyles]; | |
// Here, note that we can easily use our opinionated sheet and | |
// dynamically-computed style while including custom overrides. | |
return ( | |
<div className={mcss(sheets, 'base')} style={style(styles, 'base')}> | |
<div | |
className={mcss(sheets, 'text')} | |
style={style(styles, 'text')} | |
> | |
{'I can has stylez?'} | |
</div> | |
</div> | |
); | |
} | |
} | |
MyComponent.propTypes = { | |
sheets: PropTypes.array, | |
styles: PropTypes.array | |
}; | |
export default MyComponent; | |
// styles.js | |
// Utilities to make applying multiple styles/sheets simpler. | |
// Deeply flattens an array. | |
// [[A], [B, C, [D]]] -> [A, B, C, D] | |
function flatten(list) { | |
return list.reduce((r, x) => r.concat(Array.isArray(x) ? flatten(x) : x), [ | |
]); | |
} | |
// Pull props out of objects and only returns an array of *truthy* values. | |
function _map(...args) { | |
let nestedArray; | |
if (args.any(arg => typeof arg === 'string')) { | |
// Multiple objects for all props --> `_map(objects, 'base', 'hover')`. | |
const [objs = [], ...props] = args; | |
const trueProps = props.filter(Boolean); | |
const trueObjects = objs.filter(Boolean); | |
nestedArray = trueProps.map(prop => trueObjects.map(obj => obj[prop])); | |
} else { | |
// Per-prop objects --> `_map([obj0, 'base'], [obj1, 'hover'])`. | |
nestedArray = args.map( | |
([objects = [], prop]) => | |
prop ? objects.filter(Boolean).map(obj => obj[prop]) : null | |
); | |
} | |
return flatten(nestedArray).filter(Boolean); | |
} | |
// A utility to make applying multiple styles simpler. | |
function style(...args) { | |
// Optionally do some validation here... E.g., no nested styles or ':hover'. | |
const objects = _map(...args); | |
return Object.assign({}, ...objects); | |
} | |
// A utility to make applying multiple sheets simpler (multiple-css). | |
function mcss(...args) { | |
return css(_map(...args)); | |
} | |
// MyComponent.css.js | |
// Where there are two exports: | |
// export styleObject; | |
// export default styles; | |
const styleObject = { | |
base: { width: '100px', height: '100px', color: 'red' }, | |
text: { font: '16px', ':hover': { color: 'blue' } } | |
}; | |
const sheet = StyleSheet.create(styleObject); | |
// Caveats. There are always caveats. | |
// | |
// 1. This basically binds users of this lib to Aphrodite. I think this is OK as | |
// long as we expose Aphrodite's `StyleSheet.create` function as a utility | |
// function so folks will not need to install `aphrodite` as a first-order dep. | |
// | |
// 2. Dynamically changing values are not well-handled by `aphrodite`. I.e., if | |
// you want to have a `{':hover': {color: getRandomColor()}}`... too bad. Note | |
// that this particular example cannot be handled by inlining since React does | |
// not support `:hover` either in `style={{ .. }}`. | |
// | |
// 3. If we *do* have dynamically changing values that *are* valid, I'd suggest | |
// that we *not* try to make a special spec out of it, but rater let the | |
// developer of the component choose a prop name that will suffice. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment