[add] StyleSheet API

Initial StyleSheet implementation for Web. Converts style object
declarations to "atomic" CSS rules.

Close gh-25
This commit is contained in:
Nicolas Gallagher
2015-10-17 17:40:54 -07:00
parent b59bdb17b2
commit cd89f88d96
36 changed files with 809 additions and 1048 deletions

View File

@@ -68,7 +68,7 @@ not want to merge into the project.
Development commands:
* `npm run build` build the library
* `npm run dev` start the dev server and develop against live examples
* `npm run examples` start the dev server and develop against live examples
* `npm run lint` run the linter
* `npm run test` run the linter and unit tests

186
README.md
View File

@@ -3,8 +3,8 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
The core [React Native][react-native-url] components adapted and expanded upon
for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
[React Native][react-native-url] components and APIs for the Web.
~19 KB minified and gzipped.
* [Slack: reactiflux channel #react-native-web][slack-url]
* [Gitter: react-native-web][gitter-url]
@@ -12,7 +12,8 @@ for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
## Table of contents
* [Install](#install)
* [Use](#use)
* [Example](#example)
* [APIs](#APIs)
* [Components](#components)
* [Styling](#styling)
* [Contributing](#contributing)
@@ -25,32 +26,107 @@ for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
npm install --save react react-native-web
```
## Use
## Example
React Native for Web exports its components and a reference to the `React`
installation. Styles are authored in JavaScript as plain objects.
installation. Styles are defined with, and used as JavaScript objects.
Component:
```js
import React, { View } from 'react-native-web'
import React, { Image, StyleSheet, Text, View } from 'react-native-web'
class MyComponent extends React.Component {
const Title = ({ children }) => <Text style={styles.title}>{children}</Text>
const Summary = ({ children }) => (
<View style={styles.text}>
<Text style={styles.subtitle}>{children}</Text>
</View>
)
class App extends React.Component {
render() {
return (
<View style={styles.root} />
<View style={styles.row}>
<Image
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png' }}
style={styles.image}
/>
<Title>React Native Web</Title>
<Summary>Build high quality web apps using React</Summary>
</View>
)
}
}
},
})
const styles = {
root: {
borderColor: 'currentcolor'
borderWidth: '5px',
flexDirection: 'row'
height: '5em'
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
margin: 40
},
image: {
height: 40,
marginRight: 10,
width: 40,
},
text: {
flex: 1,
justifyContent: 'center'
},
title: {
fontSize: '1.25rem',
fontWeight: 'bold'
},
subtitle: {
fontSize: '1rem'
}
}
})
```
Pre-render styles on the server:
```js
// server.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
const html = React.renderToString(<App />);
const css = StyleSheet.renderToString();
const Html = () => (
<html>
<head>
<style id="react-stylesheet">{css}</style>
</head>
<body>
<div id="react-root" dangerouslySetInnerHTML={{ __html: html }} />
</body>
</html>
)
```
Render styles on the client:
```js
// client.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
React.render(
<App />,
document.getElementById('react-root')
)
document.getElementById('stylesheet').textContent = StyleSheet.renderToString()
```
## APIs
### [`StyleSheet`](docs/apis/StyleSheet.md)
StyleSheet is a style abstraction that transforms inline styles to CSS on the
client or the server. It provides a minimal CSS reset.
## Components
### [`Image`](docs/components/Image.md)
@@ -88,78 +164,14 @@ The fundamental UI building block using flexbox for layout.
## Styling
React Native for Web provides a mechanism to declare all your styles in
JavaScript within your components. The `View` component makes it easy to build
common layouts with flexbox, such as stacked and nested boxes with margin
and padding. See this [guide to flexbox][flexbox-guide-url].
React Native for Web relies on styles being defined in JavaScript.
Authoring `style` is no different to the existing use of inline styles in
React, but most inline styles are converted to single-purpose class names. The
current implementation includes 300+ precomputed CSS declarations (~4.5KB
gzipped) that covers many common property-value pairs. A more sophisticated
build-time implementation may produce a slightly larger CSS file for large
apps, and fall back to fewer inline styles. Read more about the [styling
strategy](docs/style.md).
The `View` component makes it easy to build common layouts with flexbox, such
as stacked and nested boxes with margin and padding. See this [guide to
flexbox][flexbox-guide-url].
```js
import React, { Image, Text, View } from 'react-native-web'
class App extends React.Component {
render() {
return (
<View style={styles.row}>
<Image
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png' }}
style={styles.image}
/>
<View style={styles.text}>
<Text style={styles.title}>
React Native Web
</Text>
<Text style={styles.subtitle}>
Build high quality web apps using React
</Text>
</View>
</View>
)
},
})
const styles = {
row: {
flexDirection: 'row',
margin: 40
},
image: {
height: 40,
marginRight: 10,
width: 40,
},
text: {
flex: 1,
justifyContent: 'center'
},
title: {
fontSize: '1.25rem',
fontWeight: 'bold'
},
subtitle: {
fontSize: '1rem'
}
}
```
Combine and override style objects:
```js
import baseStyle from './baseStyle'
const buttonStyle = {
...baseStyle,
backgroundColor: '#333',
color: '#fff'
}
```
Styling components can be achieved with inline styles or the use of
[StyleSheet](docs/apis/StyleSheet.md).
## Contributing

View File

@@ -25,14 +25,6 @@ if (process.env.NODE_ENV === 'production') {
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: [
'style-loader',
'css-loader?module&localIdentName=[hash:base64:5]',
'autoprefixer-loader'
].join('!')
},
{
test: /\.jsx?$/,
exclude: /node_modules/,

117
docs/apis/StyleSheet.md Normal file
View File

@@ -0,0 +1,117 @@
# StyleSheet
React Native for Web will automatically vendor-prefix styles applied to the
libraries components. The `StyleSheet` abstraction converts predefined styles
to CSS without a compile-time step. Some styles cannot be resolved outside of
the render loop and are applied as inline styles.
The `style`-to-`className` conversion strategy is optimized to minimize the
amount of CSS required. Unique declarations are defined using "atomic" CSS a
unique class name for a unique declaration.
React Native for Web includes a CSS reset to remove unwanted user agent styles
from elements and pseudo-elements beyond the reach of React (e.g., `html` and
`body`).
Create a new StyleSheet:
```
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
},
})
```
Use styles:
```js
<View style={styles.container}>
<Text
style={{
...styles.title,
...(this.props.isActive && styles.activeTitle)
}}
/>
</View>
```
Render styles on the server or in the browser:
```js
StyleSheet.renderToString()
```
## Methods
**create**(obj: {[key: string]: any})
**renderToString**()
## Strategy
Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
files. Each new component adds new rules to the stylesheet.
![](../static/styling-strategy.png)
React Native for Web uses an alternative strategy: mapping declarations to
declarations.
For example:
```js
<View style={styles.root}>...</View>
const styles = StyleSheet.create({
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
})
```
Yields (in development):
```html
<div className="background:transparent display:flex flexGrow:1 justifyContent:center">...</div>
```
And is backed by the following CSS:
```css
.background\:transparent {background:transparent;}
.display\:flex {display:flex;}
.flexGrow\:1 {flex-grow:1;}
.justifyContext\:center {justify-content:center;}
```
In production the class names are obfuscated.
(CSS libraries like [Atomic CSS](http://acss.io/),
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
stylesheet growth in a similar way. But they're CSS utility libraries, each with a
particular set of classes and features to learn. All of them require developers
to manually connect CSS classes for given styles.)
## Media Queries, pseudo-classes, and pseudo-elements
Media Queries in JavaScript can be used to modify the render tree and styles.
This has the benefit of co-locating breakpoint-specific DOM and style changes.
Pseudo-classes like `:hover` and `:focus` can be replaced with JavaScript
events.
Pseudo-elements are not supported.

View File

@@ -1,123 +0,0 @@
# Styling strategy
Using the `style` attribute would normally produce inline styles. There are
several existing approaches to using the `style` attribute, some of which
convert inline styles to static CSS:
[jsxstyle](https://github.com/petehunt/jsxstyle),
[react-free-style](https://github.com/blakeembrey/react-free-style/),
[react-inline](https://github.com/martinandert/react-inline),
[react-native](https://facebook.github.io/react-native/),
[react-style](https://github.com/js-next/react-style),
[stilr](https://github.com/kodyl/stilr).
## Style syntax: native vs proprietary data structure
React Native for Web diverges from React Native by using plain JS objects for
styles:
```js
<Text style={styles.root}>...</Text>
const styles = {
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
};
```
Most approaches to managing style in React introduce a proprietary data
structure, often via an implementation of `Stylesheet.create`.
```js
<Text style={styles.root}>...</Text>
const styles = Stylesheet.create({
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
});
```
## JS-to-CSS: conversion strategies
Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
files. Each new component adds new rules to the stylesheet.
![](../static/styling-strategy.png)
One strategy for converting styles from JS to CSS is to map style objects to
CSS rules. Another strategy is to map declarations to declarations.
React Native for Web currently includes a proof-of-concept implementation of
the latter strategy. This results in smaller CSS files because all applications
has fewer unique declarations than total declarations. Creating a new component
with no new unique declarations results in no change to the CSS file.
For example:
```js
<Text style={styles.root}>...</Text>
const styles = {
root: {
background: 'transparent',
display: 'flex',
flexGrow: 1,
justifyContent: 'center'
}
};
```
Yields:
```html
<span className="_abcde _fghij _klmno _pqrst">...</span>
```
And is backed by:
```css
._abcde { background: transparent }
._fghij { display: flex }
._klmno { flex-grow: 1 }
._pqrst { justify-content: center }
```
The current implementation uses a precomputed CSS library of single-declaration
rules, with obfuscated selectors. This handles a signficant portion of possible
declarations. A build-time implementation would produce more accurate CSS
files and fall through to inline styles significantly less often.
(CSS libraries like [Atomic CSS](http://acss.io/),
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
stylesheet growth in a similar way. But they're CSS utility libraries, each with a
particular set of classes and features to learn. All of them require developers
to manually connect CSS classes for given styles.)
## Dynamic styles: use inline styles
Some styles cannot be resolved ahead of time and continue to rely on inline
styles:
```js
<View style={{ backgroundColor: (Math.random() > 0.5 ? 'red' : 'black') }}>...</Text>
```
## Media Queries, pseudo-classes, and pseudo-elements
Media Queries could be replaced with `mediaMatch`. This would have the added
benefit of co-locating breakpoint-specific DOM and style changes. Perhaps Media
Query data could be accessed on `this.content`?
Pseudo-classes like `:hover` and `:focus` can be handled with JavaScript.
Pseudo-elements should be avoided in general, but for particular cases like
`::placeholder` it might be necessary to reimplement it in the `TextInput`
component (see React Native's API).

View File

@@ -8,7 +8,7 @@
],
"scripts": {
"build": "rm -rf ./dist && webpack --config config/webpack.config.publish.js --sort-assets-by --progress",
"dev": "webpack-dev-server --config config/webpack.config.example.js --inline --colors --quiet",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config src",
"prepublish": "NODE_ENV=publish npm run build",
"test": "npm run lint && npm run test:unit",
@@ -16,24 +16,22 @@
"test:watch": "npm run test:unit -- --no-single-run"
},
"dependencies": {
"inline-style-prefixer": "^0.3.3",
"react": ">=0.13.3",
"react-swipeable": "^3.0.2",
"react-tappable": "^0.6.0",
"react-textarea-autosize": "^2.5.3"
},
"devDependencies": {
"autoprefixer-loader": "^3.1.0",
"babel-core": "^5.8.23",
"babel-eslint": "^4.1.1",
"babel-loader": "^5.3.2",
"babel-runtime": "^5.8.20",
"css-loader": "^0.18.0",
"eslint": "^1.3.1",
"eslint-config-standard": "^4.3.1",
"eslint-config-standard-react": "^1.0.4",
"eslint-plugin-react": "^3.3.1",
"eslint-plugin-standard": "^1.3.0",
"extract-text-webpack-plugin": "^0.8.2",
"karma": "^0.13.9",
"karma-browserstack-launcher": "^0.1.5",
"karma-chrome-launcher": "^0.2.0",
@@ -45,7 +43,6 @@
"mocha": "^2.3.0",
"node-libs-browser": "^0.5.2",
"object-assign": "^4.0.1",
"style-loader": "^0.12.3",
"webpack": "^1.12.1",
"webpack-dev-server": "^1.10.1"
},
@@ -54,5 +51,14 @@
"repository": {
"type": "git",
"url": "git://github.com/necolas/react-native-web.git"
}
},
"tags": [
"react"
],
"keywords": [
"react",
"react-component",
"react-native",
"web"
]
}

View File

@@ -1,6 +1,6 @@
import React, { PropTypes } from 'react'
import restyle from './modules/restyle'
import stylePropTypes from './modules/stylePropTypes'
import StylePropTypes from '../../modules/StylePropTypes'
import StyleSheet from '../../modules/StyleSheet'
class CoreComponent extends React.Component {
static propTypes = {
@@ -13,18 +13,15 @@ class CoreComponent extends React.Component {
testID: PropTypes.string
}
static stylePropTypes = stylePropTypes;
static defaultProps = {
className: '',
component: 'div'
}
static stylePropTypes = StylePropTypes;
render() {
const {
className,
component: Component,
style,
testID,
...other
} = this.props
@@ -32,7 +29,7 @@ class CoreComponent extends React.Component {
return (
<Component
{...other}
{...restyle({ className, style })}
{...StyleSheet.resolve(other)}
data-testid={testID}
/>
)

View File

@@ -1,47 +0,0 @@
export default function prefixStyles(style) {
if (style.hasOwnProperty('flexBasis')) {
style = {
WebkitFlexBasis: style.flexBasis,
msFlexBasis: style.flexBasis,
...style
}
}
if (style.hasOwnProperty('flexGrow')) {
style = {
WebkitBoxFlex: style.flexGrow,
WebkitFlexGrow: style.flexGrow,
msFlexPositive: style.flexGrow,
...style
}
}
if (style.hasOwnProperty('flexShrink')) {
style = {
WebkitFlexShrink: style.flexShrink,
msFlexNegative: style.flexShrink,
...style
}
}
// NOTE: adding `;` to the string value prevents React from automatically
// adding a `px` suffix to the unitless value
if (style.hasOwnProperty('order')) {
style = {
WebkitBoxOrdinalGroup: `${parseInt(style.order, 10) + 1};`,
WebkitOrder: `${style.order}`,
msFlexOrder: `${style.order}`,
...style
}
}
if (style.hasOwnProperty('transform')) {
style = {
WebkitTransform: style.transform,
msTransform: style.transform,
...style
}
}
return style
}

View File

@@ -1,40 +0,0 @@
import autoprefix from './autoprefix'
import styles from '../../../modules/styles'
/**
* Get the HTML class that corresponds to a style declaration
* @param prop {string} prop name
* @param style {Object} style
* @return {string} class name
*/
function getSinglePurposeClassName(prop, style) {
const className = `${prop}-${style[prop]}`
if (style.hasOwnProperty(prop) && styles[className]) {
return styles[className]
}
}
/**
* Replace inline styles with single purpose classes where possible
* @param props {Object} React Element properties
* @return {Object}
*/
export default function stylingStrategy(props) {
let className
let style = {}
const classList = [ props.className ]
for (const prop in props.style) {
const styleClass = getSinglePurposeClassName(prop, props.style)
if (styleClass) {
classList.push(styleClass)
} else {
style[prop] = props.style[prop]
}
}
className = classList.join(' ')
style = autoprefix(style)
return { className, style }
}

View File

@@ -1,5 +1,6 @@
/* global window */
import { pickProps } from '../../modules/filterObjectProps'
import StyleSheet from '../../modules/StyleSheet'
import CoreComponent from '../CoreComponent'
import ImageStylePropTypes from './ImageStylePropTypes'
import React, { PropTypes } from 'react'
@@ -13,7 +14,7 @@ const STATUS_IDLE = 'IDLE'
const imageStyleKeys = Object.keys(ImageStylePropTypes)
const styles = {
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
backgroundColor: 'lightgray',
@@ -49,7 +50,7 @@ const styles = {
backgroundSize: '100% 100%'
}
}
}
})
class Image extends React.Component {
constructor(props, context) {
@@ -195,10 +196,10 @@ class Image extends React.Component {
accessible={accessible}
component='div'
style={{
...(styles.initial),
...styles.initial,
...resolvedStyle,
...(backgroundImage && { backgroundImage }),
...(styles.resizeMode[resizeMode])
...styles.resizeMode[resizeMode]
}}
testID={testID}
>

View File

@@ -1,11 +1,12 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import StyleSheet from '../../modules/StyleSheet'
import TextStylePropTypes from './TextStylePropTypes'
const textStyleKeys = Object.keys(TextStylePropTypes)
const styles = {
const styles = StyleSheet.create({
initial: {
color: 'inherit',
display: 'inline-block',
@@ -20,7 +21,7 @@ const styles = {
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}
})
class Text extends React.Component {
static propTypes = {

View File

@@ -1,12 +1,13 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import StyleSheet from '../../modules/StyleSheet'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputStylePropTypes from './TextInputStylePropTypes'
const textInputStyleKeys = Object.keys(TextInputStylePropTypes)
const styles = {
const styles = StyleSheet.create({
initial: {
appearance: 'none',
backgroundColor: 'transparent',
@@ -17,7 +18,7 @@ const styles = {
font: 'inherit',
padding: 0
}
}
})
class TextInput extends React.Component {
static propTypes = {

View File

@@ -1,14 +1,15 @@
import React, { PropTypes } from 'react'
import Tappable from 'react-tappable'
import View from '../View'
import StyleSheet from '../../modules/StyleSheet'
const styles = {
const styles = StyleSheet.create({
initial: {
...View.defaultProps.style,
cursor: 'pointer',
userSelect: undefined
}
}
})
class Touchable extends React.Component {
constructor(props, context) {

View File

@@ -1,11 +1,12 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import StyleSheet from '../../modules/StyleSheet'
import ViewStylePropTypes from './ViewStylePropTypes'
const viewStyleKeys = Object.keys(ViewStylePropTypes)
const styles = {
const styles = StyleSheet.create({
// https://github.com/facebook/css-layout#default-values
initial: {
alignItems: 'stretch',
@@ -26,7 +27,7 @@ const styles = {
font: 'inherit',
textAlign: 'inherit'
}
}
})
class View extends React.Component {
static propTypes = {

View File

@@ -1,12 +1,12 @@
import React, { Image, Swipeable, Text, TextInput, Touchable, View } from '.'
import React, { Image, StyleSheet, Swipeable, Text, TextInput, Touchable, View } from '.'
const { Component, PropTypes } = React
class Heading extends Component {
class Heading extends React.Component {
static propTypes = {
children: Text.propTypes.children,
level: PropTypes.oneOf(['1', '2', '3']),
size: PropTypes.oneOf(['xlarge', 'large', 'normal'])
children: PropTypes.any,
level: PropTypes.string,
size: PropTypes.string
}
static defaultProps = {
@@ -27,7 +27,7 @@ class Heading extends Component {
}
}
const headingStyles = {
const headingStyles = StyleSheet.create({
size: {
xlarge: {
fontSize: '2rem',
@@ -44,7 +44,7 @@ const headingStyles = {
marginTop: '0.5em'
}
}
}
})
class Example extends Component {
static propTypes = {
@@ -205,7 +205,7 @@ class Example extends Component {
}
}
const styles = {
const styles = StyleSheet.create({
root: {
maxWidth: '600px',
margin: '0 auto'
@@ -226,7 +226,7 @@ const styles = {
pointerEventsBox: {
alignItems: 'center',
borderWidth: '1px',
flexGrow: '1',
flexGrow: 1,
height: '100px',
justifyContent: 'center'
},
@@ -236,6 +236,8 @@ const styles = {
height: '200px',
justifyContent: 'center'
}
}
})
React.render(<Example />, document.getElementById('react-root'))
document.getElementById('react-stylesheet').textContent = StyleSheet.renderToString()

View File

@@ -4,5 +4,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="The core React Native components adapted and expanded upon for the web">
<style>html { font-family: sans-serif; }</style>
<style id="react-stylesheet"></style>
<div id="react-root"></div>
<script src="/example.js"></script>

View File

@@ -1,5 +1,7 @@
import React from 'react'
import StyleSheet from './modules/StyleSheet'
// components
import Image from './components/Image'
import ListView from './components/ListView'
@@ -13,6 +15,9 @@ import View from './components/View'
export default React
export {
StyleSheet,
// components
Image,
ListView,
ScrollView,

View File

@@ -1,11 +1,7 @@
import { PropTypes } from 'react'
const numberOrString = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
])
const { string } = PropTypes
const { number, string } = PropTypes
const numberOrString = PropTypes.oneOfType([ number, string ])
export default {
alignContent: string,
@@ -20,21 +16,22 @@ export default {
backgroundPosition: string,
backgroundRepeat: string,
backgroundSize: string,
borderColor: numberOrString,
borderBottomColor: numberOrString,
borderLeftColor: numberOrString,
borderRightColor: numberOrString,
borderTopColor: numberOrString,
border: string,
borderColor: string,
borderBottomColor: string,
borderLeftColor: string,
borderRightColor: string,
borderTopColor: string,
borderRadius: numberOrString,
borderTopLeftRadius: numberOrString,
borderTopRightRadius: numberOrString,
borderBottomLeftRadius: numberOrString,
borderBottomRightRadius: numberOrString,
borderStyle: numberOrString,
borderBottomStyle: numberOrString,
borderLeftStyle: numberOrString,
borderRightStyle: numberOrString,
borderTopStyle: numberOrString,
borderStyle: string,
borderBottomStyle: string,
borderLeftStyle: string,
borderRightStyle: string,
borderTopStyle: string,
borderWidth: numberOrString,
borderBottomWidth: numberOrString,
borderLeftWidth: numberOrString,
@@ -47,6 +44,7 @@ export default {
cursor: string,
direction: string,
display: string,
flex: string,
flexBasis: string,
flexDirection: string,
flexGrow: numberOrString,

View File

@@ -0,0 +1,99 @@
import hyphenate from './hyphenate'
import normalizeValue from './normalizeValue'
import prefixer from './prefixer'
export default class Store {
constructor(
initialState:Object = {},
options:Object = { obfuscateClassNames: false }
) {
this._counter = 0
this._classNames = { ...initialState.classNames }
this._declarations = { ...initialState.declarations }
this._options = options
}
get(property, value) {
const normalizedValue = normalizeValue(property, value)
const key = this._getDeclarationKey(property, normalizedValue)
return this._classNames[key]
}
set(property, value) {
if (value != null) {
const normalizedValue = normalizeValue(property, value)
const values = this._getPropertyValues(property) || []
if (values.indexOf(normalizedValue) === -1) {
values.push(normalizedValue)
this._setClassName(property, normalizedValue)
this._setPropertyValues(property, values)
}
}
}
toString() {
const obfuscate = this._options.obfuscateClassNames
// sort the properties to ensure shorthands are first in the cascade
const properties = Object.keys(this._declarations).sort()
// transform the class name to a valid CSS selector
const getCssSelector = (property, value) => {
let className = this.get(property, value)
if (!obfuscate && className) {
className = className.replace(/[:?.%\\$#]/g, '\\$&')
}
return className
}
// transform the declarations into CSS rules with vendor-prefixes
const buildCSSRules = (property, values) => {
return values.reduce((cssRules, value) => {
const declarations = prefixer.prefix({ [property]: value })
const cssDeclarations = Object.keys(declarations).reduce((str, prop) => {
str += `${hyphenate(prop)}:${value};`
return str
}, '')
const selector = getCssSelector(property, value)
cssRules += `\n.${selector}{${cssDeclarations}}`
return cssRules
}, '')
}
const css = properties.reduce((css, property) => {
const values = this._declarations[property]
css += buildCSSRules(property, values)
return css
}, '')
return (`/* ${this._counter} unique declarations */${css}`)
}
_getDeclarationKey(property, value) {
return `${property}:${value}`
}
_getPropertyValues(property) {
return this._declarations[property]
}
_setPropertyValues(property, values) {
this._declarations[property] = values.map(value => normalizeValue(property, value))
}
_setClassName(property, value) {
const key = this._getDeclarationKey(property, value)
const exists = !!this._classNames[key]
if (!exists) {
this._counter += 1
if (this._options.obfuscateClassNames) {
this._classNames[key] = `_rn_${this._counter}`
} else {
const val = `${value}`.replace(/\s/g, '-')
this._classNames[key] = `${property}:${val}`
}
}
}
}

View File

@@ -0,0 +1,33 @@
/* eslint-env mocha */
import assert from 'assert'
import getStyleObjects from '../getStyleObjects'
const fixture = {
rule: {
margin: 0,
padding: 0
},
nested: {
auto: {
backgroundSize: 'auto'
},
contain: {
backgroundSize: 'contain'
}
},
ignored: {
pading: 0
}
}
suite('modules/StyleSheet/getStyleObjects', () => {
test('returns only style objects', () => {
const actual = getStyleObjects(fixture)
assert.deepEqual(actual, [
{ margin: 0, padding: 0 },
{ backgroundSize: 'auto' },
{ backgroundSize: 'contain' }
])
})
})

View File

@@ -0,0 +1,13 @@
/* eslint-env mocha */
import assert from 'assert'
import hyphenate from '../hyphenate'
suite('modules/StyleSheet/hyphenate', () => {
test('style property', () => {
assert.equal(hyphenate('alignItems'), 'align-items')
})
test('vendor prefixed style property', () => {
assert.equal(hyphenate('WebkitAppearance'), '-webkit-appearance')
})
})

View File

@@ -0,0 +1,34 @@
/* eslint-env mocha */
import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert'
import StyleSheet from '..'
const styles = { root: { border: 0 } }
suite('modules/StyleSheet', () => {
setup(() => {
StyleSheet.destroy()
})
test('create', () => {
assert.equal(StyleSheet.create(styles), styles)
})
test('renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet.renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 1 unique declarations */\n` +
`.border\\:0px{border:0px;}`
)
})
test('resolve', () => {
const props = { className: 'className', style: styles.root }
const expected = { className: 'className border:0px', style: {} }
StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected)
})
})

View File

@@ -0,0 +1,15 @@
/* eslint-env mocha */
import assert from 'assert'
import isObject from '../isObject'
suite('modules/StyleSheet/isObject', () => {
test('returns "true" for objects', () => {
assert.ok(isObject({}) === true)
})
test('returns "false" for non-objects', () => {
assert.ok(isObject(function () {}) === false)
assert.ok(isObject([]) === false)
assert.ok(isObject('') === false)
})
})

View File

@@ -0,0 +1,16 @@
/* eslint-env mocha */
import assert from 'assert'
import isStyleObject from '../isStyleObject'
const style = { margin: 0 }
const notStyle = { root: style }
suite('modules/StyleSheet/isStyleObject', () => {
test('returns "true" for style objects', () => {
assert.ok(isStyleObject(style) === true)
})
test('returns "false" for non-style objects', () => {
assert.ok(isStyleObject(notStyle) === false)
})
})

View File

@@ -0,0 +1,13 @@
/* eslint-env mocha */
import assert from 'assert'
import normalizeValue from '../normalizeValue'
suite('modules/StyleSheet/normalizeValue', () => {
test('normalizes property values requiring units', () => {
assert.deepEqual(normalizeValue('margin', 0), '0px')
})
test('ignores unitless property values', () => {
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
})
})

View File

@@ -0,0 +1,127 @@
/* eslint-env mocha */
import assert from 'assert'
import Store from '../Store'
suite('modules/StyleSheet/Store', () => {
suite('the constructor', () => {
test('initialState', () => {
const initialState = { classNames: { 'alignItems:center': '__classname__' } }
const store = new Store(initialState)
assert.deepEqual(store._classNames['alignItems:center'], '__classname__')
})
})
suite('#get', () => {
test('returns a declaration-specific className', () => {
const initialState = {
classNames: {
'alignItems:center': '__expected__',
'alignItems:flex-start': '__error__'
}
}
const store = new Store(initialState)
assert.deepEqual(store.get('alignItems', 'center'), '__expected__')
})
})
suite('#set', () => {
test('stores declarations', () => {
const store = new Store()
store.set('alignItems', 'center')
store.set('flexGrow', 0)
store.set('flexGrow', 1)
store.set('flexGrow', 2)
assert.deepEqual(store._declarations, {
alignItems: [ 'center' ],
flexGrow: [ 0, 1, 2 ]
})
})
test('human-readable classNames', () => {
const store = new Store()
store.set('alignItems', 'center')
store.set('flexGrow', 0)
store.set('flexGrow', 1)
store.set('flexGrow', 2)
assert.deepEqual(store._classNames, {
'alignItems:center': 'alignItems:center',
'flexGrow:0': 'flexGrow:0',
'flexGrow:1': 'flexGrow:1',
'flexGrow:2': 'flexGrow:2'
})
})
test('obfuscated classNames', () => {
const store = new Store({}, { obfuscateClassNames: true })
store.set('alignItems', 'center')
store.set('flexGrow', 0)
store.set('flexGrow', 1)
store.set('flexGrow', 2)
assert.deepEqual(store._classNames, {
'alignItems:center': '_rn_1',
'flexGrow:0': '_rn_2',
'flexGrow:1': '_rn_3',
'flexGrow:2': '_rn_4'
})
})
test('value normalization', () => {
const store = new Store()
store.set('flexGrow', 0)
store.set('margin', 0)
assert.deepEqual(store._declarations, {
flexGrow: [ 0 ],
margin: [ '0px' ]
})
assert.deepEqual(store._classNames, {
'flexGrow:0': 'flexGrow:0',
'margin:0px': 'margin:0px'
})
})
test('replaces space characters', () => {
const store = new Store()
store.set('margin', '0 auto')
assert.deepEqual(store.get('margin', '0 auto'), 'margin:0-auto')
})
})
suite('#toString', () => {
test('human-readable style sheet', () => {
const store = new Store()
store.set('alignItems', 'center')
store.set('marginBottom', 0)
store.set('margin', 1)
store.set('margin', 2)
store.set('margin', 3)
const expected = '/* 5 unique declarations */\n' +
'.alignItems\\:center{align-items:center;}\n' +
'.margin\\:1px{margin:1px;}\n' +
'.margin\\:2px{margin:2px;}\n' +
'.margin\\:3px{margin:3px;}\n' +
'.marginBottom\\:0px{margin-bottom:0px;}'
assert.equal(store.toString(), expected)
})
test('obfuscated style sheet', () => {
const store = new Store({}, { obfuscateClassNames: true })
store.set('alignItems', 'center')
store.set('marginBottom', 0)
store.set('margin', 1)
store.set('margin', 2)
store.set('margin', 3)
const expected = '/* 5 unique declarations */\n' +
'._rn_1{align-items:center;}\n' +
'._rn_3{margin:1px;}\n' +
'._rn_4{margin:2px;}\n' +
'._rn_5{margin:3px;}\n' +
'._rn_2{margin-bottom:0px;}'
assert.equal(store.toString(), expected)
})
})
})

View File

@@ -0,0 +1,22 @@
import isObject from './isObject'
import isStyleObject from './isStyleObject'
/**
* Recursively check for objects that are style rules.
*/
const getStyleObjects = (styles: Object): Array => {
const keys = Object.keys(styles)
return keys.reduce((rules, key) => {
const possibleRule = styles[key]
if (isObject(possibleRule)) {
if (isStyleObject(possibleRule)) {
rules.push(possibleRule)
} else {
rules = rules.concat(getStyleObjects(possibleRule))
}
}
return rules
}, [])
}
export default getStyleObjects

View File

@@ -0,0 +1 @@
export default (string) => string.replace(/([A-Z])/g, '-$1').toLowerCase()

View File

@@ -0,0 +1,75 @@
import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
import getStyleObjects from './getStyleObjects'
import prefixer from './prefixer'
import Store from './Store'
/**
* Initialize the store with pointer-event styles mapping to our custom pointer
* event classes
*/
const initialState = { classNames: predefinedClassNames }
const options = { obfuscateClassNames: process.env.NODE_ENV === 'production' }
const createStore = () => new Store(initialState, options)
let store = createStore()
/**
* Process all unique declarations
*/
const create = (styles: Object): Object => {
const rules = getStyleObjects(styles)
rules.forEach((rule) => {
Object.keys(rule).forEach(property => {
const value = rule[property]
// add each declaration to the store
store.set(property, value)
})
})
return styles
}
/**
* Destroy existing styles
*/
const destroy = () => {
store = createStore()
}
/**
* Render the styles as a CSS style sheet
*/
const renderToString = () => {
const css = store.toString()
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
/**
* Accepts React props and converts inline styles to single purpose classes
* where possible.
*/
const resolve = ({ className = '', style = {} }) => {
let _className
let _style = {}
const classList = [ className ]
for (const prop in style) {
let styleClass = store.get(prop, style[prop])
if (styleClass) {
classList.push(styleClass)
} else {
_style[prop] = style[prop]
}
}
_className = classList.join(' ')
_style = prefixer.prefix(_style)
return { className: _className, style: _style }
}
export default {
create,
destroy,
renderToString,
resolve
}

View File

@@ -0,0 +1,5 @@
const isObject = (obj) => {
return Object.prototype.toString.call(obj) === '[object Object]'
}
export default isObject

View File

@@ -0,0 +1,9 @@
import { pickProps } from '../filterObjectProps'
import StylePropTypes from '../StylePropTypes'
const isStyleObject = (obj) => {
const declarations = pickProps(obj, Object.keys(StylePropTypes))
return Object.keys(declarations).length > 0
}
export default isStyleObject

View File

@@ -0,0 +1,33 @@
const unitlessNumbers = {
boxFlex: true,
boxFlexGroup: true,
columnCount: true,
flex: true,
flexGrow: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
opacity: true,
order: true,
orphans: true,
widows: true,
zIndex: true,
zoom: true,
// SVG-related
fillOpacity: true,
strokeDashoffset: true,
strokeOpacity: true,
strokeWidth: true
}
const normalizeValues = (property, value) => {
if (!unitlessNumbers[property] && typeof value === 'number') {
value = `${value}px`
}
return value
}
export default normalizeValues

View File

@@ -0,0 +1,28 @@
/**
* Reset unwanted styles beyond the control of React inline styles
*/
export const resetCSS =
`/* React Native Web */
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
body {margin:0}
button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}`
/**
* Custom pointer event styles
*/
export const predefinedCSS =
`/* pointer-events */
._rn_pe-a {pointer-events:auto}
._rn_pe-bn {pointer-events:none}
._rn_pe-bn * {pointer-events:auto}
._rn_pe-bo {pointer-events:auto}
._rn_pe-bo * {pointer-events:none}
._rn_pe-n {pointer-events:none}`
export const predefinedClassNames = {
'pointerEvents:auto': '_rn_pe-a',
'pointerEvents:box-none': '_rn_pe-bn',
'pointerEvents:box-only': '_rn_pe-bo',
'pointerEvents:none': '_rn_pe-n'
}

View File

@@ -0,0 +1,3 @@
import Prefixer from 'inline-style-prefixer'
const prefixer = new Prefixer()
export default prefixer

View File

@@ -1,2 +0,0 @@
import styles from './styles.css'
export default styles

View File

@@ -1,688 +0,0 @@
/* align-content */
.alignContent-center { align-content: center; }
.alignContent-flex-end { align-content: flex-end; }
.alignContent-flex-start { align-content: flex-start; }
.alignContent-stretch { align-content: stretch; }
.alignContent-space-around { align-content: space-around; }
.alignContent-space-between { align-content: space-between; }
/* align-items */
.alignItems-center { align-items: center; }
.alignItems-flex-end { align-items: flex-end; }
.alignItems-flex-start { align-items: flex-start; }
.alignItems-stretch { align-items: stretch; }
.alignItems-space-around { align-items: space-around; }
.alignItems-space-between { align-items: space-between; }
/* align-self */
.alignSelf-auto { align-self: auto; }
.alignSelf-baseline { align-self: baseline; }
.alignSelf-center { align-self: center; }
.alignSelf-flex-end { align-self: flex-end; }
.alignSelf-flex-start { align-self: flex-start; }
.alignSelf-stretch { align-self: stretch; }
/* appearance */
.appearance-none { appearance: none; }
/* background-attachment */
.backgroundAttachment-fixed { background-attachment: fixed; }
.backgroundAttachment-inherit { background-attachment: inherit; }
.backgroundAttachment-local { background-attachment: local; }
.backgroundAttachment-scroll { background-attachment: scroll; }
/* background-clip */
.backgroundClip-border-box { background-clip: border-box; }
.backgroundClip-content-box { background-clip: content-box; }
.backgroundClip-inherit { background-clip: inherit; }
.backgroundClip-padding-box { background-clip: padding-box; }
/* background-color */
.backgroundColor-\#000,
.backgroundColor-black { background-color: black; }
.backgroundColor-\#fff,
.backgroundColor-white { background-color: white; }
.backgroundColor-currentcolor,
.backgroundColor-currentColor { background-color: currentcolor; }
.backgroundColor-inherit { background-color: inherit; }
.backgroundColor-transparent { background-color: transparent; }
/* background-image */
.backgroundImage { background-image: none; }
/* background-origin */
.backgroundOrigin-border-box { background-clip: border-box; }
.backgroundOrigin-content-box { background-clip: content-box; }
.backgroundOrigin-inherit { background-clip: inherit; }
.backgroundOrigin-padding-box { background-clip: padding-box; }
/* background-position */
.backgroundPosition-bottom { background-position: bottom; }
.backgroundPosition-center { background-position: center; }
.backgroundPosition-left { background-position: left; }
.backgroundPosition-right { background-position: right; }
.backgroundPosition-top { background-position: top; }
/* background-repeat */
.backgroundRepeat-inherit { background-repeat: inherit; }
.backgroundRepeat-no-repeat { background-repeat: no-repeat; }
.backgroundRepeat-repeat { background-repeat: repeat; }
.backgroundRepeat-repeat-x { background-repeat: repeat-x; }
.backgroundRepeat-repeat-y { background-repeat: repeat-y; }
.backgroundRepeat-round { background-repeat: round; }
.backgroundRepeat-space { background-repeat: space; }
/* background-size */
.backgroundSize-auto { background-size: auto; }
.backgroundSize-contain { background-size: contain; }
.backgroundSize-cover { background-size: cover; }
.backgroundSize-inherit { background-size: inherit; }
/* border-color */
.borderColor-\#fff,
.borderColor-white { border-color: white; }
.borderColor-currentcolor { border-color: currentcolor; }
.borderColor-inherit { border-color: inherit; }
.borderColor-transparent { border-color: transparent; }
/* border-bottom-color */
.borderBottomColor-\#fff,
.borderBottomColor-white { border-bottom-color: white; }
.borderBottomColor-currentcolor { border-bottom-color: currentcolor; }
.borderBottomColor-inherit { border-bottom-color: inherit; }
.borderBottomColor-transparent { border-bottom-color: transparent; }
/* border-left-color */
.borderLeftColor-\#fff,
.borderLeftColor-white { border-left-color: white; }
.borderLeftColor-currentcolor { border-left-color: currentcolor; }
.borderLeftColor-inherit { border-left-color: inherit; }
.borderLeftColor-transparent { border-left-color: transparent; }
/* border-right-color */
.borderRightColor-\#fff,
.borderRightColor-white { border-right-color: white; }
.borderRightColor-currentcolor { border-right-color: currentcolor; }
.borderRightColor-inherit { border-right-color: inherit; }
.borderRightColor-transparent { border-right-color: transparent; }
/* border-top-color */
.borderTopColor-\#fff,
.borderTopColor-white { border-top-color: white; }
.borderTopColor-currentcolor { border-top-color: currentcolor; }
.borderTopColor-inherit { border-top-color: inherit; }
.borderTopColor-transparent { border-top-color: transparent; }
/* border-style */
.borderStyle-dashed { border-style: dashed; }
.borderStyle-dotted { border-style: dotted; }
.borderStyle-inherit { border-style: inherit; }
.borderStyle-none { border-style: none; }
.borderStyle-solid { border-style: solid; }
/* border-bottom-style */
.borderBottomStyle-dashed { border-bottom-style: dashed; }
.borderBottomStyle-dotted { border-bottom-style: dotted; }
.borderBottomStyle-inherit { border-bottom-style: inherit; }
.borderBottomStyle-none { border-bottom-style: none; }
.borderBottomStyle-solid { border-bottom-style: solid; }
/* border-left-style */
.borderLeftStyle-dashed { border-left-style: dashed; }
.borderLeftStyle-dotted { border-left-style: dotted; }
.borderLeftStyle-inherit { border-left-style: inherit; }
.borderLeftStyle-none { border-left-style: none; }
.borderLeftStyle-solid { border-left-style: solid; }
/* border-right-style */
.borderRightStyle-dashed { border-right-style: dashed; }
.borderRightStyle-dotted { border-right-style: dotted; }
.borderRightStyle-inherit { border-right-style: inherit; }
.borderRightStyle-none { border-right-style: none; }
.borderRightStyle-solid { border-right-style: solid; }
/* border-top-style */
.borderTopStyle-dashed { border-top-style: dashed; }
.borderTopStyle-dotted { border-top-style: dotted; }
.borderTopStyle-inherit { border-top-style: inherit; }
.borderTopStyle-none { border-top-style: none; }
.borderTopStyle-solid { border-top-style: solid; }
/* border-width */
.borderWidth-0 { border-width: 0; }
.borderWidth-1px { border-width: 1px; }
.borderWidth-2px { border-width: 2px; }
.borderWidth-3px { border-width: 3px; }
.borderWidth-4px { border-width: 4px; }
.borderWidth-5px { border-width: 5px; }
/* border-bottom-width */
.borderBottomWidth-0 { border-bottom-width: 0; }
.borderBottomWidth-1px { border-bottom-width: 1px; }
.borderBottomWidth-2px { border-bottom-width: 2px; }
.borderBottomWidth-3px { border-bottom-width: 3px; }
.borderBottomWidth-4px { border-bottom-width: 4px; }
.borderBottomWidth-5px { border-bottom-width: 5px; }
/* border-left-width */
.borderLeftWidth-0 { border-left-width: 0; }
.borderLeftWidth-1px { border-left-width: 1px; }
.borderLeftWidth-2px { border-left-width: 2px; }
.borderLeftWidth-3px { border-left-width: 3px; }
.borderLeftWidth-4px { border-left-width: 4px; }
.borderLeftWidth-5px { border-left-width: 5px; }
/* border-right-width */
.borderRightWidth-0 { border-right-width: 0; }
.borderRightWidth-1px { border-right-width: 1px; }
.borderRightWidth-2px { border-right-width: 2px; }
.borderRightWidth-3px { border-right-width: 3px; }
.borderRightWidth-4px { border-right-width: 4px; }
.borderRightWidth-5px { border-right-width: 5px; }
/* border-top-width */
.borderTopWidth-0 { border-top-width: 0; }
.borderTopWidth-1px { border-top-width: 1px; }
.borderTopWidth-2px { border-top-width: 2px; }
.borderTopWidth-3px { border-top-width: 3px; }
.borderTopWidth-4px { border-top-width: 4px; }
.borderTopWidth-5px { border-top-width: 5px; }
/* bottom */
.bottom-0 { bottom: 0; }
.bottom-50% { bottom: 50%; }
.bottom-100% { bottom: 100%; }
/* box-sizing */
.boxSizing-border-box { box-sizing: border-box; }
.boxSizing-content-box { box-sizing: content-box; }
.boxSizing-inherit { box-sizing: inherit; }
.boxSizing-padding-box { box-sizing: padding-box; }
/* clear */
.clear-both { clear: both; }
.clear-inherit { clear: inherit; }
.clear-left { clear: left; }
.clear-none { clear: none; }
.clear-right { clear: right; }
/* color */
.color-#000,
.color-black { color: black; }
.color-\#fff,
.color-white { color: white; }
.color-inherit { color: inherit; }
.color-transparent { color: transparent; }
/* cursor */
.cursor-default { cursor: default; }
.cursor-pointer { cursor: pointer; }
/* direction */
.direction-inherit { direction: inherit; }
.direction-ltr { direction: ltr; }
.direction-rtl { direction: rtl; }
/* display */
.display-block { display: block; }
.display-contents { display: contents; }
.display-flex { display: flex; }
.display-grid { display: grid; }
.display-inherit { display: inherit; }
.display-initial { display: initial; }
.display-inline { display: inline; }
.display-inline-block { display: inline-block; }
.display-inline-flex { display: inline-flex; }
.display-inline-grid { display: inline-grid; }
.display-inline-table { display: inline-table; }
.display-list-item { display: list-item; }
.display-none { display: none; }
.display-table { display: table; }
.display-table-cell { display: table-cell; }
.display-table-column { display: table-column; }
.display-table-column-group { display: table-column-group; }
.display-table-footer-group { display: table-footer-group; }
.display-table-header-group { display: table-header-group; }
.display-table-row { display: table-row; }
.display-table-row-group { display: table-row-group; }
.display-unset { display: unset; }
/* flex-basis */
.flexBasis-0 { flex-basis: 0%; }
.flexBasis-auto { flex-basis: auto; }
/* flex-direction */
.flexDirection-column { flex-direction: column; }
.flexDirection-column-reverse { flex-direction: column-reverse; }
.flexDirection-row { flex-direction: row; }
.flexDirection-row-reverse { flex-direction: row-reverse; }
/* flex-grow */
.flexGrow-0 { flex-grow: 0; }
.flexGrow-1 { flex-grow: 1; }
.flexGrow-2 { flex-grow: 2; }
.flexGrow-3 { flex-grow: 3; }
.flexGrow-4 { flex-grow: 4; }
.flexGrow-5 { flex-grow: 5; }
.flexGrow-6 { flex-grow: 6; }
.flexGrow-7 { flex-grow: 7; }
.flexGrow-8 { flex-grow: 8; }
.flexGrow-9 { flex-grow: 9; }
.flexGrow-10 { flex-grow: 10; }
.flexGrow-11 { flex-grow: 11; }
/* flex-shrink */
.flexShrink-0 { flex-shrink: 0; }
.flexShrink-1 { flex-shrink: 1; }
.flexShrink-2 { flex-shrink: 2; }
.flexShrink-3 { flex-shrink: 3; }
.flexShrink-4 { flex-shrink: 4; }
.flexShrink-5 { flex-shrink: 5; }
.flexShrink-6 { flex-shrink: 6; }
.flexShrink-7 { flex-shrink: 7; }
.flexShrink-8 { flex-shrink: 8; }
.flexShrink-9 { flex-shrink: 9; }
.flexShrink-10 { flex-shrink: 10; }
.flexShrink-11 { flex-shrink: 11; }
/* flex-wrap */
.flexWrap-nowrap { flex-wrap: nowrap; }
.flexWrap-wrap { flex-wrap: wrap; }
.flexWrap-wrap-reverse { flex-wrap: wrap-reverse; }
/* float */
.float-left { float: left; }
.float-none { float: none; }
.float-right { float: right; }
/* font */
.font-inherit { font: inherit; }
/* font-family */
.fontFamily-inherit { font-family: inherit; }
.fontFamily-monospace { font-family: monospace; }
.fontFamily-sans-serif { font-family: sans-serif; }
.fontFamily-serif { font-family: serif; }
/* font-size */
.fontSize-100\% { font-size: 100%; }
.fontSize-inherit { font-size: inherit; }
/* font-style */
.fontStyle-inherit { font-style: inherit; }
.fontStyle-italic { font-style: italic; }
.fontStyle-normal { font-style: normal; }
.fontStyle-oblique { font-style: oblique; }
/* font-weight */
.fontWeight-100 { font-weight: 100; }
.fontWeight-200 { font-weight: 200; }
.fontWeight-300 { font-weight: 300; }
.fontWeight-400 { font-weight: 400; }
.fontWeight-500 { font-weight: 500; }
.fontWeight-600 { font-weight: 600; }
.fontWeight-700 { font-weight: 700; }
.fontWeight-800 { font-weight: 800; }
.fontWeight-900 { font-weight: 900; }
.fontWeight-bold { font-weight: bold; }
.fontWeight-bolder { font-weight: bolder; }
.fontWeight-inherit { font-weight: inherit; }
.fontWeight-lighter { font-weight: lighter; }
.fontWeight-normal { font-weight: normal; }
/* height */
.height-0 { height: 0; }
.height-10\% { height: 10%; }
.height-12\.5\% { height: 12.5%; }
.height-20\% { height: 20%; }
.height-25\% { height: 25%; }
.height-30\% { height: 30%; }
.height-37\.5\% { height: 37.5%; }
.height-40\% { height: 40%; }
.height-50\% { height: 50%; }
.height-60\% { height: 60%; }
.height-62\.5\% { height: 62.5%; }
.height-70\% { height: 70%; }
.height-75\% { height: 75%; }
.height-80\% { height: 80%; }
.height-87\.5\% { height: 87.5%; }
.height-90\% { height: 90%; }
.height-100\% { height: 100%; }
/* justify-content */
.justifyContent-center { justify-content: center; }
.justifyContent-flex-end { justify-content: flex-end; }
.justifyContent-flex-start { justify-content: flex-start; }
.justifyContent-space-around { justify-content: space-around; }
.justifyContent-space-between { justify-content: space-between; }
/* left */
.left-0 { left: 0; }
.left-50% { left: 50%; }
.left-100% { left: 100%; }
/* line-height */
.lineHeight-inherit { line-height: inherit; }
.lineHeight-normal { line-height: normal; }
/* list-style */
.listStyle-none { list-style: none; }
/* margin */
.margin-0 { margin: 0; }
.margin-auto { margin: auto; }
/* margin-bottom */
.marginBottom-auto { margin-bottom: auto; }
.marginBottom-0 { margin-bottom: 0; }
.marginBottom-1em { margin-bottom: 1em; }
.marginBottom-1rem { margin-bottom: 1rem; }
/* margin-left */
.marginLeft-auto { margin-left: auto; }
.marginLeft-0 { margin-left: 0; }
.marginLeft-1em { margin-left: 1em; }
.marginLeft-1rem { margin-left: 1rem; }
/* margin-right */
.marginRight-auto { margin-right: auto; }
.marginRight-0 { margin-right: 0; }
.marginRight-1em { margin-right: 1em; }
.marginRight-1rem { margin-right: 1rem; }
/* margin-top */
.marginTop-auto { margin-top: auto; }
.marginTop-0 { margin-top: 0; }
.marginTop-1em { margin-top: 1em; }
.marginTop-1rem { margin-top: 1rem; }
/* max-height */
.maxHeight-100\% { max-height: 100%; }
/* max-width */
.maxWidth-100\% { max-width: 100%; }
/* min-height */
.minHeight-100\% { min-height: 100%; }
/* min-width */
.minWidth-100\% { min-width: 100%; }
/* opacity */
.opacity-0 { opacity: 0; }
.opacity-0\.1 { opacity: 0.1; }
.opacity-0\.2 { opacity: 0.2; }
.opacity-0\.25 { opacity: 0.25; }
.opacity-0\.3 { opacity: 0.3; }
.opacity-0\.4 { opacity: 0.4; }
.opacity-0\.5 { opacity: 0.5; }
.opacity-0\.6 { opacity: 0.6; }
.opacity-0\.7 { opacity: 0.7; }
.opacity-0\.75 { opacity: 0.75; }
.opacity-0\.8 { opacity: 0.8; }
.opacity-0\.9 { opacity: 0.9; }
.opacity-1 { opacity: 1; }
/* order */
.order--1 { order: -1; }
.order-1 { order: 1; }
.order-2 { order: 2; }
.order-3 { order: 3; }
.order-4 { order: 4; }
.order-5 { order: 5; }
.order-6 { order: 6; }
.order-7 { order: 7; }
.order-8 { order: 8; }
.order-9 { order: 9; }
.order-10 { order: 10; }
.order-11 { order: 11; }
/* overflow */
.overflow-auto { overflow: auto; }
.overflow-hidden { overflow: hidden; }
.overflow-inherit { overflow: inherit; }
.overflow-scroll { overflow: scroll; }
.overflow-visible { overflow: visible; }
/* overflow-x */
.overflowX-auto { overflow-x: auto; }
.overflowX-hidden { overflow-x: hidden; }
.overflowX-inherit { overflow-x: inherit; }
.overflowX-scroll { overflow-x: scroll; }
.overflowX-visible { overflow-x: visible; }
/* overflow-y */
.overflowY-auto { overflow-y: auto; }
.overflowY-hidden { overflow-y: hidden; }
.overflowY-inherit { overflow-y: inherit; }
.overflowY-scroll { overflow-y: scroll; }
.overflowY-visible { overflow-y: visible; }
/* padding */
.padding-0 { padding: 0; }
.padding-1em { padding: 1em; }
.padding-1rem { padding: 1rem; }
/* padding-bottom */
.paddingBottom-0 { padding-bottom: 0; }
.paddingBottom-1em { padding-bottom: 1em; }
.paddingBottom-1rem { padding-bottom: 1rem; }
/* padding-left */
.paddingLeft-0 { padding-left: 0; }
.paddingLeft-1em { padding-left: 1em; }
.paddingLeft-1rem { padding-left: 1rem; }
/* padding-right */
.paddingRight-0 { padding-right: 0; }
.paddingRight-1em { padding-right: 1em; }
.paddingRight-1rem { padding-right: 1rem; }
/* padding-top */
.paddingTop-0 { padding-top: 0; }
.paddingTop-1em { padding-top: 1em; }
.paddingTop-1rem { padding-top: 1rem; }
/* pointer-events */
.pointerEvents-auto { pointer-events: auto; }
.pointerEvents-none { pointer-events: none; }
.pointerEvents-box-none { pointer-events: none; }
.pointerEvents-box-none * { pointer-events: auto;}
.pointerEvents-box-only { pointer-events: auto; }
.pointerEvents-box-only * { pointer-events: none; }
/* position */
.position-absolute { position: absolute; }
.position-fixed { position: fixed; }
.position-relative { position: relative; }
/* right */
.right-0 { right: 0; }
.right-50% { right: 50%; }
.right-100% { right: 100%; }
/* text-align */
.textAlign-center { text-align: center; }
.textAlign-end { text-align: end; }
.textAlign-inherit { text-align: inherit; }
.textAlign-left { text-align: left; }
.textAlign-right { text-align: right; }
.textAlign-justify { text-align: justify; }
.textAlign-start { text-align: start; }
/* text-decoration */
.textDecoration-inherit { text-decoration: inherit; }
.textDecoration-none { text-decoration: none; }
.textDecoration-underline { text-decoration: underline; }
/* text-overflow */
.textOverflow-clip { text-overflow: clip; }
.textOverflow-ellipsis { text-overflow: ellipsis; }
/* text-rendering */
.textRendering-auto { text-rendering: auto; }
.textRendering-geometricPrecision { text-rendering: geometricPrecision; }
.textRendering-inherit { text-rendering: inherit; }
.textRendering-optimizeLegibility { text-rendering: optimizeLegibility; }
.textRendering-optimizeSpeed { text-rendering: optimizeSpeed; }
/* text-transform */
.textTransform-capitalize { text-transform: capitalize; }
.textTransform-lowercase { text-transform: lowercase; }
.textTransform-none { text-transform: none; }
.textTransform-uppercase { text-transform: uppercase; }
/* top */
.top-0 { top: 0; }
.top-50% { top: 50%; }
.top-100% { top: 100%; }
/* unicode-bidi */
.unicodeBidi-bidi-override { unicode-bidi: bidi-override; }
.unicodeBidi-embed { unicode-bidi: embed; }
.unicodeBidi-inherit { unicode-bidi: inherit; }
.unicodeBidi-isolate { unicode-bidi: isolate; }
.unicodeBidi-isolate-override { unicode-bidi: isolate-override; }
.unicodeBidi-normal { unicode-bidi: normal; }
.unicodeBidi-plaintext { unicode-bidi: plaintext; }
/* user-select */
.userSelect-all { user-select: all; }
.userSelect-inherit { user-select: inherit; }
.userSelect-none { user-select: none; }
.userSelect-text { user-select: text; }
/* visibility */
.visibility-collapse { visibility: collapse; }
.visibility-hidden { visibility: hidden; }
.visibility-inherit { visibility: inherit; }
.visibility-visible { visibility: visible; }
/* white-space */
.whiteSpace-normal { white-space: normal; }
.whiteSpace-nowrap { white-space: nowrap; }
.whiteSpace-pre { white-space: pre; }
.whiteSpace-pre-line { white-space: pre-line; }
.whiteSpace-pre-wrap { white-space: pre-wrap; }
/* width */
.width-0 { width: 0; }
.width-10\% { width: 10%; }
.width-12\.5\% { width: 12.5%; }
.width-20\% { width: 20%; }
.width-25\% { width: 25%; }
.width-30\% { width: 30%; }
.width-37\.5\% { width: 37.5%; }
.width-40\% { width: 40%; }
.width-50\% { width: 50%; }
.width-60\% { width: 60%; }
.width-62\.5\% { width: 62.5%; }
.width-70\% { width: 70%; }
.width-75\% { width: 75%; }
.width-80\% { width: 80%; }
.width-87\.5\% { width: 87.5%; }
.width-90\% { width: 90%; }
.width-100\% { width: 100%; }
/* word-wrap */
.wordWrap-break-word { word-wrap: break-word; }
.wordWrap-normal { word-wrap: normal; }
/* z-index */
.zIndex--1 { z-index: -1; }
.zIndex-0 { z-index: 0; }
.zIndex-1 { z-index: 1; }
.zIndex-2 { z-index: 2; }
.zIndex-3 { z-index: 3; }
.zIndex-4 { z-index: 4; }
.zIndex-5 { z-index: 5; }
.zIndex-6 { z-index: 6; }
.zIndex-7 { z-index: 7; }
.zIndex-8 { z-index: 8; }
.zIndex-9 { z-index: 9; }
.zIndex-10 { z-index: 10; }