Skip to content

Instantly share code, notes, and snippets.

@necolas
Last active September 15, 2015 09:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save necolas/de617f3556aa1fa7bc75 to your computer and use it in GitHub Desktop.
Save necolas/de617f3556aa1fa7bc75 to your computer and use it in GitHub Desktop.
React Web Style-API

ReactWebStyle API

  1. Styles are plain objects passed to the style prop.
  2. WebComponent hijacks style.
  3. WebComponent converts every unique declaration to single-purpose class (i.e., .abcd { position: relative; }) that is added to the component's className.
  4. Either a library like free-style injects a <style> element with all classes needed to render the existing document, or CSS is statically extracted.
  5. Dynamic styles are explicitly labelled (escape hatch) and written as inline styles on the DOM node.

WebComponent

Author style as plain objects and pass them to the style attribute.

props

  • element: Function|String="div"
  • style: Object

example

import Theme from './theme';

<WebComponent element="nav" style={{
  alignItems: 'center',
  justifyContent: 'space-between',
  marginTop: Theme.VERTICAL_SPACE_UNIT * 2
}} />

Produces this CSS (example hashes):

.a1lkd9i { align-items: center; }
.9b5klpp { justify-content: space-between; }
.apknvbu { margin-top: 2rem; }

And this rendered HTML:

<nav class="a1lkd9i 9b5klpp apknvbu">...</nav>

dynamic(value)

Wraps dynamic styles and writes them to the DOM node rather than the stylesheet.

example

<MyComponent style={{
  opacity: ReactWebStyle.dynamic(this.state.opacity)
}} />

Produces:

<div style="opacity:0.78">...</div>

filterStyleProps(propTypes, props)

Filters out all style properties that are not defined in the Constuctor's propTypes. Helps define explicit style APIs, like react-native, allowing easier SDK / UI library development.

example

class Example extends React.Component {
  static propTypes = {
    // only accept 'color' and 'width' styles
    style: PropTypes.shape({
      color: PropTypes.string,
      width: PropTypes.oneOf(['number', 'string'])
    })
  }
  
  static defaultPropTypes = {
    style: {}
  }
  
  render() {
    return (
      <WebComponent {...this.props} style={
        ReactWebStyle.filterStyleProps(this.constructor.PropTypes, this.props)
      } />
    );
  }
}

React.render(<Example style={{ color: 'red', padding: '10px' }} />, document.body);

Produces:

<div className="aHashForColorRed">...</div>

See View.jsx for an example of how a react-web-sdk could provide a partial web implementation of react-native's View.

See CustomView.jsx for an example of how an application-specific view could define a small style-API and build upon the SDK's View and Text primitives.

import ReactWebStyle, {WebComponent} from 'react-web-style';
import React, {PropTypes} from 'react';
import {Text, View} from 'react-web-sdk';
const styles = {
root: {
borderColor: 'red',
padding: '10px',
position: 'absolute',
'@media (min-width: 500px)': {
borderColor: 'blue'
}
},
text: {
fontSize: '1rem',
fontWeight: 'bold'
}
});
class CustomView extends React.Component {
static propTypes = {
style: PropTypes.shape({
top: PropTypes.number
})
}
static defaultPropTypes = {
style: {
top: '0'
}
}
render() {
return (
<View
element="span"
style={[style.root, {
// marks as inline-style rather than class
top: ReactWebStyle.dynamic(this.props.style.top)
}]}
/>
<Text style={style.text}>
Positioned {this.props.style.top} from top
</Text>
</View>
);
}
}
export default CustomView;
import ReactWebStyle, {WebComponent} from 'react-web-style';
import React, {PropTypes} from 'react';
import BackgroundPropTypes from './BackgroundPropTypes';
import BorderThemePropTypes from './BorderThemePropTypes';
import BoxShadowPropTypes from './BoxShadowPropTypes';
import LayoutPropTypes from './LayoutPropTypes';
import OpacityPropTypes from './OpacityPropTypes';
import OverflowPropTypes from './OverflowPropTypes';
import TransformPropTypes from './TransformPropTypes';
const styles = {
'default': {
alignItems: 'stretch',
border: 0,
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
listStyle: 'none',
margin: 0,
padding: 0,
position: 'relative'
},
pointerEvents: {
'auto': {
pointerEvents: 'auto'
},
'none': {
pointerEvents: 'none'
},
'box-none': {
pointerEvents: 'none',
'*': {
pointerEvents: 'auto'
}
},
'box-only': {
pointerEvents: 'auto',
'*': {
pointerEvents: 'none'
}
}
}
});
class View extends React.Component {
static propTypes = {
element: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string
]),
style: PropTypes.shape({
...BackgroundPropTypes,
...BorderThemePropTypes,
...BoxShadowPropTypes,
...LayoutPropTypes,
...OpacityPropTypes,
...OverflowPropTypes
...TransformPropTypes
}),
pointerEvents: PropTypes.oneOf([
'auto',
'box-none',
'box-only',
'none'
])
}
static defaultPropTypes = {
element: 'div',
style: {},
pointerEvents: 'auto'
}
render() {
return (
<WebComponent {...this.props} style={[
// default View styles
style.default,
// filter out any styles not defined in View's style-API
ReactWebStyle.filterStyleProps(this.constructor.PropTypes, this.props),
// merge custom pointerEvents implementation
style.pointerEvents[this.props.pointerEvents]
]} />
);
}
}
export default View;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment