Compare commits

..

36 Commits

Author SHA1 Message Date
Nicolas Gallagher
ffc6368162 0.0.10 2015-12-13 12:49:48 -08:00
Nicolas Gallagher
501c19fe9b [fix] StyleSheet: shorthand properties
Expand shorthand properties to preserve the one-rule-to-one-style
isolation. Resolve styles like React Native - most specific comes last.
Add support for the hz and vt properties for margin and padding.

Fix #40
2015-12-13 12:48:26 -08:00
Kirill Korolyov
e1da11fa1d [change] transparent default Image backgroundColor 2015-12-07 13:33:16 -08:00
Nicolas Gallagher
b2a4d742a9 [chore] update dependencies 2015-12-01 16:01:51 -08:00
Nicolas Gallagher
8b965fdfa0 [chore] update development dependencies 2015-12-01 15:58:04 -08:00
Nicolas Gallagher
8cfef85934 [fix] Image tests 2015-12-01 15:50:38 -08:00
Nicolas Gallagher
6db24e9358 [fix] Image: default 'resizeMode' is 'cover'
Fix #41
2015-12-01 14:44:17 -08:00
Nicolas Gallagher
13e36bee65 0.0.9 2015-10-24 12:06:37 -07:00
Nicolas Gallagher
93e8e90a1a [fix] README docs 2015-10-24 11:54:31 -07:00
Tom Ashworth
894fd0362d [add] initial ScrollView
Supports the following props: `children`, `contentContainerStyle`,
`horizontal`, `onScroll`, `scrollEnabled`, `scrollEventThrottle`, and
`style`.

Fix #6
2015-10-24 11:35:30 -07:00
Nicolas Gallagher
a1664927ce [change] initial example with media queries 2015-10-21 18:00:10 -07:00
Nicolas Gallagher
ae2abc578a [change] update dependencies 2015-10-19 18:45:33 -07:00
Nicolas Gallagher
5f7b3f5fef [change] move examples 2015-10-19 18:32:38 -07:00
Nicolas Gallagher
75f653818a [change] move tests.webpack.js 2015-10-19 18:31:27 -07:00
Nicolas Gallagher
2c52d41b75 [fix] -ms- CSS vendor prefix 2015-10-19 16:02:41 -07:00
Nicolas Gallagher
83f749d983 [change] docs on MQs and a11y 2015-10-19 10:35:42 -07:00
Nicolas Gallagher
bf5046415c [fix] doc examples 2015-10-19 09:42:30 -07:00
Nicolas Gallagher
885d4586a9 [change] obfuscated selector prefix 2015-10-18 22:15:03 -07:00
Nicolas Gallagher
ea0a778ba3 [change] add more about accessibility to docs 2015-10-18 22:11:20 -07:00
Nicolas Gallagher
0a7eda2505 [fix] remove default link styles 2015-10-18 22:09:27 -07:00
Nicolas Gallagher
35385e7b69 [change] pointerEvents CSS 2015-10-18 22:08:36 -07:00
Nicolas Gallagher
3fd29697c0 [fix] initial 'display' value of 'Text'
Using `inline-block` breaks text overflow truncation in nested `Text`
components.

Fix gh-19
2015-10-18 18:08:39 -07:00
Nicolas Gallagher
a26033be2d 0.0.8 2015-10-18 17:56:05 -07:00
Nicolas Gallagher
fdb4ee4aae [change] StyleSheet docs 2015-10-18 17:49:35 -07:00
Nicolas Gallagher
08300f624f [change] remove 'component' prop; accessibility docs
- infer underlying HTML tag from 'accessibilityRole'
- move accessibility props to 'CoreComponent'
- remove the 'component' prop from exported Components

Fix gh-23
2015-10-18 15:24:59 -07:00
Nicolas Gallagher
7f5a2807e2 [fix] avoid eslint-plugin-react@3.6.0 bug 2015-10-18 11:23:29 -07:00
Nicolas Gallagher
292f045c52 [fix] README examples 2015-10-18 11:02:36 -07:00
Nicolas Gallagher
a19b57df4d 0.0.7 2015-10-18 09:11:38 -07:00
Nicolas Gallagher
1c444569ae [change] React 0.14 support 2015-10-18 08:54:09 -07:00
Nicolas Gallagher
0b8c4b8746 [fix] link to API docs 2015-10-17 18:51:01 -07:00
Nicolas Gallagher
6772233837 [remove] Swipeable 2015-10-17 18:49:21 -07:00
Nicolas Gallagher
cd89f88d96 [add] StyleSheet API
Initial StyleSheet implementation for Web. Converts style object
declarations to "atomic" CSS rules.

Close gh-25
2015-10-17 17:52:01 -07:00
Nicolas Gallagher
b59bdb17b2 [change] Unit test setup and reporter 2015-10-17 17:51:54 -07:00
Nicolas Gallagher
2bfa579fe4 [remove] babel-plugin-typecheck 2015-10-17 17:51:30 -07:00
Nicolas Gallagher
f4c6e33b2f Fix #26 2015-10-01 15:18:28 -07:00
Nicolas Gallagher
39b273f9d8 Moves 2015-09-20 21:00:15 -07:00
76 changed files with 1930 additions and 1695 deletions

View File

@@ -1,12 +1,7 @@
{
"env": {
"development": {
"plugins": [ "typecheck" ]
}
},
"optional": [
"es7.classProperties",
"runtime"
],
"stage": 1
"presets": [
"es2015",
"stage-1",
"react"
]
}

View File

@@ -3,9 +3,6 @@
"parser": "babel-eslint",
// based on https://github.com/feross/standard
"extends": [ "standard", "standard-react" ],
"env": {
"mocha": true
},
"rules": {
// overrides of the standard style
"space-before-function-paren": [ 2, { "anonymous": "always", "named": "never" } ],

View File

@@ -1,6 +1,6 @@
language: node_js
node_js:
- "0.12"
- "4.1"
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

View File

@@ -68,9 +68,9 @@ 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:specs` run and watch the unit tests
* `npm run test` run the linter and unit tests
Please follow this process for submitting a patch:

254
README.md
View File

@@ -2,19 +2,21 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~18.6k-blue.svg)
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.
* [Slack: reactiflux channel #react-native-web][slack-url]
* [Discord: #react-native-web on reactiflux][discord-url]
* [Gitter: react-native-web][gitter-url]
## Table of contents
* [Install](#install)
* [Use](#use)
* [Example](#example)
* [APIs](#apis)
* [Components](#components)
* [Styling](#styling)
* [Accessibility](#accessibility)
* [Contributing](#contributing)
* [Thanks](#thanks)
* [License](#license)
@@ -22,87 +24,26 @@ for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
## Install
```
npm install --save react react-native-web
npm install --save react react-dom 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 {
render() {
return (
<View style={styles.root} />
)
}
}
const Title = ({ children }) => <Text style={styles.title}>{children}</Text>
const styles = {
root: {
borderColor: 'currentcolor'
borderWidth: '5px',
flexDirection: 'row'
height: '5em'
}
}
```
## Components
### [`Image`](docs/components/Image.md)
An accessibile image component with support for image resizing, default image,
and child content.
### [`ListView`](docs/components/ListView.md)
(TODO)
### [`ScrollView`](docs/components/ListView.md)
(TODO)
### [`Swipeable`](docs/components/Swipeable.md)
Touch bindings for swipe gestures.
### [`Text`](docs/components/Text.md)
Displays text as an inline block and supports basic press handling.
### [`TextInput`](docs/components/TextInput.md)
Accessible single- and multi-line text input via a keyboard.
### [`Touchable`](docs/components/Touchable.md)
Touch bindings for press and long press.
### [`View`](docs/components/View.md)
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].
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).
```js
import React, { Image, Text, View } from 'react-native-web'
const Summary = ({ children }) => (
<View style={styles.text}>
<Text style={styles.subtitle}>{children}</Text>
</View>
)
class App extends React.Component {
render() {
@@ -112,20 +53,14 @@ class App extends React.Component {
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>
<Title>React Native Web</Title>
<Summary>Build high quality web apps using React</Summary>
</View>
)
},
})
const styles = {
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
margin: 40
@@ -146,21 +81,139 @@ const styles = {
subtitle: {
fontSize: '1rem'
}
}
})
```
Combine and override style objects:
Pre-render styles on the server:
```js
import baseStyle from './baseStyle'
// server.js
import App from './components/App'
import React, { StyleSheet } from 'react-native-web'
const buttonStyle = {
...baseStyle,
backgroundColor: '#333',
color: '#fff'
}
const html = React.renderToString(<App />);
const css = StyleSheet.renderToString();
const Html = () => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
<style id="react-stylesheet" dangerouslySetInnerHTML={{ __html: css } />
</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'
import ReactDOM from 'react-dom'
const reactRoot = document.getElementById('react-root')
const reactStyleSheet = document.getElementById('react-stylesheet')
ReactDOM.render(<App />, reactRoot)
reactStyleSheet.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)
An accessibile image component with support for image resizing, default image,
and child content.
### [`ListView`](docs/components/ListView.md)
(TODO)
### [`ScrollView`](docs/components/ScrollView.md)
A scrollable view with event throttling.
### [`Text`](docs/components/Text.md)
Displays text as an inline block and supports basic press handling.
### [`TextInput`](docs/components/TextInput.md)
Accessible single- and multi-line text input via a keyboard.
### [`Touchable`](docs/components/Touchable.md)
Touch bindings for press and long press.
### [`View`](docs/components/View.md)
The fundamental UI building block using flexbox for layout.
## Styling
React Native for Web relies on styles being defined in JavaScript. Styling
components can be achieved with inline styles or the use of
[StyleSheet](docs/apis/StyleSheet.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].
### Media Queries, pseudo-classes, and pseudo-elements
Changing styles and/or the render tree in response to device adaptation can be
controlled in JavaScript, e.g.,
[react-media-queries](https://github.com/bloodyowl/react-media-queries),
[media-query-fascade](https://github.com/tanem/media-query-facade), or
[react-responsive](https://github.com/contra/react-responsive). This has the
benefit of co-locating breakpoint-specific DOM and style changes.
Pseudo-classes like `:hover` and `:focus` can be implemented with the `onHover`
and `onFocus` events.
Pseudo-elements are not supported.
## Accessibility
On the Web, assistive technologies derive useful information about the
structure, purpose, and interactivity of apps from their [HTML
elements][html-accessibility-url], attributes, and [ARIA in
HTML][aria-in-html-url].
The most common and best supported accessibility features of the Web are
exposed as the props: `accessible`, `accessibilityLabel`,
`accessibilityLiveRegion`, and `accessibilityRole`.
React Native for Web does not provide a way to directly control the rendered
HTML element. The `accessibilityRole` prop is used to infer an [analogous HTML
element][html-aria-url] to use in addition, where possible. While this may
contradict some ARIA recommendations, it also helps avoid certain HTML5
conformance errors and accessibility anti-patterns (e.g., giving a `heading`
role to a `button` element).
For example:
* `<View accessibilityRole='article' />` => `<article role='article' />`.
* `<View accessibilityRole='banner' />` => `<header role='banner' />`.
* `<View accessibilityRole='button' />` => `<button type='button' role='button' />`.
* `<Text accessibilityRole='link' href='/' />` => `<a role='link' href='/' />`.
* `<View accessibilityRole='main' />` => `<main role='main' />`.
See the component documentation for more details.
## Contributing
Please read the [contribution guidelines][contributing-url]. Contributions are
@@ -169,24 +222,25 @@ welcome!
## Thanks
Thanks to current and past members of the React and React Native teams (in
particular Vjeux and Pete Hunt), and Tobias Koppers for Webpack and CSS loader.
particular Vjeux and Pete Hunt).
Thanks to [react-swipeable](https://github.com/dogfessional/react-swipeable/)
for the current implementation of `Swipeable`, and to
[react-tappable](https://github.com/JedWatson/react-tappable) for backing the
current implementation of `Touchable`.
Thanks to [react-tappable](https://github.com/JedWatson/react-tappable) for
backing the current implementation of `Touchable`.
## License
Copyright (c) 2015 Nicolas Gallagher. Released under the [MIT
license](http://www.opensource.org/licenses/mit-license.php).
[aria-in-html-url]: https://w3c.github.io/aria-in-html/
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/CONTRIBUTING.md
[discord-url]: http://join.reactiflux.com
[flexbox-guide-url]: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
[gitter-url]: https://gitter.im/necolas/react-native-web
[html-accessibility-url]: http://www.html5accessibility.com/
[html-aria-url]: http://www.w3.org/TR/html-aria/
[npm-image]: https://badge.fury.io/js/react-native-web.svg
[npm-url]: https://npmjs.org/package/react-native-web
[react-native-url]: https://facebook.github.io/react-native/
[slack-url]: https://reactiflux.slack.com/messages/react-native-web/
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
[travis-url]: https://travis-ci.org/necolas/react-native-web

View File

@@ -4,6 +4,8 @@ var ROOT = path.join(__dirname, '..')
module.exports = {
DIST_DIRECTORY: path.join(ROOT, 'dist'),
EXAMPLES_DIRECTORY: path.join(ROOT, 'examples'),
SRC_DIRECTORY: path.join(ROOT, 'src'),
ROOT_DIRECTORY: ROOT
ROOT_DIRECTORY: ROOT,
TEST_ENTRY: path.join(ROOT, 'tests.webpack.js')
}

View File

@@ -1,6 +1,5 @@
var assign = require('object-assign')
var constants = require('./constants')
var webpackConfig = require('./webpack.config.base')
var webpack = require('webpack')
module.exports = function (config) {
config.set({
@@ -9,38 +8,48 @@ module.exports = function (config) {
browserNoActivityTimeout: 60000,
client: {
captureConsole: true,
mocha: {
ui: 'tdd'
},
mocha: { ui: 'tdd' },
useIframe: true
},
files: [
'src/specs.context.js'
],
frameworks: [
'mocha'
constants.TEST_ENTRY
],
frameworks: [ 'mocha' ],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-sourcemap-loader',
'karma-spec-reporter',
'karma-webpack'
],
preprocessors: {
'src/specs.context.js': [ 'webpack', 'sourcemap' ]
[constants.TEST_ENTRY]: [ 'webpack', 'sourcemap' ]
},
reporters: [ 'dots' ],
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
singleRun: true,
webpack: assign({}, webpackConfig, { devtool: 'inline' }),
webpackMiddleware: {
stats: {
assetsSort: 'name',
colors: true,
children: false,
chunks: false,
modules: false
}
webpack: {
devtool: 'inline-source-map',
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
query: { cacheDirectory: true }
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('test')
}
})
]
},
webpackServer: {
noInfo: true
}
})
}

View File

@@ -1,11 +1,13 @@
var webpack = require('webpack')
var DedupePlugin = webpack.optimize.DedupePlugin
var EnvironmentPlugin = webpack.EnvironmentPlugin
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
var plugins = [
new DedupePlugin(),
new EnvironmentPlugin('NODE_ENV'),
new OccurenceOrderPlugin()
]
@@ -25,14 +27,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/,

View File

@@ -5,13 +5,13 @@ var path = require('path')
module.exports = assign({}, base, {
devServer: {
contentBase: constants.SRC_DIRECTORY
contentBase: constants.EXAMPLES_DIRECTORY
},
entry: {
example: path.join(constants.SRC_DIRECTORY, 'example')
example: path.join(constants.EXAMPLES_DIRECTORY, 'index')
},
output: {
filename: 'example.js',
filename: 'examples.js',
path: constants.DIST_DIRECTORY
}
})

View File

@@ -7,7 +7,8 @@ module.exports = assign({}, base, {
main: constants.SRC_DIRECTORY
},
externals: [{
react: true
'react': true,
'react-dom': true
}],
output: {
filename: 'react-native-web.js',

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

@@ -0,0 +1,165 @@
# StyleSheet
React Native for Web will automatically vendor-prefix styles applied to the
library's 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.
Create a new StyleSheet:
```js
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})
**destroy**()
Clears all style information.
**renderToString**()
Renders a CSS Style Sheet.
## About
### Strategy
React Native for Web uses a `style`-to-`className` conversion strategy that is
designed to avoid issues arising from the [7 deadly sins of
CSS](https://speakerdeck.com/vjeux/react-css-in-js):
1. Global namespace
2. Dependency hell
3. Dead code elimination
4. Code minification
5. Sharing constants
6. Non-deterministic resolution
7. Breaking isolation
The strategy also minimizes the amount of generated CSS, making it more viable
to inline the style sheet when pre-rendering pages on the server. There is one
unique selector per unique style _declaration_.
```js
// definition
{
heading: {
color: 'gray',
fontSize: '2rem'
},
text: {
color: 'gray',
fontSize: '1.25rem'
}
}
// css
//
// .a { color: gray; }
// .b { font-size: 2rem; }
// .c { font-size: 1.25rem; }
```
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
style sheet 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.)
### Reset
React Native for Web includes a very small CSS reset taken from
[normalize.css](https://necolas.github.io/normalize.css/). It removes unwanted
User Agent styles from (pseudo-)elements beyond the reach of React (e.g.,
`html`, `body`) or inline styles (e.g., `::-moz-focus-inner`).
```css
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;
}
```

View File

@@ -64,7 +64,7 @@ Defaults:
```js
{
alignSelf: 'flex-start',
backgroundColor: 'lightGray'
backgroundColor: 'transparent'
}
```
@@ -76,11 +76,11 @@ Used to locate a view in end-to-end tests.
```js
import placeholderAvatar from './placeholderAvatar.png'
import React, { Image } from 'react-native-web'
import React, { Image, StyleSheet } from 'react-native-web'
const { Component, PropTypes } = React;
class Avatar extends Component {
export default class Avatar extends Component {
constructor(props, context) {
super(props, context)
this.state = { loading: true }
@@ -118,7 +118,7 @@ class Avatar extends Component {
}
}
const styles = {
const styles = StyleSheet.create({
base: {
borderColor: 'white',
borderRadius: '5px',
@@ -139,5 +139,5 @@ const styles = {
height: '64px',
width: '64px'
}
}
})
```

View File

@@ -1,6 +1,7 @@
# ScrollView
TODO
Scrollable `View` for use with bounded height, either by setting the height of
the view directly (discouraged) or by bounding the height of ancestor views.
## Props
@@ -11,27 +12,29 @@ Child content.
**contentContainerStyle**: style
These styles will be applied to the scroll view content container which wraps
all of the child views. Example:
all of the child views.
**horizontal**: bool = false
When true, the scroll view's children are arranged horizontally in a row instead of vertically in a column. Default: `false`.
When true, the scroll view's children are arranged horizontally in a row
instead of vertically in a column.
**onScroll**: function
Fires at most once per frame during scrolling. The frequency of the events can be contolled using the `scrollEventThrottle` prop.
Fires at most once per frame during scrolling. The frequency of the events can
be contolled using the `scrollEventThrottle` prop.
**scrollEnabled**: bool
**scrollEnabled**: bool = true
When false, the content does not scroll. Default: `true`.
When false, the content does not scroll.
**scrollEventThrottle**: number
**scrollEventThrottle**: number = 0
This controls how often the scroll event will be fired while scrolling (in
events per seconds). A higher number yields better accuracy for code that is
tracking the scroll position, but can lead to scroll performance problems.
Default: `0` (the scroll event will be sent only once each time the view is
scrolled.)
tracking the scroll position, but can lead to scroll performance problems. The
default value is `0`, which means the scroll event will be sent only once each
time the view is scrolled.
**style**: style
@@ -40,20 +43,42 @@ scrolled.)
## Examples
```js
import React, { ScrollView } from 'react-native-web'
import React, { ScrollView, StyleSheet } from 'react-native-web'
const { Component, PropTypes } = React;
import Item from './Item'
class Example extends Component {
static propTypes = {
export default class App extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
items: Array.from({ length: 20 }).map((_, i) => ({ id: i }))
}
}
static defaultProps = {
onScroll(e) {
console.log(e)
}
render() {
return (
<ScrollView
children={this.state.items.map((item) => <Item {...item} />)}
contentContainerStyle={styles.container}
horizontal
onScroll={(e) => this.onScroll(e)}
scrollEventThrottle={60}
style={styles.root}
/>
)
}
}
const styles = StyleSheet.create({
root: {
borderWidth: '1px'
},
container: {
padding: '10px'
}
})
```

View File

@@ -1,95 +0,0 @@
# Swipeable
Unique to web.
## Props
**delta**: number = 10
Number of pixels that must be swiped before events are dispatched.
**flickThreshold**: number = 0.6
The velocity threshold at which a swipe is considered a flick.
**onSwiped**: function
(SyntheticTouchEvent, deltaX, deltaY, isFlick) => swipeHandler
Called once a swipe has ended.
**onSwipedDown**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-down has ended.
**onSwipedLeft**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-left has ended.
**onSwipedUp**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-up has ended.
**onSwipedRight**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called once a swipe-right has ended.
**onSwipingDown**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-down is in progress.
**onSwipingLeft**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-left is in progress.
**onSwipingRight**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-right is in progress.
**onSwipingUp**: function
(SyntheticTouchEvent, delta, isFlick) => swipeHandler
Called while a swipe-up is in progress.
## Examples
```js
import React, { Swipeable } from 'react-native-web'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
_onSwiped(event, x, y, isFlick) {
}
render() {
return (
<Swipeable
onSwiped={this._onSwiped.bind(this)}
/>
)
}
}
```

View File

@@ -23,6 +23,17 @@ NOTE: `Text` will transfer all other props to the rendered HTML element.
Defines the text available to assistive technologies upon interaction with the
element. (This is implemented using `aria-label`.)
(web) **accessibilityRole**: oneOf(roles)
Allows assistive technologies to present and support interaction with the view
in a manner that is consistent with user expectations for similar views of that
type. For example, marking a touchable view with an `accessibilityRole` of
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
Note: Avoid changing `accessibilityRole` values over time or after user
actions. Generally, accessibility APIs do not provide a means of notifying
assistive technologies of a `role` value change.
(web) **accessible**: bool = true
When `false`, the text is hidden from assistive technologies. (This is
@@ -32,10 +43,6 @@ implemented using `aria-hidden`.)
Child content.
(web) **component**: function | string = 'span'
Backing component.
**numberOfLines**: number
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
@@ -70,7 +77,7 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Text } from 'react-native-web'
import React, { StyleSheet, Text } from 'react-native-web'
const { Component, PropTypes } = React
@@ -104,7 +111,7 @@ class PrettyText extends Component {
}
}
const localStyle = {
const localStyle = StyleSheet.create({
color: {
white: { color: 'white' },
gray: { color: 'gray' },
@@ -120,5 +127,5 @@ const localStyle = {
normal: { fontWeight: '400' },
bold: { fontWeight: '700' }
}
}
})
```

View File

@@ -159,11 +159,9 @@ user edits to the value set `editable={false}`.
## Examples
```js
import React, { TextInput } from 'react-native-web'
import React, { StyleSheet, TextInput } from 'react-native-web'
const { Component } = React
class AppTextInput extends Component {
export default class AppTextInput extends React.Component {
constructor(props, context) {
super(props, context)
this.state = { isFocused: false }
@@ -182,16 +180,16 @@ class AppTextInput extends Component {
numberOfLines={2}
onFocus={this._onFocus.bind(this)}
placeholder={`What's happening?`}
style={
style={{
...styles.default
...(this.state.isFocused && styles.focused)
}
}}
/>
);
}
}
const styles = {
const styles = StyleSheet.create({
default: {
borderColor: 'gray',
borderWidth: '0 0 2px 0'
@@ -199,5 +197,5 @@ const styles = {
focused: {
borderColor: 'blue'
}
}
})
```

View File

@@ -54,10 +54,6 @@ assistive technologies of a `role` value change.
When `false`, the view is hidden from assistive technologies. (This is
implemented using `aria-hidden`.)
(web) **component**: function | string = 'div'
The React Component for this view.
**onLayout**: function
(TODO)
@@ -159,7 +155,7 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { View } from 'react-native-web'
import React, { StyleSheet, View } from 'react-native-web'
const { Component, PropTypes } = React
@@ -177,14 +173,14 @@ class Example extends Component {
}
}
const styles = {
const styles = StyleSheet.create({
row: {
flexDirection: 'row'
},
cell: {
flexGrow: 1
}
}
})
export default Example
```

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

@@ -1,68 +1,42 @@
import React, { Image, Swipeable, Text, TextInput, Touchable, View } from '.'
import GridView from './GridView'
import Heading from './Heading'
import MediaQueryWidget from './MediaQueryWidget'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, Touchable, View } from '../../src'
const { Component, PropTypes } = React
class Heading extends Component {
static propTypes = {
children: Text.propTypes.children,
level: PropTypes.oneOf(['1', '2', '3']),
size: PropTypes.oneOf(['xlarge', 'large', 'normal'])
}
static defaultProps = {
level: '1',
size: 'normal'
}
render() {
const { children, level, size } = this.props
return (
<Text
children={children}
component={`h${level}`}
style={headingStyles.size[size]}
/>
)
}
}
const headingStyles = {
size: {
xlarge: {
fontSize: '2rem',
marginBottom: '1em'
},
large: {
fontSize: '1.5rem',
marginBottom: '1em',
marginTop: '1em'
},
normal: {
fontSize: '1.25rem',
marginBottom: '0.5em',
marginTop: '0.5em'
}
}
}
class Example extends Component {
export default class App extends React.Component {
static propTypes = {
mediaQuery: React.PropTypes.object,
style: View.propTypes.style
}
constructor(...args) {
super(...args)
this.state = {
scrollEnabled: true
}
}
render() {
const { mediaQuery } = this.props
const rootStyles = {
...(styles.root.common),
...(mediaQuery.small.matches && styles.root.mqSmall),
...(mediaQuery.large.matches && styles.root.mqLarge)
}
return (
<View accessibilityRole='main' style={styles.root}>
<Heading level='1' size='xlarge'>React Native Web</Heading>
<View accessibilityRole='main' style={rootStyles}>
<Heading size='xlarge'>React Native for Web</Heading>
<Text>React Native Web takes the core components from <Text
component='a' href='https://facebook.github.io/react-native/'>React
accessibilityRole='link' href='https://facebook.github.io/react-native/'>React
Native</Text> and brings them to the web. These components provide
simple building blocks touch and swipe handling, flexbox layout,
simple building blocks touch handling, flexbox layout,
scroll views from which more complex components and apps can be
constructed.</Text>
<Heading level='2' size='large'>Image</Heading>
<MediaQueryWidget mediaQuery={mediaQuery} />
<Heading size='large'>Image</Heading>
<Image
accessibilityLabel='accessible image'
children={<Text>Inner content</Text>}
@@ -85,22 +59,7 @@ class Example extends Component {
testID='Example.image'
/>
<Heading level='2' size='large'>Swipeable</Heading>
<Swipeable
onSwiped={(e) => { console.log('Swipeable.onSwiped', e) }}
testID={'Example.swipeable'}
>
<View
style={{
backgroundColor: 'black',
alignSelf: 'center',
width: '200px',
height: '200px'
}}
/>
</Swipeable>
<Heading level='2' size='large'>Text</Heading>
<Heading size='large'>Text</Heading>
<Text
onPress={(e) => { console.log('Text.onPress', e) }}
testID={'Example.text'}
@@ -125,7 +84,7 @@ class Example extends Component {
hendrerit consequat.
</Text>
<Heading level='2' size='large'>TextInput</Heading>
<Heading size='large'>TextInput</Heading>
<TextInput
keyboardType='default'
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
@@ -139,7 +98,7 @@ class Example extends Component {
<TextInput keyboardType='email-address' />
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput keyboardType='url' />
<TextInput keyboardType='url' selectTextOnFocus />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
@@ -147,7 +106,7 @@ class Example extends Component {
numberOfLines={5}
/>
<Heading level='2' size='large'>Touchable</Heading>
<Heading size='large'>Touchable</Heading>
<Touchable
accessibilityLabel={'Touchable element'}
activeHighlight='lightblue'
@@ -162,8 +121,8 @@ class Example extends Component {
</View>
</Touchable>
<Heading level='2' size='large'>View</Heading>
<Heading level='3'>Default layout</Heading>
<Heading size='large'>View</Heading>
<Heading>Default layout</Heading>
<View>
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
return (
@@ -174,7 +133,7 @@ class Example extends Component {
})}
</View>
<Heading level='3'>Row layout</Heading>
<Heading>Row layout</Heading>
<View style={styles.row}>
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
return (
@@ -185,13 +144,13 @@ class Example extends Component {
})}
</View>
<Heading level='3'>pointerEvents</Heading>
<View style={styles.row}>
<Heading>pointerEvents</Heading>
<GridView alley='10px'>
{['box-none', 'box-only', 'none'].map((value, i) => {
return (
<View
accessibilityRole='link'
children={value}
component='a'
href='https://google.com'
key={i}
pointerEvents={value}
@@ -199,16 +158,70 @@ class Example extends Component {
/>
)
})}
</GridView>
<Heading size='large'>ScrollView</Heading>
<label>
<input
checked={this.state.scrollEnabled}
onChange={() => this.setState({
scrollEnabled: !this.state.scrollEnabled
})}
type='checkbox'
/> Enable scroll
</label>
<Heading>Default layout</Heading>
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEnabled={this.state.scrollEnabled}
scrollEventThrottle={1} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={styles.box}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
<Heading>Horizontal layout</Heading>
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
horizontal
onScroll={e => console.log('ScrollView.onScroll', e)}
scrollEnabled={this.state.scrollEnabled}
scrollEventThrottle={1} // 1 event per second
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={{...styles.box, ...styles.horizontalBox}}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
</View>
)
}
}
const styles = {
const styles = StyleSheet.create({
root: {
maxWidth: '600px',
margin: '0 auto'
common: {
marginVertical: 0,
marginHorizontal: 'auto'
},
mqSmall: {
maxWidth: '400px'
},
mqLarge: {
maxWidth: '600px'
}
},
row: {
flexDirection: 'row',
@@ -218,7 +231,10 @@ const styles = {
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
borderWidth: '1px'
borderWidth: 1
},
horizontalBox: {
width: '50px'
},
boxFull: {
width: '100%'
@@ -226,7 +242,7 @@ const styles = {
pointerEventsBox: {
alignItems: 'center',
borderWidth: '1px',
flexGrow: '1',
flexGrow: 1,
height: '100px',
justifyContent: 'center'
},
@@ -235,7 +251,14 @@ const styles = {
borderWidth: 1,
height: '200px',
justifyContent: 'center'
},
scrollViewContainer: {
height: '200px'
},
scrollViewStyle: {
borderWidth: '1px'
},
scrollViewContentContainerStyle: {
padding: '10px'
}
}
React.render(<Example />, document.getElementById('react-root'))
})

View File

@@ -0,0 +1,67 @@
import React, { StyleSheet, View } from '../../src'
const { Component, PropTypes } = React
const styles = StyleSheet.create({
root: {
overflow: 'hidden'
},
contentContainer: {
flexDirection: 'row',
flexGrow: 1
},
// distribute all space (rather than extra space)
column: {
flexBasis: '0%'
}
})
export default class GridView extends Component {
static propTypes = {
alley: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.arrayOf(PropTypes.element)
]),
gutter: PropTypes.string,
style: PropTypes.object
}
static defaultProps = {
alley: '0',
gutter: '0'
}
render() {
const { alley, children, gutter, style, ...other } = this.props
const rootStyle = {
...style,
...styles.root
}
const contentContainerStyle = {
...styles.contentContainer,
margin: `0 calc(-0.5 * ${alley})`,
padding: `0 ${gutter}`
}
const newChildren = React.Children.map(children, (child) => {
return child && React.cloneElement(child, {
style: {
...child.props.style,
...styles.column,
margin: `0 calc(0.5 * ${alley})`
}
})
})
return (
<View className='GridView' {...other} style={rootStyle}>
<View style={contentContainerStyle}>
{newChildren}
</View>
</View>
)
}
}

View File

@@ -0,0 +1,31 @@
import React from 'react'
import { StyleSheet, Text } from '../../src'
const headingStyles = StyleSheet.create({
size: {
xlarge: {
fontSize: '2rem',
marginBottom: '1em'
},
large: {
fontSize: '1.5rem',
marginBottom: '1em',
marginTop: '1em'
},
normal: {
fontSize: '1.25rem',
marginBottom: '0.5em',
marginTop: '0.5em'
}
}
})
const Heading = ({ children, size = 'normal' }) => (
<Text
accessibilityRole='heading'
children={children}
style={headingStyles.size[size]}
/>
)
export default Heading

View File

@@ -0,0 +1,36 @@
import React, { StyleSheet, Text, View } from '../../src'
const styles = StyleSheet.create({
root: {
alignItems: 'center',
borderWidth: 1,
marginVertical: 10,
padding: 10,
textAlign: 'center'
},
heading: {
fontWeight: 'bold',
padding: 5
}
})
const MediaQueryWidget = ({ mediaQuery = {} }) => {
const active = Object.keys(mediaQuery).reduce((active, alias) => {
if (mediaQuery[alias].matches) {
active = {
alias,
mql: mediaQuery[alias]
}
}
return active
}, {})
return (
<View style={styles.root}>
<Text style={styles.heading}>Active Media Query</Text>
<Text>{`"${active.alias}"`} {active.mql.media}</Text>
</View>
)
}
export default MediaQueryWidget

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>
<script src="/examples.js"></script>

22
examples/index.js Normal file
View File

@@ -0,0 +1,22 @@
import { MediaProvider, matchMedia } from 'react-media-queries'
import App from './components/App'
import createGetter from 'react-media-queries/lib/createMediaQueryGetter'
import createListener from 'react-media-queries/lib/createMediaQueryListener'
import React, { StyleSheet } from '../src'
import ReactDOM from 'react-dom'
const mediaQueries = {
small: '(min-width: 300px)',
medium: '(min-width: 400px)',
large: '(min-width: 500px)'
}
const ResponsiveApp = matchMedia()(App)
ReactDOM.render(
<MediaProvider getMedia={createGetter(mediaQueries)} listener={createListener(mediaQueries)}>
<ResponsiveApp />
</MediaProvider>,
document.getElementById('react-root')
)
document.getElementById('react-stylesheet').textContent = StyleSheet.renderToString()

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.6",
"version": "0.0.10",
"description": "React Native for Web",
"main": "dist/react-native-web.js",
"files": [
@@ -8,50 +8,63 @@
],
"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",
"lint": "eslint config src",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config examples src",
"prepublish": "NODE_ENV=publish npm run build",
"test:specs": "NODE_ENV=test karma start config/karma.config.js",
"test:specs:watch": "npm run test:specs -- --no-single-run",
"test": "npm run test:specs && npm run lint"
"test": "npm run lint && npm run test:unit",
"test:unit": "karma start config/karma.config.js",
"test:watch": "npm run test:unit -- --no-single-run"
},
"dependencies": {
"react": ">=0.13.3",
"react-swipeable": "^3.0.2",
"react-tappable": "^0.6.0",
"react-textarea-autosize": "^2.5.2"
"inline-style-prefixer": "^0.5.3",
"lodash.debounce": "^3.1.1",
"react-tappable": "^0.7.1",
"react-textarea-autosize": "^3.1.0"
},
"devDependencies": {
"autoprefixer-loader": "^3.1.0",
"babel-core": "^5.8.23",
"babel-eslint": "^4.1.1",
"babel-loader": "^5.3.2",
"babel-plugin-typecheck": "^1.2.0",
"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-chrome-launcher": "^0.2.0",
"karma-firefox-launcher": "^0.1.6",
"karma-mocha": "^0.2.0",
"karma-sourcemap-loader": "^0.3.5",
"babel-core": "^6.2.4",
"babel-eslint": "^4.1.6",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.2.4",
"babel-preset-react": "^6.2.4",
"babel-preset-stage-1": "^6.2.4",
"babel-runtime": "^6.2.4",
"eslint": "^1.10.3",
"eslint-config-standard": "^4.4.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-plugin-react": "^3.11.2",
"eslint-plugin-standard": "^1.3.1",
"karma": "^0.13.15",
"karma-browserstack-launcher": "^0.1.7",
"karma-chrome-launcher": "^0.2.2",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "^0.2.1",
"karma-sourcemap-loader": "^0.3.6",
"karma-spec-reporter": "0.0.23",
"karma-webpack": "^1.7.0",
"mocha": "^2.3.0",
"node-libs-browser": "^0.5.2",
"mocha": "^2.3.4",
"node-libs-browser": "^0.5.3",
"object-assign": "^4.0.1",
"style-loader": "^0.12.3",
"webpack": "^1.12.1",
"webpack-dev-server": "^1.10.1"
"react": "^0.14.3",
"react-addons-test-utils": "^0.14.3",
"react-dom": "^0.14.3",
"react-media-queries": "^2.0.1",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0"
},
"author": "Nicolas Gallagher",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/necolas/react-native-web.git"
}
},
"tags": [
"react"
],
"keywords": [
"react",
"react-component",
"react-native",
"web"
]
}

View File

@@ -0,0 +1,62 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import CoreComponent from '../'
suite('components/CoreComponent', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const dom = utils.renderToDOM(<CoreComponent accessibilityLabel={accessibilityLabel} />)
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const dom = utils.renderToDOM(<CoreComponent accessibilityLiveRegion={accessibilityLiveRegion} />)
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'banner'
let dom = utils.renderToDOM(<CoreComponent accessibilityRole={accessibilityRole} />)
assert.equal(dom.getAttribute('role'), accessibilityRole)
assert.equal((dom.tagName).toLowerCase(), 'header')
const button = 'button'
dom = utils.renderToDOM(<CoreComponent accessibilityRole={button} />)
assert.equal(dom.getAttribute('type'), button)
assert.equal((dom.tagName).toLowerCase(), button)
})
test('prop "accessible"', () => {
// accessible (implicit)
let dom = utils.renderToDOM(<CoreComponent />)
assert.equal(dom.getAttribute('aria-hidden'), null)
// accessible (explicit)
dom = utils.renderToDOM(<CoreComponent accessible />)
assert.equal(dom.getAttribute('aria-hidden'), null)
// not accessible
dom = utils.renderToDOM(<CoreComponent accessible={false} />)
assert.equal(dom.getAttribute('aria-hidden'), 'true')
})
test('prop "component"', () => {
const component = 'main'
const dom = utils.renderToDOM(<CoreComponent component={component} />)
const tagName = (dom.tagName).toLowerCase()
assert.equal(tagName, component)
})
test('prop "testID"', () => {
// no testID
let dom = utils.renderToDOM(<CoreComponent />)
assert.equal(dom.getAttribute('data-testid'), null)
// with testID
const testID = 'Example.testID'
dom = utils.renderToDOM(<CoreComponent testID={testID} />)
assert.equal(dom.getAttribute('data-testid'), testID)
})
})

View File

@@ -1,39 +1,68 @@
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'
const roleComponents = {
article: 'article',
banner: 'header',
button: 'button',
complementary: 'aside',
contentinfo: 'footer',
form: 'form',
heading: 'h1',
link: 'a',
main: 'main',
navigation: 'nav',
region: 'section'
}
class CoreComponent extends React.Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf(['assertive', 'off', 'polite']),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
className: PropTypes.string,
component: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string
]),
style: PropTypes.object,
testID: PropTypes.string
testID: PropTypes.string,
type: PropTypes.string
}
static stylePropTypes = stylePropTypes;
static defaultProps = {
className: '',
accessible: true,
component: 'div'
}
static stylePropTypes = StylePropTypes;
render() {
const {
className,
component: Component,
style,
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible,
component,
testID,
type,
...other
} = this.props
const Component = roleComponents[accessibilityRole] || component
return (
<Component
{...other}
{...restyle({ className, style })}
{...StyleSheet.resolve(other)}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
aria-live={accessibilityLiveRegion}
data-testid={testID}
role={accessibilityRole}
type={accessibilityRole === 'button' ? 'button' : type}
/>
)
}

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,73 +1,77 @@
import { assertProps, render, renderToDOM } from '../../modules/specHelpers'
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import React from 'react'
import Image from '.'
import Image from '../'
suite('Image', () => {
suite('components/Image', () => {
test('default accessibility', () => {
const dom = renderToDOM(<Image />)
const dom = utils.renderToDOM(<Image />)
assert.equal(dom.getAttribute('role'), 'img')
})
test('prop "accessibilityLabel"', () => {
assertProps.accessibilityLabel(Image)
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Image accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessible"', () => {
assertProps.accessible(Image)
const accessible = false
const result = utils.shallowRender(<Image accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test.skip('prop "children"', () => { })
test('prop "children"')
test('prop "defaultSource"', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' }
const dom = renderToDOM(<Image defaultSource={defaultSource} />)
const dom = utils.renderToDOM(<Image defaultSource={defaultSource} />)
const backgroundImage = dom.style.backgroundImage
assert(backgroundImage.indexOf(defaultSource.uri) > -1)
})
test('prop "onError"', function (done) {
this.timeout(5000)
utils.render(<Image
onError={onError}
source={{ uri: 'https://google.com/favicon.icox' }}
/>)
function onError(e) {
assert.equal(e.nativeEvent.type, 'error')
done()
}
render(<Image
onError={onError}
source={{ uri: 'https://google.com/favicon.icox' }}
/>)
})
test('prop "onLoad"', function (done) {
this.timeout(5000)
utils.render(<Image
onLoad={onLoad}
source={{ uri: 'https://google.com/favicon.ico' }}
/>)
function onLoad(e) {
assert.equal(e.nativeEvent.type, 'load')
done()
}
render(<Image
onLoad={onLoad}
source={{ uri: 'https://google.com/favicon.ico' }}
/>)
})
test.skip('prop "onLoadEnd"', () => { })
test('prop "onLoadEnd"')
test.skip('prop "onLoadStart"', () => { })
test('prop "onLoadStart"')
test.skip('prop "resizeMode"', () => { })
test('prop "resizeMode"')
test.skip('prop "source"', () => { })
test('prop "source"')
test('prop "style"', () => {
assertProps.style(Image)
utils.assertProps.style(Image)
})
test('prop "testID"', () => {
assertProps.testID(Image)
const testID = 'testID'
const result = utils.shallowRender(<Image testID={testID} />)
assert.equal(result.props.testID, testID)
})
})

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,13 +14,13 @@ const STATUS_IDLE = 'IDLE'
const imageStyleKeys = Object.keys(ImageStylePropTypes)
const styles = {
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
backgroundColor: 'lightgray',
backgroundColor: 'transparent',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: '100% 100%'
backgroundSize: 'cover'
},
img: {
borderWidth: 0,
@@ -49,23 +50,22 @@ const styles = {
backgroundSize: '100% 100%'
}
}
}
})
class Image extends React.Component {
constructor(props, context) {
super(props, context)
const { uri } = props.source
// state
this.state = { status: props.source.uri ? STATUS_PENDING : STATUS_IDLE }
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
// autobinding
this._onError = this._onError.bind(this)
this._onLoad = this._onLoad.bind(this)
}
static propTypes = {
accessibilityLabel: PropTypes.string,
accessible: PropTypes.bool,
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessible: CoreComponent.propTypes.accessible,
children: PropTypes.any,
defaultSource: PropTypes.object,
onError: PropTypes.func,
@@ -83,7 +83,7 @@ class Image extends React.Component {
static defaultProps = {
accessible: true,
defaultSource: {},
resizeMode: 'stretch',
resizeMode: 'cover',
source: {},
style: styles.initial
}
@@ -101,8 +101,8 @@ class Image extends React.Component {
_destroyImageLoader() {
if (this.image) {
this.image.onload = null
this.image.onerror = null
this.image.onload = null
this.image = null
}
}
@@ -123,8 +123,8 @@ class Image extends React.Component {
this._destroyImageLoader()
this.setState({ status: STATUS_LOADED })
this._onLoadEnd()
if (onLoad) onLoad(event)
this._onLoadEnd()
}
_onLoadEnd() {
@@ -193,12 +193,11 @@ class Image extends React.Component {
accessibilityLabel={accessibilityLabel}
accessibilityRole='img'
accessible={accessible}
component='div'
style={{
...(styles.initial),
...styles.initial,
...resolvedStyle,
...(backgroundImage && { backgroundImage }),
...(styles.resizeMode[resizeMode])
...styles.resizeMode[resizeMode]
}}
testID={testID}
>

View File

@@ -0,0 +1 @@
/* eslint-env mocha */

View File

@@ -1,18 +0,0 @@
/*
import assert from 'assert'
import React from 'react/addons'
import ListView from '.'
const ReactTestUtils = React.addons.TestUtils
function shallowRender(component, context = {}) {
const shallowRenderer = React.addons.TestUtils.createRenderer()
shallowRenderer.render(component, context)
return shallowRenderer.getRenderOutput()
}
suite.skip('ListView', () => {
test('prop "children"', () => {})
})
*/

View File

@@ -0,0 +1,4 @@
import View from '../View'
export default {
...(View.stylePropTypes)
}

View File

@@ -0,0 +1,11 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import ScrollView from '../'
suite('components/ScrollView', () => {
test('prop "style"', () => {
utils.assertProps.style(ScrollView)
})
})

View File

@@ -1,18 +1,138 @@
import { pickProps } from '../../modules/filterObjectProps'
import debounce from 'lodash.debounce'
import React, { PropTypes } from 'react'
import ScrollViewStylePropTypes from './ScrollViewStylePropTypes'
import StyleSheet from '../../modules/StyleSheet'
import View from '../View'
const scrollViewStyleKeys = Object.keys(ScrollViewStylePropTypes)
const styles = StyleSheet.create({
initial: {
flexGrow: 1,
flexShrink: 1,
overflow: 'scroll'
},
initialContentContainer: {
flexGrow: 1,
flexShrink: 1
},
row: {
flexDirection: 'row'
}
})
class ScrollView extends React.Component {
static propTypes = {
children: PropTypes.any
children: PropTypes.any,
contentContainerStyle: PropTypes.shape(ScrollViewStylePropTypes),
horizontal: PropTypes.bool,
onScroll: PropTypes.func,
scrollEnabled: PropTypes.bool,
scrollEventThrottle: PropTypes.number,
style: PropTypes.shape(ScrollViewStylePropTypes)
}
static defaultProps = {
className: ''
contentContainerStyle: styles.initialContentContainer,
horizontal: false,
scrollEnabled: true,
scrollEventThrottle: 0,
style: styles.initial
}
constructor(...args) {
super(...args)
this._debouncedOnScrollEnd = debounce(this._onScrollEnd, 100)
this.state = {
isScrolling: false
}
}
_onScroll(e) {
const { scrollEventThrottle } = this.props
const { isScrolling, scrollLastTick } = this.state
// A scroll happened, so the scroll bumps the debounce.
this._debouncedOnScrollEnd(e)
if (isScrolling) {
// Scroll last tick may have changed, check if we need to notify
if (this._shouldEmitScrollEvent(scrollLastTick, scrollEventThrottle)) {
this._onScrollTick(e)
}
} else {
// Weren't scrolling, so we must have just started
this._onScrollStart(e)
}
}
_onScrollStart() {
this.setState({
isScrolling: true,
scrollLastTick: Date.now()
})
}
_onScrollTick(e) {
const { onScroll } = this.props
this.setState({
scrollLastTick: Date.now()
})
if (onScroll) onScroll(e)
}
_onScrollEnd(e) {
const { onScroll } = this.props
this.setState({
isScrolling: false
})
if (onScroll) onScroll(e)
}
_shouldEmitScrollEvent(lastTick, eventThrottle) {
const timeSinceLastTick = Date.now() - lastTick
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
}
_maybePreventScroll(e) {
const { scrollEnabled } = this.props
if (!scrollEnabled) e.preventDefault()
}
render() {
const {
children,
contentContainerStyle,
horizontal,
style
} = this.props
const resolvedStyle = pickProps(style, scrollViewStyleKeys)
const resolvedContentContainerStyle = pickProps(contentContainerStyle, scrollViewStyleKeys)
return (
<View {...this.props} />
<View
_className='ScrollView'
onScroll={(e) => this._onScroll(e)}
onTouchMove={(e) => this._maybePreventScroll(e)}
onWheel={(e) => this._maybePreventScroll(e)}
style={{
...styles.initial,
...resolvedStyle
}}
>
{children ? (
<View
children={children}
style={{
...styles.initialContentContainer,
...resolvedContentContainerStyle,
...(horizontal && styles.row)
}}
/>
) : null}
</View>
)
}
}

View File

@@ -1,13 +0,0 @@
/*
import { assertProps, renderToDOM, shallowRender } from '../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import ScrollView from '.'
const ReactTestUtils = React.addons.TestUtils
suite.skip('ScrollView', () => {
test('prop "children"', () => {})
})
*/

View File

@@ -1,2 +0,0 @@
import Swipeable from 'react-swipeable'
export default Swipeable

View File

@@ -13,11 +13,15 @@ export default {
'letterSpacing',
'lineHeight',
'margin',
'marginHorizontal',
'marginVertical',
'marginBottom',
'marginLeft',
'marginRight',
'marginTop',
'padding',
'paddingHorizontal',
'paddingVertical',
'paddingBottom',
'paddingLeft',
'paddingRight',

View File

@@ -0,0 +1,55 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import ReactTestUtils from 'react-addons-test-utils'
import Text from '../'
suite('components/Text', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Text accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<Text accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<Text accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<Text>{children}</Text>)
assert.equal(result.props.children, children)
})
test('prop "numberOfLines"')
test('prop "onPress"', (done) => {
const dom = utils.renderToDOM(<Text onPress={onPress} />)
ReactTestUtils.Simulate.click(dom)
function onPress(e) {
assert.ok(e.nativeEvent)
done()
}
})
test('prop "style"', () => {
utils.assertProps.style(Text)
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<Text testID={testID} />)
assert.equal(result.props.testID, testID)
})
})

View File

@@ -1,17 +1,19 @@
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',
display: 'inline',
font: 'inherit',
margin: 0,
padding: 0,
textDecoration: 'none',
wordWrap: 'break-word'
},
singleLineStyle: {
@@ -20,15 +22,15 @@ const styles = {
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}
})
class Text extends React.Component {
static propTypes = {
_className: PropTypes.string, // escape-hatch for code migrations
accessibilityLabel: PropTypes.string,
accessible: PropTypes.bool,
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
accessible: CoreComponent.propTypes.accessible,
children: PropTypes.any,
component: CoreComponent.propTypes.component,
numberOfLines: PropTypes.number,
onPress: PropTypes.func,
style: PropTypes.shape(TextStylePropTypes),
@@ -40,7 +42,6 @@ class Text extends React.Component {
static defaultProps = {
_className: '',
accessible: true,
component: 'span',
style: styles.initial
}
@@ -51,14 +52,9 @@ class Text extends React.Component {
render() {
const {
_className,
accessibilityLabel,
accessible,
children,
component,
numberOfLines,
onPress,
style,
testID,
...other
} = this.props
@@ -68,18 +64,14 @@ class Text extends React.Component {
return (
<CoreComponent
{...other}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
children={children}
className={className}
component={component}
component='span'
onClick={this._onPress.bind(this)}
style={{
...styles.initial,
...resolvedStyle,
...(numberOfLines === 1 && styles.singleLineStyle)
}}
testID={testID}
/>
)
}

View File

@@ -1,49 +0,0 @@
import { assertProps, renderToDOM, shallowRender } from '../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import Text from '.'
const ReactTestUtils = React.addons.TestUtils
suite('Text', () => {
test('prop "accessibilityLabel"', () => {
assertProps.accessibilityLabel(Text)
})
test('prop "accessible"', () => {
assertProps.accessible(Text)
})
test('prop "children"', () => {
const children = 'children'
const result = shallowRender(<Text>{children}</Text>)
assert.equal(result.props.children, children)
})
test('prop "component"', () => {
assertProps.component(Text, 'span')
})
test.skip('prop "numberOfLines"', () => {})
test('prop "onPress"', (done) => {
const dom = renderToDOM(<Text onPress={onPress} />)
ReactTestUtils.Simulate.click(dom)
function onPress(e) {
assert(true, 'the "onPress" callback was never called')
assert.ok(e.nativeEvent)
done()
}
})
test('prop "style"', () => {
assertProps.style(Text)
})
test('prop "testID"', () => {
assertProps.testID(Text)
})
})

View File

@@ -1,14 +1,17 @@
import * as utils from '../../modules/specHelpers'
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import React from 'react'
import ReactTestUtils from 'react-addons-test-utils'
import TextInput from '.'
import TextInput from '../'
const ReactTestUtils = React.addons.TestUtils
suite('TextInput', () => {
suite('components/TextInput', () => {
test('prop "accessibilityLabel"', () => {
utils.assertProps.accessibilityLabel(TextInput)
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<TextInput accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "autoComplete"', () => {
@@ -29,18 +32,16 @@ suite('TextInput', () => {
assert.deepEqual(document.activeElement, dom)
})
test('prop "clearTextOnFocus"', () => {
utils.testIfDocumentFocused('prop "clearTextOnFocus"', () => {
const defaultValue = 'defaultValue'
utils.requiresFocus(() => {
// false
let dom = utils.renderAndInject(<TextInput defaultValue={defaultValue} />)
dom.focus()
assert.equal(dom.value, defaultValue)
// true
dom = utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />)
dom.focus()
assert.equal(dom.value, '')
})
// false
let dom = utils.renderAndInject(<TextInput defaultValue={defaultValue} />)
dom.focus()
assert.equal(dom.value, defaultValue)
// true
dom = utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />)
dom.focus()
assert.equal(dom.value, '')
})
test('prop "defaultValue"', () => {
@@ -87,17 +88,18 @@ suite('TextInput', () => {
test('prop "maxNumberOfLines"', () => {
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
const value = (() => {
const generateValue = () => {
let str = ''
while (str.length < 100) str += 'x'
return str
}())
}
let dom = utils.renderAndInject(
<TextInput
maxNumberOfLines={3}
multiline
style={style}
value={value}
value={generateValue()}
/>
)
const height = dom.getBoundingClientRect().height
@@ -126,7 +128,7 @@ suite('TextInput', () => {
const height = dom.getBoundingClientRect().height
// need a range because of cross-browser differences
assert.ok(height >= 40)
assert.ok(height <= 45)
assert.ok(height <= 46)
})
test('prop "onBlur"', (done) => {
@@ -166,7 +168,7 @@ suite('TextInput', () => {
}
})
test.skip('prop "onLayout"', () => {})
test('prop "onLayout"')
test('prop "onSelectionChange"', (done) => {
const input = utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />)
@@ -178,9 +180,9 @@ suite('TextInput', () => {
}
})
test.skip('prop "placeholder"', () => {})
test('prop "placeholder"')
test.skip('prop "placeholderTextColor"', () => {})
test('prop "placeholderTextColor"')
test('prop "secureTextEntry"', () => {
let dom = utils.renderToDOM(<TextInput secureTextEntry />)
@@ -190,20 +192,18 @@ suite('TextInput', () => {
assert.equal(dom.getAttribute('type'), undefined)
})
test('prop "selectTextOnFocus"', () => {
utils.testIfDocumentFocused('prop "selectTextOnFocus"', () => {
const text = 'Text'
utils.requiresFocus(() => {
// false
let dom = utils.renderAndInject(<TextInput defaultValue={text} />)
dom.focus()
assert.equal(dom.selectionEnd, 0)
assert.equal(dom.selectionStart, 0)
// true
dom = utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />)
dom.focus()
assert.equal(dom.selectionEnd, 4)
assert.equal(dom.selectionStart, 0)
})
// false
let dom = utils.renderAndInject(<TextInput defaultValue={text} />)
dom.focus()
assert.equal(dom.selectionEnd, 0)
assert.equal(dom.selectionStart, 0)
// true
dom = utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />)
dom.focus()
assert.equal(dom.selectionEnd, 4)
assert.equal(dom.selectionStart, 0)
})
test('prop "style"', () => {
@@ -211,7 +211,9 @@ suite('TextInput', () => {
})
test('prop "testID"', () => {
utils.assertProps.testID(TextInput)
const testID = 'testID'
const result = utils.shallowRender(<TextInput testID={testID} />)
assert.equal(result.props.testID, testID)
})
test('prop "value"', () => {

View File

@@ -1,12 +1,14 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
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,11 +19,11 @@ const styles = {
font: 'inherit',
padding: 0
}
}
})
class TextInput extends React.Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
autoComplete: PropTypes.bool,
autoFocus: PropTypes.bool,
clearTextOnFocus: PropTypes.bool,
@@ -70,7 +72,7 @@ class TextInput extends React.Component {
_onFocus(e) {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = React.findDOMNode(this)
const node = ReactDOM.findDOMNode(this)
if (clearTextOnFocus) node.value = ''
if (selectTextOnFocus) node.select()
if (onFocus) onFocus(e)
@@ -134,7 +136,7 @@ class TextInput extends React.Component {
}
const propsCommon = {
'aria-label': accessibilityLabel,
accessibilityLabel,
autoComplete: autoComplete && 'on',
autoFocus,
className: 'TextInput',
@@ -167,9 +169,10 @@ class TextInput extends React.Component {
type
}
return (multiline
? <CoreComponent {...propsMultiline} />
: <CoreComponent {...propsSingleline} />
const props = multiline ? propsMultiline : propsSingleline
return (
<CoreComponent {...props} />
)
}
}

View File

@@ -0,0 +1,35 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import Touchable from '../'
const children = <span style={{}}>children</span>
const requiredProps = { children }
suite('components/Touchable', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<Touchable {...requiredProps} accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const result = utils.shallowRender(<Touchable {...requiredProps} />)
assert.deepEqual(result.props.children, children)
})
})

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) {
@@ -24,9 +25,9 @@ class Touchable extends React.Component {
}
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
accessibilityLabel: View.propTypes.accessibilityLabel,
accessibilityRole: View.propTypes.accessibilityRole,
accessible: View.propTypes.accessible,
activeOpacity: PropTypes.number,
activeUnderlayColor: PropTypes.string,
children: PropTypes.element,
@@ -44,7 +45,6 @@ class Touchable extends React.Component {
accessibilityRole: 'button',
activeOpacity: 1,
activeUnderlayColor: 'transparent',
component: 'div',
delayLongPress: 1000,
delayPressIn: 0,
delayPressOut: 0,

View File

@@ -1,27 +0,0 @@
import { assertProps, shallowRender } from '../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import Touchable from '.'
const children = <span style={{}}>children</span>
const requiredProps = { children }
suite('Touchable', () => {
test('prop "accessibilityLabel"', () => {
assertProps.accessibilityLabel(Touchable, requiredProps)
})
test('prop "accessibilityRole"', () => {
assertProps.accessibilityRole(Touchable, requiredProps)
})
test('prop "accessible"', () => {
assertProps.accessible(Touchable, requiredProps)
})
test('prop "children"', () => {
const result = shallowRender(<Touchable {...requiredProps} />)
assert.deepEqual(result.props.children, children)
})
})

View File

@@ -54,6 +54,8 @@ export default {
'left',
// margin
'margin',
'marginHorizontal',
'marginVertical',
'marginBottom',
'marginLeft',
'marginRight',
@@ -70,6 +72,8 @@ export default {
'overflowY',
// padding
'padding',
'paddingHorizontal',
'paddingVertical',
'paddingBottom',
'paddingLeft',
'paddingRight',

View File

@@ -0,0 +1,54 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import View from '../'
suite('components/View', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<View accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const result = utils.shallowRender(<View accessibilityLiveRegion={accessibilityLiveRegion} />)
assert.equal(result.props.accessibilityLiveRegion, accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<View accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<View accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<View>{children}</View>)
assert.equal(result.props.children, children)
})
test('prop "pointerEvents"', () => {
const result = utils.shallowRender(<View pointerEvents='box-only' />)
assert.equal(result.props.style.pointerEvents, 'box-only')
})
test('prop "style"', () => {
utils.assertProps.style(View)
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<View testID={testID} />)
assert.equal(result.props.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 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',
@@ -20,23 +21,23 @@ const styles = {
margin: 0,
padding: 0,
position: 'relative',
textDecoration: 'none',
// button reset
backgroundColor: 'transparent',
color: 'inherit',
font: 'inherit',
textAlign: 'inherit'
}
}
})
class View extends React.Component {
static propTypes = {
_className: PropTypes.string, // escape-hatch for code migrations
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf(['assertive', 'off', 'polite']),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: CoreComponent.propTypes.accessibilityLiveRegion,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
accessible: CoreComponent.propTypes.accessible,
children: PropTypes.any,
component: CoreComponent.propTypes.component,
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
style: PropTypes.shape(ViewStylePropTypes),
testID: CoreComponent.propTypes.testID
@@ -47,20 +48,14 @@ class View extends React.Component {
static defaultProps = {
_className: '',
accessible: true,
component: 'div',
style: styles.initial
}
render() {
const {
_className,
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible,
pointerEvents,
style,
testID,
...other
} = this.props
@@ -71,17 +66,12 @@ class View extends React.Component {
return (
<CoreComponent
{...other}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
aria-live={accessibilityLiveRegion}
className={className}
role={accessibilityRole}
style={{
...styles.initial,
...resolvedStyle,
...pointerEventsStyle
}}
testID={testID}
/>
)
}

View File

@@ -1,46 +0,0 @@
import { assertProps, shallowRender } from '../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import View from '.'
suite('View', () => {
test('prop "accessibilityLabel"', () => {
assertProps.accessibilityLabel(View)
})
test('prop "accessibilityLiveRegion"', () => {
assertProps.accessibilityLiveRegion(View)
})
test('prop "accessibilityRole"', () => {
assertProps.accessibilityRole(View)
})
test('prop "accessible"', () => {
assertProps.accessible(View)
})
test('prop "children"', () => {
const children = 'children'
const result = shallowRender(<View>{children}</View>)
assert.equal(result.props.children, children)
})
test('prop "component"', () => {
assertProps.component(View)
})
test('prop "pointerEvents"', () => {
const result = shallowRender(<View pointerEvents='box-only' />)
assert.equal(result.props.style.pointerEvents, 'box-only')
})
test('prop "style"', () => {
assertProps.style(View)
})
test('prop "testID"', () => {
assertProps.testID(View)
})
})

View File

@@ -1,10 +1,11 @@
import React from 'react'
import StyleSheet from './modules/StyleSheet'
// components
import Image from './components/Image'
import ListView from './components/ListView'
import ScrollView from './components/ScrollView'
import Swipeable from './components/Swipeable'
import Text from './components/Text'
import TextInput from './components/TextInput'
import Touchable from './components/Touchable'
@@ -13,10 +14,12 @@ import View from './components/View'
export default React
export {
StyleSheet,
// components
Image,
ListView,
ScrollView,
Swipeable,
Text,
TextInput,
Touchable,

View File

@@ -1,16 +1,13 @@
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,
alignItems: string,
alignSelf: string,
appearance: string,
backfaceVisibility: string,
backgroundAttachment: string,
backgroundClip: string,
@@ -20,21 +17,21 @@ export default {
backgroundPosition: string,
backgroundRepeat: string,
backgroundSize: string,
borderColor: numberOrString,
borderBottomColor: numberOrString,
borderLeftColor: numberOrString,
borderRightColor: numberOrString,
borderTopColor: numberOrString,
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,
@@ -63,11 +61,14 @@ export default {
left: numberOrString,
letterSpacing: string,
lineHeight: numberOrString,
listStyle: string,
margin: numberOrString,
marginBottom: numberOrString,
marginHorizontal: numberOrString,
marginLeft: numberOrString,
marginRight: numberOrString,
marginTop: numberOrString,
marginVertical: numberOrString,
maxHeight: numberOrString,
maxWidth: numberOrString,
minHeight: numberOrString,
@@ -79,13 +80,16 @@ export default {
overflowY: string,
padding: numberOrString,
paddingBottom: numberOrString,
paddingHorizontal: numberOrString,
paddingLeft: numberOrString,
paddingRight: numberOrString,
paddingTop: numberOrString,
paddingVertical: numberOrString,
position: string,
right: numberOrString,
textAlign: string,
textDecoration: string,
textOverflow: string,
textTransform: string,
top: numberOrString,
userSelect: string,

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] = `_s_${this._counter}`
} else {
const val = `${value}`.replace(/\s/g, '-')
this._classNames[key] = `${property}:${val}`
}
}
}
}

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': '_s_1',
'flexGrow:0': '_s_2',
'flexGrow:1': '_s_3',
'flexGrow:2': '_s_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' +
'._s_1{align-items:center;}\n' +
'._s_3{margin:1px;}\n' +
'._s_4{margin:2px;}\n' +
'._s_5{margin:3px;}\n' +
'._s_2{margin-bottom:0px;}'
assert.equal(store.toString(), expected)
})
})
})

View File

@@ -0,0 +1,29 @@
/* eslint-env mocha */
import assert from 'assert'
import expandStyle from '../expandStyle'
suite('modules/StyleSheet/expandStyle', () => {
test('style property', () => {
const initial = {
borderTopWidth: 1,
borderWidth: 2,
marginTop: 50,
marginVertical: 25,
margin: 10
}
const expectedStyle = {
borderTopWidth: 1,
borderLeftWidth: 2,
borderRightWidth: 2,
borderBottomWidth: 2,
marginTop: 50,
marginBottom: 25,
marginLeft: 10,
marginRight: 10
}
assert.deepEqual(expandStyle(initial), expectedStyle)
})
})

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,16 @@
/* eslint-env mocha */
import assert from 'assert'
import hyphenate from '../hyphenate'
suite('modules/StyleSheet/hyphenate', () => {
test('style property', () => {
assert.equal(hyphenate('alignItems'), 'align-items')
assert.equal(hyphenate('color'), 'color')
})
test('vendor prefixed style property', () => {
assert.equal(hyphenate('MozTransition'), '-moz-transition')
assert.equal(hyphenate('msTransition'), '-ms-transition')
assert.equal(hyphenate('WebkitTransition'), '-webkit-transition')
})
})

View File

@@ -0,0 +1,37 @@
/* eslint-env mocha */
import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert'
import StyleSheet from '..'
const styles = { root: { borderWidth: 1 } }
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` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
)
})
test('resolve', () => {
const props = { className: 'className', style: styles.root }
const expected = { className: 'className borderTopWidth:1px borderRightWidth:1px borderBottomWidth:1px borderLeftWidth:1px', 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,51 @@
const styleShortHands = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ],
borderWidth: [ 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth' ],
margin: [ 'marginTop', 'marginRight', 'marginBottom', 'marginLeft' ],
marginHorizontal: [ 'marginRight', 'marginLeft' ],
marginVertical: [ 'marginTop', 'marginBottom' ],
padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ],
paddingHorizontal: [ 'paddingRight', 'paddingLeft' ],
paddingVertical: [ 'paddingTop', 'paddingBottom' ]
}
/**
* Alpha-sort properties, apart from shorthands which appear before the
* properties they expand into. This ensures that more specific styles override
* the shorthands, whatever the order in which they were originally declared.
*/
const sortProps = (propsArray) => propsArray.sort((a, b) => {
const expandedA = styleShortHands[a]
const expandedB = styleShortHands[b]
if (expandedA && expandedA.indexOf(b) > -1) {
return -1
} else if (expandedB && expandedB.indexOf(a) > -1) {
return 1
}
return a < b ? -1 : a > b ? 1 : 0
})
/**
* Expand the shorthand properties to isolate every declaration from the others.
*/
const expandStyle = (style) => {
const propsArray = Object.keys(style)
const sortedProps = sortProps(propsArray)
return sortedProps.reduce((resolvedStyle, key) => {
const expandedProps = styleShortHands[key]
const value = style[key]
if (expandedProps) {
expandedProps.forEach((prop, i) => {
resolvedStyle[expandedProps[i]] = value
})
} else {
resolvedStyle[key] = value
}
return resolvedStyle
}, {})
}
export default expandStyle

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()).replace(/^ms-/, '-ms-')

View File

@@ -0,0 +1,88 @@
import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
import expandStyle from './expandStyle'
import getStyleObjects from './getStyleObjects'
import prefixer from './prefixer'
import Store from './Store'
import StylePropTypes from '../StylePropTypes'
/**
* 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) => {
const style = expandStyle(rule)
Object.keys(style).forEach((property) => {
if (!StylePropTypes[property]) {
console.error(`ReactNativeWeb: the style property "${property}" is not supported`)
} else {
const value = style[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 expandedStyle = expandStyle(style)
const classList = [ className ]
for (const prop in expandedStyle) {
if (!StylePropTypes[prop]) {
continue
}
let styleClass = store.get(prop, expandedStyle[prop])
if (styleClass) {
classList.push(styleClass)
} else {
_style[prop] = expandedStyle[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,24 @@
/**
* 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 */
._s_pe-a, ._s_pe-bo, ._s_pe-bn * {pointer-events:auto}
._s_pe-n, ._s_pe-bo *, ._s_pe-bn {pointer-events:none}`
export const predefinedClassNames = {
'pointerEvents:auto': '_s_pe-a',
'pointerEvents:box-none': '_s_pe-bn',
'pointerEvents:box-only': '_s_pe-bo',
'pointerEvents:none': '_s_pe-n'
}

View File

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

View File

@@ -1,4 +1,6 @@
import { omitProps, pickProps } from '.'
/* eslint-env mocha */
import { omitProps, pickProps } from '..'
import assert from 'assert'
suite('pickProps', () => {

View File

@@ -1,47 +1,11 @@
import assert from 'assert'
import React from 'react/addons'
/* eslint-env mocha */
const ReactTestUtils = React.addons.TestUtils
import assert from 'assert'
import React from 'react'
import ReactDOM from 'react-dom'
import ReactTestUtils from 'react-addons-test-utils'
export const assertProps = {
accessibilityLabel: function (Component, props) {
// with label
const accessibilityLabel = 'accessibilityLabel'
const dom = renderToDOM(<Component {...props} accessibilityLabel={accessibilityLabel} />)
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
},
accessibilityLiveRegion: function (Component, props) {
const accessibilityLiveRegion = 'polite'
const dom = renderToDOM(<Component {...props} accessibilityLiveRegion={accessibilityLiveRegion} />)
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
},
accessibilityRole: function (Component, props) {
const accessibilityRole = 'main'
const dom = renderToDOM(<Component {...props} accessibilityRole={accessibilityRole} />)
assert.equal(dom.getAttribute('role'), accessibilityRole)
},
accessible: function (Component, props) {
// accessible (implicit)
let dom = renderToDOM(<Component {...props} />)
assert.equal(dom.getAttribute('aria-hidden'), null)
// accessible (explicit)
dom = renderToDOM(<Component {...props} accessible />)
assert.equal(dom.getAttribute('aria-hidden'), null)
// not accessible
dom = renderToDOM(<Component {...props} accessible={false} />)
assert.equal(dom.getAttribute('aria-hidden'), 'true')
},
component: function (Component, props) {
const component = 'main'
const dom = renderToDOM(<Component {...props} component={component} />)
const tagName = (dom.tagName).toLowerCase()
assert.equal(tagName, component)
},
style: function (Component, props) {
let shallow
// default styles
@@ -65,30 +29,15 @@ export const assertProps = {
shallow.props.style,
{ ...Component.defaultProps.style, ...styleToMerge }
)
},
testID: function (Component, props) {
// no testID
let dom = renderToDOM(<Component {...props} />)
assert.equal(dom.getAttribute('data-testid'), null)
// with testID
const testID = 'Example.testID'
dom = renderToDOM(<Component {...props} testID={testID} />)
assert.equal(dom.getAttribute('data-testid'), testID)
}
}
export function render(element, container) {
return container
? React.render(element, container)
? ReactDOM.render(element, container)
: ReactTestUtils.renderIntoDocument(element)
}
export function renderToDOM(element, container) {
const result = render(element, container)
return React.findDOMNode(result)
}
export function renderAndInject(element) {
const id = '_renderAndInject'
let div = document.getElementById(id)
@@ -102,22 +51,24 @@ export function renderAndInject(element) {
}
const result = render(element, div)
return React.findDOMNode(result)
return ReactDOM.findDOMNode(result)
}
export function requiresFocus(test, fallback) {
if (document.hasFocus && document.hasFocus()) {
test()
} else {
console.warn('Test was skipped as the document is not focused')
if (fallback) {
fallback()
}
}
export function renderToDOM(element, container) {
const result = render(element, container)
return ReactDOM.findDOMNode(result)
}
export function shallowRender(component, context = {}) {
const shallowRenderer = React.addons.TestUtils.createRenderer()
const shallowRenderer = ReactTestUtils.createRenderer()
shallowRenderer.render(component, context)
return shallowRenderer.getRenderOutput()
}
export function testIfDocumentFocused(message, fn) {
if (document.hasFocus && document.hasFocus()) {
test(message, fn)
} else {
test.skip(`${message} WARNING: document is not focused`)
}
}

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; }

View File

@@ -4,6 +4,5 @@
*
* See: https://github.com/webpack/docs/wiki/context
*/
const specContext = require.context('.', true, /.+\.spec\.jsx?$/)
specContext.keys().forEach(specContext)
module.exports = specContext
var context = require.context('./src', true, /-test\.js$/)
context.keys().forEach(context)