Compare commits

..

83 Commits
0.0.2 ... 0.0.6

Author SHA1 Message Date
Nicolas Gallagher
ec9985a3b3 0.0.6 2015-09-20 19:52:18 -07:00
Nicolas Gallagher
0aa29d8816 Image: add test for 'role=img' 2015-09-20 19:41:13 -07:00
Nicolas Gallagher
45777e0405 Add '_className' escape-hatch to Text and View 2015-09-20 19:31:59 -07:00
Nicolas Gallagher
84e06564d4 Accessibility fixes for Image and Text 2015-09-20 19:21:01 -07:00
Nicolas Gallagher
1d1b633317 npm-script: fix 'test' 2015-09-20 16:40:46 -07:00
Nicolas Gallagher
565ec2fee8 Docs: minor update 2015-09-20 15:50:22 -07:00
Nicolas Gallagher
33082f988e Text: add props 'accessibilityLabel' and 'accessible'
These properties are missing in React Native but important for
accessible web interfaces.
2015-09-20 15:48:09 -07:00
Nicolas Gallagher
a2fb65a79c Touchable: activeHighlight -> activeUnderlayColor 2015-09-20 15:47:35 -07:00
Nicolas Gallagher
e727193809 TextInput: props and tests 2015-09-20 15:43:52 -07:00
Nicolas Gallagher
d6db206ec4 Fix type of 'fontSize' style 2015-09-20 15:29:00 -07:00
Nicolas Gallagher
b3beea9bb3 Fix npm scripts 2015-09-20 15:25:03 -07:00
Nicolas Gallagher
ef4de789ae ScrollView: initial docs 2015-09-14 17:28:20 -07:00
Nicolas Gallagher
86037ab740 Add links to slack and gitter rooms 2015-09-14 11:51:44 -07:00
Nicolas Gallagher
6c20646f10 Touchable: add 'accessibilityRole' prop 2015-09-13 22:59:45 -07:00
Nicolas Gallagher
b5aa68dc7a specHelpers: accept props 2015-09-13 22:59:04 -07:00
Nicolas Gallagher
5668c40ee6 Don't strip testID's in production 2015-09-13 21:43:25 -07:00
Nicolas Gallagher
be86250ac6 Image: fix example code 2015-09-12 18:08:58 -07:00
Nicolas Gallagher
1e04dfc306 filterObjectProps: props -> propKeys 2015-09-12 18:08:21 -07:00
Nicolas Gallagher
283ab2fa2e Update dependencies 2015-09-12 18:07:22 -07:00
Nicolas Gallagher
09dd9a224a Remove type tests; fix code style 2015-09-11 21:28:03 -07:00
Nicolas Gallagher
c7524b7b6f Add Flow type checking; React >= 0.13 2015-09-11 21:17:48 -07:00
Nicolas Gallagher
5453c8843a Trivial edits 2015-09-09 00:48:18 -07:00
Nicolas Gallagher
e0f836ccb5 Minor example page update 2015-09-09 00:31:32 -07:00
Nicolas Gallagher
eada8e7fc7 Refactor dev workflow 2015-09-08 23:29:31 -07:00
Nicolas Gallagher
114fb5f8c7 LICENCE -> LICENSE 2015-09-08 22:44:27 -07:00
Nicolas Gallagher
4aa87c79fa Fix tests and code style 2015-09-08 14:21:17 -07:00
Nicolas Gallagher
9107fd3de9 Use more reliable npm badge host 2015-09-08 09:26:16 -07:00
Nicolas Gallagher
c72173ff88 Image: set 'resizeMode' default to 'stretch'
Fix #8
2015-09-08 00:29:53 -07:00
Nicolas Gallagher
edf0fda75a Docs: minor adjustments 2015-09-08 00:24:12 -07:00
Nicolas Gallagher
3b848fe378 View: additional accessibility props
* Add `accessibilityLiveRegion` for `aria-live` support.
* Add `accessibilityRole` for `role` support.

Fix #11
2015-09-08 00:09:09 -07:00
Nicolas Gallagher
7eff1a644e Spec helpers 2015-09-07 23:37:36 -07:00
Nicolas Gallagher
2750d70a93 Add support for 'accessible' prop 2015-09-07 22:09:16 -07:00
Nicolas Gallagher
65559f50e6 Image: fix ARIA role 2015-09-07 19:16:58 -07:00
Nicolas Gallagher
77fd21ea44 Touchable: add support for 'style' and keyboard 2015-09-07 14:40:37 -07:00
Nicolas Gallagher
38e4de76cd Documentation edits; transfer props 2015-09-07 13:14:09 -07:00
Nicolas Gallagher
0f42cd83e1 Component stylePropTypes keys as constants 2015-09-07 13:00:59 -07:00
Nicolas Gallagher
e6cbea82c4 Don't optimize published package 2015-09-07 12:59:16 -07:00
Nicolas Gallagher
6d4c9e881f Move CoreComponent module 2015-09-07 12:58:48 -07:00
Nicolas Gallagher
b9c8a560a0 0.0.5 2015-09-07 10:16:22 -07:00
Nicolas Gallagher
357201e843 Fix lint errors 2015-09-07 10:12:42 -07:00
Nicolas Gallagher
0ab345a490 Fix tests for Firefox 2015-09-07 10:00:06 -07:00
Nicolas Gallagher
2e087c10ad Add karma-firefox-launcher 2015-09-07 09:51:09 -07:00
Nicolas Gallagher
a7c1e105c6 Fix links to View doc 2015-09-07 09:49:38 -07:00
Nicolas Gallagher
969b57de53 Update examples 2015-09-07 09:45:16 -07:00
Nicolas Gallagher
50771fc5cd Misc fixes 2015-09-07 09:45:10 -07:00
Nicolas Gallagher
cd215a916d Remove legacy react-native-web-style module 2015-09-07 09:44:18 -07:00
Nicolas Gallagher
1668dc4635 Add placeholder ListView and ScrollView 2015-09-07 09:43:53 -07:00
Nicolas Gallagher
f0b6576e80 Add new Swipeable component 2015-09-07 09:43:19 -07:00
Nicolas Gallagher
7551596fa5 Add early Touchable implementation 2015-09-07 09:42:52 -07:00
Nicolas Gallagher
90e015112a Update Image props and implementation 2015-09-07 09:42:23 -07:00
Nicolas Gallagher
cfdbd351f0 Update TextInput props and implementation 2015-09-07 09:42:02 -07:00
Nicolas Gallagher
08013daa23 Update Text props and implementation 2015-09-07 09:41:37 -07:00
Nicolas Gallagher
e7a5d56058 Create CoreComponent; update View implementation 2015-09-07 09:40:51 -07:00
Nicolas Gallagher
abf2c0307f Rewrite documentation 2015-09-07 09:38:25 -07:00
Nicolas Gallagher
6bb6a17046 Fix travis build 2015-09-04 22:12:59 -07:00
Nicolas Gallagher
b5c8af2694 Documentation fixes 2015-09-03 23:12:17 -07:00
Nicolas Gallagher
17d993261b 0.0.4 2015-09-03 19:43:32 -07:00
Nicolas Gallagher
ef4064d966 Add CONTRIBUTING.md 2015-09-03 19:39:03 -07:00
Nicolas Gallagher
b9e2007b59 Add travis.yml 2015-09-03 19:38:52 -07:00
Nicolas Gallagher
247f489fdd Add LICENSE 2015-09-03 19:38:45 -07:00
Nicolas Gallagher
d5b3b60c04 Update package.json and scripts 2015-09-03 19:38:36 -07:00
Nicolas Gallagher
6b55032e49 Reorganize docs; rewrite README 2015-09-03 19:38:21 -07:00
Nicolas Gallagher
77f73a8929 Reorganize modules 2015-09-03 19:37:20 -07:00
Nicolas Gallagher
f951de43a2 Move config files 2015-09-03 19:36:43 -07:00
Nicolas Gallagher
fb7a997256 Add 'Image' tests 2015-09-02 17:28:07 -07:00
Nicolas Gallagher
1417dd2e6a Install eslint and fix code style 2015-09-02 17:15:05 -07:00
Nicolas Gallagher
ff5c8f64cc Text tests 2015-09-01 16:45:11 -07:00
Nicolas Gallagher
653cfd71ce View tests 2015-09-01 16:45:00 -07:00
Nicolas Gallagher
95d6b98e9d Add tests for 'filterObjectProps' 2015-09-01 14:44:59 -07:00
Nicolas Gallagher
92dbadacb5 Initial test framework setup 2015-09-01 14:44:42 -07:00
Nicolas Gallagher
d3dce675df Reduce size of API; remove helpers 2015-09-01 14:43:05 -07:00
Nicolas Gallagher
000bbf9f93 Simplify organization of react-native-web-style 2015-09-01 14:07:38 -07:00
Nicolas Gallagher
9d424fa529 Update docs 2015-08-19 14:28:26 -07:00
Nicolas Gallagher
344239bd8a Export the styling strategy 2015-08-19 14:28:17 -07:00
Nicolas Gallagher
8ec27cefab Add more opacity values 2015-08-19 14:27:46 -07:00
Nicolas Gallagher
ecc23f46d4 Move all style related work into 'react-web-style'
Move the styling strategy into a separate module within react-web-style;
consider supporting strategy injection.
2015-08-19 14:07:57 -07:00
Nicolas Gallagher
39088affbc Add TextInput to example 2015-08-17 13:46:28 -07:00
Nicolas Gallagher
5494a8e191 add(TextInput): initial implementation 2015-08-17 13:46:11 -07:00
Nicolas Gallagher
95b1af9f1f Update default styles 2015-08-17 13:45:36 -07:00
Nicolas Gallagher
1f3ac7a7b8 fix(StylePropTypes): add missing values and props 2015-08-17 13:15:09 -07:00
Nicolas Gallagher
d86c2f4840 change(babelrc): add es7.classProperties 2015-08-17 13:13:43 -07:00
Nicolas Gallagher
89202e51f6 0.0.3 2015-07-05 15:23:41 -07:00
Nicolas Gallagher
36b6bd05af Remove unused import 2015-07-05 15:23:00 -07:00
106 changed files with 4111 additions and 1633 deletions

View File

@@ -1,5 +1,11 @@
{
"env": {
"development": {
"plugins": [ "typecheck" ]
}
},
"optional": [
"es7.classProperties",
"runtime"
],
"stage": 1

17
.eslintrc Normal file
View File

@@ -0,0 +1,17 @@
{
// babel parser to support ES features
"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" } ],
"wrap-iife": [ 2, "outside" ],
// overrides of the standard-react style
"react/jsx-sort-props": 2,
"react/jsx-sort-prop-types": 2
}
}

6
.travis.yml Normal file
View File

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

125
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,125 @@
# Contributing to this project
The issue tracker is the preferred channel for [bug reports](#bugs),
[features requests](#features) and [submitting pull
requests](#pull-requests).
<a name="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; create a [reduced test
case](http://css-tricks.com/reduced-test-cases/) and a live example.
A good bug report contains as much detail as possible. What is your
environment? What steps will reproduce the issue? What browser(s) and OS
experience the problem? What would you expect to be the outcome? All these
details really help!
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the browser/OS environment in which it occurs. If
> suitable, include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
<a name="features"></a>
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature. Please
provide as much detail and context as possible.
<a name="pull-requests"></a>
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. Please keep them focused in scope and avoid containing unrelated commits.
**Please ask first** before embarking on any significant pull request (e.g.
implementing new features or components, refactoring code), otherwise you risk
spending a lot of time working on something that the project's developers might
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 lint` run the linter
* `npm run test:specs` run and watch the unit tests
Please follow this process for submitting a patch:
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/react-native-web
# Navigate to the newly cloned directory
cd react-native-web
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/necolas/react-native-web
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout master
git pull upstream master
```
3. Create a new topic branch (off the main project development branch) to
contain your feature, change, or fix:
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Please adhere to these [git commit
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
or your code is unlikely be merged into the main project. Use Git's
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream development branch into your topic branch:
```bash
git pull [--rebase] upstream master
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
license your work under the same license as that used by the project.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Nicolas Gallagher
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

221
README.md
View File

@@ -1,102 +1,150 @@
# react-web-sdk
# React Native for Web
**Experimental / Proof of concept**
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
A React SDK (~9KB gzipped) for creating web applications and toolkits. Inspired by `react-native`.
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.
It includes the following components:
* [Slack: reactiflux channel #react-native-web][slack-url]
* [Gitter: react-native-web][gitter-url]
* `View`: a flexbox primitive
* `Text`: a text primitive
* `Image`: an image primitive
* (`Component`: base / implementation)
## Table of contents
And uses a [styling strategy](docs/styling-strategy.md) that maps inline styles
to single-purpose CSS rules.
* [Install](#install)
* [Use](#use)
* [Components](#components)
* [Styling](#styling)
* [Contributing](#contributing)
* [Thanks](#thanks)
* [License](#license)
This proof of concept uses a CSS bundle (~4.5KB gzipped) of 300+ precomputed
declarations. A more sophisticated implementation is likely to produce a
slightly larger CSS file and fewer inline styles.
## Install
```
npm install --save react react-native-web
```
## Use
React Native for Web exports its components and a reference to the `React`
installation. Styles are authored in JavaScript as plain objects.
```js
import React, { View } from 'react-native-web'
class MyComponent extends React.Component {
render() {
return (
<View style={styles.root} />
)
}
}
const styles = {
root: {
borderColor: 'currentcolor'
borderWidth: '5px',
flexDirection: 'row'
height: '5em'
}
}
```
## Components
All components define explicit `style` PropTypes according to the [`StyleProp`
spec](docs/StyleProp.spec.md).
### [`Image`](docs/components/Image.md)
### Component
An accessibile image component with support for image resizing, default image,
and child content.
TODO
### [`ListView`](docs/components/ListView.md)
[`Component`](docs/Component.md)
(TODO)
### View
### [`ScrollView`](docs/components/ListView.md)
TODO
(TODO)
A flexbox container; foundational layout building block.
### [`Swipeable`](docs/components/Swipeable.md)
[`View` spec](docs/View.spec.md)
Touch bindings for swipe gestures.
See this [guide to flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/).
### [`Text`](docs/components/Text.md)
### Text
Displays text as an inline block and supports basic press handling.
TODO
### [`TextInput`](docs/components/TextInput.md)
[`Text` spec](docs/View.spec.md)
Accessible single- and multi-line text input via a keyboard.
### Image
### [`Touchable`](docs/components/Touchable.md)
TODO
Touch bindings for press and long press.
[`Image` spec](docs/View.spec.md)
### [`View`](docs/components/View.md)
The fundamental UI building block using flexbox for layout.
## Styling
Styling is identical to using inline styles in React, but most inline styles
are converted to unique CSS classes. **This is only true for the SDK
components**.
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].
The companion stylesheet can be referenced as an external resource, inlined, or
injected by JS.
See the [styling strategy docs](docs/styling-strategy.md) for more details.
### Use plain JavaScript objects
Use JavaScript to write style definitions in React components:
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
const style = {
common: {
backgroundColor: 'white',
borderRadius: '1em'
},
root: {
flexDirection: 'row',
justifyContent: 'space-between'
},
image: {
opacity: 0.5
},
text: {
fontWeight: '300'
}
};
```
import React, { Image, Text, View } from 'react-native-web'
### Use the `style` attribute
The `style` attribute is a simple API for creating element-scoped styles.
```js
import {View} from 'react-web-sdk';
class Example extends React.Component {
class App extends React.Component {
render() {
return (
<View style={style.root}>...</View>
);
<View style={styles.row}>
<Image
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png' }}
style={styles.image}
/>
<View style={styles.text}>
<Text style={styles.title}>
React Native Web
</Text>
<Text style={styles.subtitle}>
Build high quality web apps using React
</Text>
</View>
</View>
)
},
})
const styles = {
row: {
flexDirection: 'row',
margin: 40
},
image: {
height: 40,
marginRight: 10,
width: 40,
},
text: {
flex: 1,
justifyContent: 'center'
},
title: {
fontSize: '1.25rem',
fontWeight: 'bold'
},
subtitle: {
fontSize: '1rem'
}
}
```
@@ -104,7 +152,7 @@ class Example extends React.Component {
Combine and override style objects:
```js
import baseStyle from './baseStyle';
import baseStyle from './baseStyle'
const buttonStyle = {
...baseStyle,
@@ -113,25 +161,32 @@ const buttonStyle = {
}
```
## Utilities
## Contributing
#### `getOtherProps(reactComponentInstance)`
Please read the [contribution guidelines][contributing-url]. Contributions are
welcome!
Returns an object containing all the props from `this.props` that are not
defined in `propTypes`.
## Thanks
#### `omitProps(obj, arrayOfExcludedProps)`
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.
Returns an object with the specified props excluded.
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`.
#### `pickProps(obj, arrayOfIncludedProps)`
## License
Returns an object with the specified props included.
Copyright (c) 2015 Nicolas Gallagher. Released under the [MIT
license](http://www.opensource.org/licenses/mit-license.php).
## Development
```
npm install
npm run build:example:watch
open example/index.html
```
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/CONTRIBUTING.md
[flexbox-guide-url]: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
[gitter-url]: https://gitter.im/necolas/react-native-web
[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

9
config/constants.js Normal file
View File

@@ -0,0 +1,9 @@
var path = require('path')
var ROOT = path.join(__dirname, '..')
module.exports = {
DIST_DIRECTORY: path.join(ROOT, 'dist'),
SRC_DIRECTORY: path.join(ROOT, 'src'),
ROOT_DIRECTORY: ROOT
}

46
config/karma.config.js Normal file
View File

@@ -0,0 +1,46 @@
var assign = require('object-assign')
var constants = require('./constants')
var webpackConfig = require('./webpack.config.base')
module.exports = function (config) {
config.set({
basePath: constants.ROOT_DIRECTORY,
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
browserNoActivityTimeout: 60000,
client: {
captureConsole: true,
mocha: {
ui: 'tdd'
},
useIframe: true
},
files: [
'src/specs.context.js'
],
frameworks: [
'mocha'
],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-sourcemap-loader',
'karma-webpack'
],
preprocessors: {
'src/specs.context.js': [ 'webpack', 'sourcemap' ]
},
reporters: [ 'dots' ],
singleRun: true,
webpack: assign({}, webpackConfig, { devtool: 'inline' }),
webpackMiddleware: {
stats: {
assetsSort: 'name',
colors: true,
children: false,
chunks: false,
modules: false
}
}
})
}

View File

@@ -0,0 +1,45 @@
var webpack = require('webpack')
var DedupePlugin = webpack.optimize.DedupePlugin
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
var plugins = [
new DedupePlugin(),
new OccurenceOrderPlugin()
]
if (process.env.NODE_ENV === 'production') {
plugins.push(
new UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
screw_ie8: true,
warnings: true
}
})
)
}
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: [
'style-loader',
'css-loader?module&localIdentName=[hash:base64:5]',
'autoprefixer-loader'
].join('!')
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
plugins: plugins
}

View File

@@ -0,0 +1,17 @@
var assign = require('object-assign')
var base = require('./webpack.config.base')
var constants = require('./constants')
var path = require('path')
module.exports = assign({}, base, {
devServer: {
contentBase: constants.SRC_DIRECTORY
},
entry: {
example: path.join(constants.SRC_DIRECTORY, 'example')
},
output: {
filename: 'example.js',
path: constants.DIST_DIRECTORY
}
})

View File

@@ -0,0 +1,18 @@
var assign = require('object-assign')
var base = require('./webpack.config.base')
var constants = require('./constants')
module.exports = assign({}, base, {
entry: {
main: constants.SRC_DIRECTORY
},
externals: [{
react: true
}],
output: {
filename: 'react-native-web.js',
library: 'ReactNativeWeb',
libraryTarget: 'commonjs2',
path: constants.DIST_DIRECTORY
}
})

View File

@@ -1,44 +0,0 @@
# `Component`
This component is part of the implementation for managing styles across the
`className` and `style` properties. It is the building block upon which all
other components in `react-web-sdk` are built.
## PropTypes
All other props are transferred directly to the `element`.
+ `element`: `func` or `string`
+ `style`: `object`
#### Examples
```js
import {Component, pickProps} from 'react-web-sdk';
import React, {PropTypes} from 'react';
const ExampleStylePropTypes = { opacity: PropTypes.number };
const ExampleStyleDefaultProps = { opacity: 1 };
class Example extends React.Component {
static propTypes = {
style: PropTypes.shape(ExampleStylePropTypes)
}
render() {
const { style, ...other } = this.props;
// only apply supported styles
const supportedStyle = pickProps(style, ExampleStylePropTypes);
// merge with default styles
const mergedStyle = { ...ExampleStyleDefaultProps, ...supportedStyle }
return (
<Component
...other
element="main"
style={mergedStyle}
/>
);
}
}
```

View File

@@ -1,66 +0,0 @@
# Image spec
#### PropTypes
All other props are transferred directly to the `element`.
+ `accessibilityLabel`: `string`
+ `async`: `bool` (TODO)
+ `className`: `string`
+ `source`: `string`
+ `style`: `ImageStylePropTypes`
#### ImageStylePropTypes
+ `BackgroundPropTypes`
+ `BorderThemePropTypes`
+ `LayoutPropTypes`
+ `opacity`: `string`
#### Examples
```js
import {Image} from 'react-web-sdk';
import React, {PropTypes} from 'react';
class Avatar extends React.Component {
static propTypes = {
size: PropTypes.oneOf(['small', 'normal', 'large']),
user: PropTypes.object
}
static defaultProps = {
size: 'normal'
}
render() {
return (
<Image
accessibilityLabel={`${user.name}'s profile picture`}
source={user.avatarUrl}
style={ ...style.base, ...style[this.props.size] }
/>
);
}
}
const style = {
base: {
borderColor: 'white',
borderRadius: '5px',
borderWidth: '5px'
},
small: {
height: '32px',
width: '32px'
},
normal: {
height: '48px',
width: '48px'
},
large: {
height: '64px',
width: '32px'
}
}
```

View File

@@ -1,89 +0,0 @@
## StyleProp spec
### Background
+ `backgroundColor`: `string`
+ `backgroundImage`: `string`
+ `backgroundPosition`: `string`
+ `backgroundRepeat`: `string`
+ `backgroundSize`: `string`
### BorderTheme
+ `borderColor`: `string`
+ `borderTopColor`: `string`
+ `borderRightColor`: `string`
+ `borderBottomColor`: `string`
+ `borderLeftColor`: `string`
+ `borderStyle`: `string`
+ `borderRadius`: `number` or `string`
+ `borderTopLeftRadius`: `number` or `string`
+ `borderTopRightRadius`: `number` or `string`
+ `borderBottomLeftRadius`: `number` or `string`
+ `borderBottomRightRadius`: `number` or `string`
### BoxModel
+ `borderWidth`: `number` or `string`
+ `borderTopWidth`: `number` or `string`
+ `borderRightWidth`: `number` or `string`
+ `borderBottomWidth`: `number` or `string`
+ `borderLeftWidth`: `number` or `string`
+ `boxSizing`: `oneOf('border-box', 'content-box')`
+ `display`: `oneOf('block', 'flex', 'inline', 'inline-block', 'inline-flex')`
+ `height`: `number` or `string`
+ `margin`: `number` or `string`
+ `marginTop`: `number` or `string`
+ `marginRight`: `number` or `string`
+ `marginBottom`: `number` or `string`
+ `marginLeft`: `number` or `string`
+ `padding`: `number` or `string`
+ `paddingTop`: `number` or `string`
+ `paddingRight`: `number` or `string`
+ `paddingBottom`: `number` or `string`
+ `paddingLeft`: `number` or `string`
+ `width`: `number` or `string`
### Flexbox
* `alignContent`: `oneOf('center', 'flex-end', 'flex-start', 'stretch', 'space-around', 'space-between')`
* `alignItems`: `oneOf('baseline', 'center', 'flex-end', 'flex-start', 'stretch')`
* `alignSelf`: `oneOf('auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch')`
* `flex`: `string`
* `flexBasis`: `string`
* `flexDirection`: `oneOf('column', 'column-reverse', 'row', 'row-reverse')`
* `flexGrow`: `number`
* `flexShrink`: `number`
* `flexWrap`: `oneOf('nowrap', 'wrap', 'wrap-reverse')`
* `justifyContent`: `oneOf('center', 'flex-end', 'flex-start', 'space-around', 'space-between')`
* `order`: `number`
### Layout
* BoxModel
* Flexbox
* Position
### Position
* `position`: `oneOf('absolute', 'fixed', 'relative')`
* `bottom`: `number` or `string`
* `left`: `number` or `string`
* `right`: `number` or `string`
* `top`: `number` or `string`
* `zIndex`: `number`
### Typographic
* `direction`: `oneOf('auto', 'ltr', 'rtl')`
* `fontFamily`: `string`
* `fontSize`: `string`
* `fontWeight`: `oneOf('100', '200', '300', '400', '500', '600', '700', '800', '900', 'bold', 'normal')`
* `fontStyle`: `oneOf('normal', 'italic')`
* `letterSpacing`: `string`
* `lineHeight`: `number` or `string`
* `textAlign`: `oneOf('auto', 'left', 'right', 'center')`
* `textDecoration`: `oneOf('none', 'underline')`
* `textTransform`: `oneOf('capitalize', 'lowercase', 'none', 'uppercase')`
* `wordWrap`: `oneOf('break-word', 'normal')`

View File

@@ -1,70 +0,0 @@
# Text spec
Text layout and styles.
#### PropTypes
All other props are transferred directly to the `element`.
+ `element`: `func` or `string` (default `"div"`)
+ `style`: `TextStylePropTypes`
#### TextStylePropTypes
+ ViewStylePropTypes
+ TypographicPropTypes
## Examples
```js
import {Text} from 'react-web-sdk';
import React, {PropTypes} from 'react';
class PrettyText extends React.Component {
static propTypes = {
color: PropTypes.oneOf(['white', 'gray', 'red']),
size: PropTypes.oneOf(['small', 'normal', 'large']),
weight: PropTypes.oneOf(['light', 'normal', 'bold'])
}
static defaultProps = {
color: 'gray',
size: 'normal',
weight: 'normal'
}
render() {
const { color, size, style, weight, ...other } = this.props;
return (
<Text
...other
style={{
...style,
...localStyle.color[color],
...localStyle.size[size],
...localStyle.weight[weight]
}}
/>
);
}
}
const localStyle = {
color: {
white: { color: 'white' },
gray: { color: 'gray' },
red: { color: 'red' }
},
size: {
small: { fontSize: '0.85rem', padding: '0.5rem' },
normal: { fontSize: '1rem', padding: '0.75rem' },
large: { fontSize: '1.5rem', padding: '1rem' }
},
weight: {
light: { fontWeight: '300' },
normal: { fontWeight: '400' },
bold: { fontWeight: '700' }
}
}
```

View File

@@ -1,66 +0,0 @@
# View spec
`View` is a flexbox container and the fundamental building block for UI. It is
designed to be nested inside other `View`'s and to have 0-to-many children of
any type.
## PropTypes
All other props are transferred directly to the `element`.
+ `element`: `func` or `string` (default `"div"`)
+ `pointerEvents`: `oneOf('all', 'box-only', 'box-none', 'none')`
+ `style`: `ViewStylePropTypes`
## ViewStylePropTypes
+ BackgroundPropTypes
+ BorderThemePropTypes
+ LayoutPropTypes
+ `boxShadow`: `string`
+ `color`: `string`
+ `opacity`: `number`
## ViewStyleDefaultProps
Implements the default styles from
[facebook/css-layout](https://github.com/facebook/css-layout).
1. All the flex elements are oriented from top-to-bottom, left-to-right and do
not shrink. This is how things are laid out using the default CSS settings
and what you'd expect.
2. The most convenient way to express the relation between width and other
box-model properties.
3. Everything is `display:flex` by default. All the behaviors of `block` and
`inline-block` can be expressed in term of flex but not the opposite.
4. Everything is `position:relative`. This makes `position:absolute` target the
direct parent and not some parent which is either relative or absolute. If
you want to position an element relative to something else, you should move
it in the DOM instead of relying of CSS. It also makes `top`, `left`,
`right`, `bottom` do something when not specifying `position:absolute`.
```js
const ViewStyleDefaultProps = {
alignItems: 'stretch', // 1
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box', // 2
display: 'flex', // 3
flexBasis: 'auto', // 1
flexDirection: 'column', // 1
flexShrink: 0, // 1
listStyle: 'none',
margin: 0,
padding: 0,
position: 'relative' // 4
};
```
## Examples
```js
// TODO
```

143
docs/components/Image.md Normal file
View File

@@ -0,0 +1,143 @@
# Image
An accessibile image component with support for image resizing, default image,
and child content.
Unsupported React Native props:
`capInsets` (ios),
`onProgress` (ios)
## Props
**accessibilityLabel**: string
The text that's read by a screenreader when someone interacts with the image.
**accessible**: bool
When `false`, the view is hidden from screenreaders. Default: `true`.
**children**: any
Content to display over the image.
**defaultSource**: { uri: string }
An image to display as a placeholder while downloading the final image off the network.
**onError**: function
Invoked on load error with `{nativeEvent: {error}}`.
**onLayout**: function
TODO
**onLoad**: function
Invoked when load completes successfully.
**onLoadEnd**: function
Invoked when load either succeeds or fails,
**onLoadStart**: function
Invoked on load start.
**resizeMode**: oneOf('contain', 'cover', 'none', 'stretch') = 'stretch'
Determines how to resize the image when the frame doesn't match the raw image
dimensions.
**source**: { uri: string }
`uri` is a string representing the resource identifier for the image, which
could be an http address or a base64 encoded image.
**style**: style
[View](View.md) style
Defaults:
```js
{
alignSelf: 'flex-start',
backgroundColor: 'lightGray'
}
```
**testID**: string
Used to locate a view in end-to-end tests.
## Examples
```js
import placeholderAvatar from './placeholderAvatar.png'
import React, { Image } from 'react-native-web'
const { Component, PropTypes } = React;
class Avatar extends Component {
constructor(props, context) {
super(props, context)
this.state = { loading: true }
}
static propTypes = {
size: PropTypes.oneOf(['small', 'normal', 'large']),
testID: Image.propTypes.testID,
user: PropTypes.object
}
static defaultProps = {
size: 'normal'
}
_onLoad(e) {
console.log('Avatar.onLoad', e)
this.setState({ loading: false })
}
render() {
const { size, testID, user } = this.props
const loadingStyle = this.state.loading ? { styles.loading } : { }
return (
<Image
accessibilityLabel={`${user.name}'s profile picture`}
defaultSource={{ uri: placeholderAvatar }}
onLoad={this._onLoad.bind(this)}
resizeMode='cover'
source={{ uri: user.avatarUrl }}
style={{ ...styles.base, ...styles[size], ...loadingStyle }}
/>
)
}
}
const styles = {
base: {
borderColor: 'white',
borderRadius: '5px',
borderWidth: '5px'
},
loading: {
opacity: 0.5
},
small: {
height: '32px',
width: '32px'
},
normal: {
height: '48px',
width: '48px'
},
large: {
height: '64px',
width: '64px'
}
}
```

View File

@@ -0,0 +1,42 @@
# ListView
TODO
## Props
**children**: any
Content to display over the image.
**style**: style
+ `property` type
Defaults:
```js
{
}
```
## Examples
```js
import React, { ListView } from 'react-native-web'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
render() {
return (
<ListView />
)
}
}
```

View File

@@ -0,0 +1,59 @@
# ScrollView
TODO
## Props
**children**: any
Child content.
**contentContainerStyle**: style
These styles will be applied to the scroll view content container which wraps
all of the child views. Example:
**horizontal**: bool = false
When true, the scroll view's children are arranged horizontally in a row instead of vertically in a column. Default: `false`.
**onScroll**: function
Fires at most once per frame during scrolling. The frequency of the events can be contolled using the `scrollEventThrottle` prop.
**scrollEnabled**: bool
When false, the content does not scroll. Default: `true`.
**scrollEventThrottle**: number
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.)
**style**: style
[View](View.md) style
## Examples
```js
import React, { ScrollView } from 'react-native-web'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
render() {
return (
)
}
}
```

View File

@@ -0,0 +1,95 @@
# 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)}
/>
)
}
}
```

124
docs/components/Text.md Normal file
View File

@@ -0,0 +1,124 @@
# Text
`Text` is component for displaying text. It supports style, basic touch
handling, and inherits typographic styles from ancestor elements. In a
divergence from React Native, components other than `Text` can be children of a
`Text` component.
The `Text` is unique relative to layout: child elements use text layout
(`inline-block`) rather than flexbox layout. This means that elements inside of
a `Text` are not rectangles, as they wrap when reaching the edge of their
container.
Unsupported React Native props:
`allowFontScaling` (ios),
`suppressHighlighting` (ios)
## Props
NOTE: `Text` will transfer all other props to the rendered HTML element.
(web) **accessibilityLabel**: string
Defines the text available to assistive technologies upon interaction with the
element. (This is implemented using `aria-label`.)
(web) **accessible**: bool = true
When `false`, the text is hidden from assistive technologies. (This is
implemented using `aria-hidden`.)
**children**: any
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`.
**onPress**: function
This function is called on press.
**style**: style
+ `backgroundColor`
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `margin`
+ `padding`
+ `textAlign`
+ `textDecoration`
+ `textTransform`
+ `whiteSpace`
+ `wordWrap`
**testID**: string
Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Text } from 'react-native-web'
const { Component, PropTypes } = React
class PrettyText extends Component {
static propTypes = {
color: PropTypes.oneOf(['white', 'gray', 'red']),
size: PropTypes.oneOf(['small', 'normal', 'large']),
weight: PropTypes.oneOf(['light', 'normal', 'bold'])
}
static defaultProps = {
color: 'gray',
size: 'normal',
weight: 'normal'
}
render() {
const { color, size, style, weight, ...other } = this.props;
return (
<Text
...other
style={{
...style,
...localStyle.color[color],
...localStyle.size[size],
...localStyle.weight[weight]
}}
/>
);
}
}
const localStyle = {
color: {
white: { color: 'white' },
gray: { color: 'gray' },
red: { color: 'red' }
},
size: {
small: { fontSize: '0.85rem', padding: '0.5rem' },
normal: { fontSize: '1rem', padding: '0.75rem' },
large: { fontSize: '1.5rem', padding: '1rem' }
},
weight: {
light: { fontWeight: '300' },
normal: { fontWeight: '400' },
bold: { fontWeight: '700' }
}
}
```

View File

@@ -0,0 +1,203 @@
# TextInput
Accessible single- and multi-line text input via a keyboard. Supports features
such as auto-complete, auto-focus, placeholder text, and event callbacks.
Note: some props are exclusive to or excluded from `multiline`.
Unsupported React Native props:
`autoCapitalize`,
`autoCorrect`,
`onEndEditing`,
`onSubmitEditing`,
`clearButtonMode` (ios),
`enablesReturnKeyAutomatically` (ios),
`returnKeyType` (ios),
`selectionState` (ios),
`textAlign` (android),
`textAlignVertical` (android),
`underlineColorAndroid` (android)
## Props
(web) **accessibilityLabel**: string
Defines the text label available to assistive technologies upon interaction
with the element. (This is implemented using `aria-label`.)
(web) **autoComplete**: bool = false
Indicates whether the value of the control can be automatically completed by the browser.
**autoFocus**: bool = false
If true, focuses the input on `componentDidMount`. Only the first form element
in a document with `autofocus` is focused.
**clearTextOnFocus**: bool = false
If `true`, clears the text field automatically when focused.
**defaultValue**: string
Provides an initial value that will change when the user starts typing. Useful
for simple use-cases where you don't want to deal with listening to events and
updating the `value` prop to keep the controlled state in sync.
**editable**: bool = true
If `false`, text is not editable (i.e., read-only).
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'url') = 'default'
Determines which keyboard to open.
(Not available when `multiline` is `true`.)
**maxLength**: number
Limits the maximum number of characters that can be entered.
(web) **maxNumberOfLines**: number = numberOfLines
Limits the maximum number of lines for a multiline `TextInput`.
(Requires `multiline` to be `true`.)
**multiline**: bool = false
If true, the text input can be multiple lines.
**numberOfLines**: number = 2
Sets the initial number of lines for a multiline `TextInput`.
(Requires `multiline` to be `true`.)
**onBlur**: function
Callback that is called when the text input is blurred.
**onChange**: function
Callback that is called when the text input's text changes.
**onChangeText**: function
Callback that is called when the text input's text changes. The text is passed
as an argument to the callback handler.
**onFocus**: function
Callback that is called when the text input is focused.
**onLayout**: function
TODO
(web) **onSelectionChange**: function
Callback that is called when the text input's selection changes. The following
object is passed as an argument to the callback handler.
```js
{
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent
}
```
**placeholder**: string
The string that will be rendered before text input has been entered.
**placeholderTextColor**: string
TODO. The text color of the placeholder string.
**secureTextEntry**: bool = false
If true, the text input obscures the text entered so that sensitive text like
passwords stay secure.
(Not available when `multiline` is `true`.)
**selectTextOnFocus**: bool = false
If `true`, all text will automatically be selected on focus.
**style**: style
[View](View.md) style
+ `color`
+ `direction`
+ `fontFamily`
+ `fontSize`
+ `fontStyle`
+ `fontWeight`
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textTransform`
**testID**: string
Used to locate this view in end-to-end tests.
**value**: string
The value to show for the text input. `TextInput` is a controlled component,
which means the native `value` will be forced to match this prop if provided.
Read about how [React form
components](https://facebook.github.io/react/docs/forms.html) work. To prevent
user edits to the value set `editable={false}`.
## Examples
```js
import React, { TextInput } from 'react-native-web'
const { Component } = React
class AppTextInput extends Component {
constructor(props, context) {
super(props, context)
this.state = { isFocused: false }
}
_onFocus(e) {
this.setState({ isFocused: true })
}
render() {
return (
<TextInput
accessibilityLabel='Write a status update'
maxNumberOfLines={5}
multiline
numberOfLines={2}
onFocus={this._onFocus.bind(this)}
placeholder={`What's happening?`}
style={
...styles.default
...(this.state.isFocused && styles.focused)
}
/>
);
}
}
const styles = {
default: {
borderColor: 'gray',
borderWidth: '0 0 2px 0'
},
focused: {
borderColor: 'blue'
}
}
```

View File

@@ -0,0 +1,104 @@
# Touchable
A wrapper for making views respond to mouse, keyboard, and touch presses. On
press in, the touchable area can display a highlight color, and the opacity of
the wrapped view can be decreased.
This component combines the various `Touchable*` components from React Native.
Unsupported React Native props:
`accessibilityComponentType` (android) use `accessibilityRole`,
`accessibilityTraits` (ios) use `accessibilityRole`,
`onHideUnderlay` use `onPressOut`,
`onShowUnderlay` use `onPressIn`,
`underlayColor` use `activeUnderlayColor`
## Props
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
with the element.
(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.
**accessible**: bool = true
When `false`, the view is hidden from screenreaders.
**activeOpacity**: number = 1
Sets the opacity of the child view when `onPressIn` is called. The opacity is
reset when `onPressOut` is called.
(web) **activeUnderlayColor**: string = 'transparent'
Sets the color of the background highlight when `onPressIn` is called. The
highlight is removed when `onPressOut` is called.
**children**: element
A single child element.
**delayLongPress**: number = 1000
Delay in ms, from `onPressIn`, before `onLongPress` is called.
**delayPressIn**: number = 0
(TODO)
Delay in ms, from the start of the touch, before `onPressIn` is called.
**delayPressOut**: number = 0
(TODO)
Delay in ms, from the release of the touch, before `onPressOut` is called.
**onLayout**: function
(TODO)
**onLongPress**: function
**onPress**: function
**onPressIn**: function
**onPressOut**: function
**style**: style
[View](View.md) style
## Examples
```js
import React, { Touchable } from 'react-native-web'
const { Component, PropTypes } = React;
class Example extends Component {
static propTypes = {
}
static defaultProps = {
}
render() {
return (
<Touchable />
)
}
}
```

190
docs/components/View.md Normal file
View File

@@ -0,0 +1,190 @@
# View
`View` is the fundamental UI building block. It is a component that supports
style, layout with flexbox, and accessibility controls. It can be nested
inside another `View` and has 0-to-many children of any type.
Unsupported React Native props:
`accessibilityComponentType` (android) use `accessibilityRole`,
`accessibilityTraits` (ios) use `accessibilityRole`,
`collapsable` (android),
`importantForAccessibility` (android),
`needsOffscreenAlphaCompositing` (android),
`onAccessibilityTap`,
`onMagicTap`,
`onMoveShouldSetResponder`,
`onResponder*`,
`onStartShouldSetResponder`,
`onStartShouldSetResponderCapture`
`removeClippedSubviews` (ios),
`renderToHardwareTextureAndroid` (android),
`shouldRasterizeIOS` (ios)
## Props
NOTE: `View` will transfer all other props to the rendered HTML element.
**accessibilityLabel**: string
Defines the text available to assistive technologies upon interaction with the
element. (This is implemented using `aria-label`.)
**accessibilityLiveRegion**: oneOf('assertive', 'off', 'polite') = 'off'
Indicates to assistive technologies whether to notify the user when the view
changes. The values of this attribute are expressed in degrees of importance.
When regions are specified as `polite` (recommended), updates take low
priority. When regions are specified as `assertive`, assistive technologies
will interrupt and immediately notify the user. (This is implemented using
[`aria-live`](http://www.w3.org/TR/wai-aria/states_and_properties#aria-live).)
(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.
**accessible**: bool = true
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)
**pointerEvents**: oneOf('auto', 'box-only', 'box-none', 'none') = 'auto'
Configure the `pointerEvents` of the view. The enhanced `pointerEvents` modes
provided are not part of the CSS spec, therefore, `pointerEvents` is excluded
from `style`.
`box-none` is the equivalent of:
```css
.box-none { pointer-events: none }
.box-none * { pointer-events: auto }
```
`box-only` is the equivalent of:
```css
.box-only { pointer-events: auto }
.box-only * { pointer-events: none }
```
**style**: style
+ `alignContent`
+ `alignItems`
+ `alignSelf`
+ `backfaceVisibility`
+ `backgroundAttachment`
+ `backgroundClip`
+ `backgroundColor`
+ `backgroundImage`
+ `backgroundOrigin`
+ `backgroundPosition`
+ `backgroundRepeat`
+ `backgroundSize`
+ `borderColor`
+ `borderRadius`
+ `borderStyle`
+ `borderWidth`
+ `bottom`
+ `boxShadow`
+ `boxSizing`
+ `cursor`
+ `flexBasis`
+ `flexDirection`
+ `flexGrow`
+ `flexShrink`
+ `flexWrap`
+ `height`
+ `justifyContent`
+ `left`
+ `margin`
+ `maxHeight`
+ `maxWidth`
+ `minHeight`
+ `minWidth`
+ `opacity`
+ `order`
+ `overflow`
+ `overflowX`
+ `overflowY`
+ `padding`
+ `position`
+ `right`
+ `top`
+ `transform`
+ `userSelect`
+ `visibility`
+ `width`
+ `zIndex`
Default:
```js
{
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
padding: 0,
position: 'relative'
};
```
(See [facebook/css-layout](https://github.com/facebook/css-layout)).
**testID**: string
Used to locate this view in end-to-end tests.
## Examples
```js
import React, { View } from 'react-native-web'
const { Component, PropTypes } = React
class Example extends Component {
render() {
return (
<View style={styles.row}>
{
['1', '2', '3', '4', '5'].map((value, i) => {
return <View children={value} key={i} style={styles.cell} />
})
}
</View>
);
}
}
const styles = {
row: {
flexDirection: 'row'
},
cell: {
flexGrow: 1
}
}
export default Example
```

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -12,8 +12,8 @@ convert inline styles to static CSS:
## Style syntax: native vs proprietary data structure
React Web SDK (in a divergence from React Native) represents style using plain
JS objects:
React Native for Web diverges from React Native by using plain JS objects for
styles:
```js
<Text style={styles.root}>...</Text>
@@ -46,19 +46,18 @@ const styles = Stylesheet.create({
## JS-to-CSS: conversion strategies
One strategy for converting styles from JS to CSS is to map style objects to
CSS rules. Another strategy is to map declarations to declarataions.
![](sdk-styling-strategy.png)
Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
files. Each new component adds new rules to the stylesheet.
React Web SDK includes a proof-of-concept for the strategy of automatically
mapping unique declarations to declarations, via a unique selector for each
declaration. This strategy results in smaller CSS files because an application
has fewer unique declarations than total declarations. Creating a new
component with no new unique declarations results in no change to the CSS file.
![](../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:
@@ -92,10 +91,9 @@ And is backed by:
The current implementation uses a precomputed CSS library of single-declaration
rules, with obfuscated selectors. This handles a signficant portion of possible
declarations. But a build-time implementation would produce more accurate CSS
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
@@ -121,5 +119,5 @@ 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 SDK's
`TextInput` component (see React Native's API).
`::placeholder` it might be necessary to reimplement it in the `TextInput`
component (see React Native's API).

View File

@@ -1,50 +0,0 @@
import React from 'react';
import { Image, Text, View } from '../dist/main';
class Example extends React.Component {
render() {
return (
<View>
<View style={styles.grid}>
{[1,2,3,4,5,6].map((item, i) => {
return (
<View key={i} style={{ ...styles.box, ...(item === 6 && styles.boxFull) }}>
<Text style={{ fontSize: '2rem' }}>{item}</Text>
</View>
);
})}
</View>
<View style={{
alignItems: 'center',
borderWidth: '1px',
justifyContent: 'center',
marginTop: '10px',
height: '200px'
}}>
<Text>This should be centered</Text>
</View>
</View>
);
}
}
const styles = {
grid: {
flexDirection: 'row',
flexWrap: 'wrap'
},
box: {
alignItems: 'center',
backgroundColor: 'lightblue',
flexGrow: 1,
justifyContent: 'center',
borderColor: 'blue',
borderWidth: '5px'
},
boxFull: {
width: '100%'
}
}
React.render(<Example />, document.getElementById('react-root'));

View File

@@ -1,6 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../dist/react-web-sdk.css">
<div id="react-root"></div>
<script src="../dist/example.js"></script>

View File

@@ -1,19 +0,0 @@
module.exports = {
entry: {
example: './example.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { cacheDirectory: true }
}
]
},
output: {
filename: 'example.js',
path: '../dist'
}
};

View File

@@ -1,37 +1,57 @@
{
"name": "react-web-sdk",
"version": "0.0.2",
"description": "UI SDK based on react-native",
"main": "dist/main.js",
"name": "react-native-web",
"version": "0.0.6",
"description": "React Native for Web",
"main": "dist/react-native-web.js",
"files": [
"dist"
],
"scripts": {
"prebuild": "rm -rf ./dist && npm install",
"build": "webpack --config webpack.config.js",
"build:watch": "npm run build -- --watch",
"build:example": "npm run build && cd example && webpack --config ./webpack.config.js",
"build:example:watch": "npm run build:example -- --watch"
"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",
"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"
},
"dependencies": {
"react": "^0.13.3"
"react": ">=0.13.3",
"react-swipeable": "^3.0.2",
"react-tappable": "^0.6.0",
"react-textarea-autosize": "^2.5.2"
},
"devDependencies": {
"autoprefixer-core": "^5.2.0",
"babel-core": "^5.5.6",
"babel-loader": "^5.1.4",
"babel-runtime": "^5.5.6",
"css-loader": "^0.15.1",
"extract-text-webpack-plugin": "^0.8.1",
"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",
"karma-webpack": "^1.7.0",
"mocha": "^2.3.0",
"node-libs-browser": "^0.5.2",
"postcss-loader": "^0.4.4",
"object-assign": "^4.0.1",
"style-loader": "^0.12.3",
"webpack": "^1.9.10"
"webpack": "^1.12.1",
"webpack-dev-server": "^1.10.1"
},
"author": "Nicolas Gallagher",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/necolas/react-web-sdk.git"
"url": "git://github.com/necolas/react-native-web.git"
}
}

View File

@@ -0,0 +1,42 @@
import React, { PropTypes } from 'react'
import restyle from './modules/restyle'
import stylePropTypes from './modules/stylePropTypes'
class CoreComponent extends React.Component {
static propTypes = {
className: PropTypes.string,
component: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string
]),
style: PropTypes.object,
testID: PropTypes.string
}
static stylePropTypes = stylePropTypes;
static defaultProps = {
className: '',
component: 'div'
}
render() {
const {
className,
component: Component,
style,
testID,
...other
} = this.props
return (
<Component
{...other}
{...restyle({ className, style })}
data-testid={testID}
/>
)
}
}
export default CoreComponent

View File

@@ -4,7 +4,7 @@ export default function prefixStyles(style) {
WebkitFlexBasis: style.flexBasis,
msFlexBasis: style.flexBasis,
...style
};
}
}
if (style.hasOwnProperty('flexGrow')) {
@@ -13,7 +13,7 @@ export default function prefixStyles(style) {
WebkitFlexGrow: style.flexGrow,
msFlexPositive: style.flexGrow,
...style
};
}
}
if (style.hasOwnProperty('flexShrink')) {
@@ -21,7 +21,7 @@ export default function prefixStyles(style) {
WebkitFlexShrink: style.flexShrink,
msFlexNegative: style.flexShrink,
...style
};
}
}
// NOTE: adding `;` to the string value prevents React from automatically
@@ -30,9 +30,9 @@ export default function prefixStyles(style) {
style = {
WebkitBoxOrdinalGroup: `${parseInt(style.order, 10) + 1};`,
WebkitOrder: `${style.order}`,
msFlexOrder: `${style.order};`,
msFlexOrder: `${style.order}`,
...style
};
}
}
if (style.hasOwnProperty('transform')) {
@@ -40,8 +40,8 @@ export default function prefixStyles(style) {
WebkitTransform: style.transform,
msTransform: style.transform,
...style
};
}
}
return style;
return style
}

View File

@@ -0,0 +1,40 @@
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

@@ -0,0 +1,97 @@
import { PropTypes } from 'react'
const numberOrString = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
])
const { string } = PropTypes
export default {
alignContent: string,
alignItems: string,
alignSelf: string,
backfaceVisibility: string,
backgroundAttachment: string,
backgroundClip: string,
backgroundColor: string,
backgroundImage: string,
backgroundOrigin: string,
backgroundPosition: string,
backgroundRepeat: string,
backgroundSize: string,
borderColor: numberOrString,
borderBottomColor: numberOrString,
borderLeftColor: numberOrString,
borderRightColor: numberOrString,
borderTopColor: numberOrString,
borderRadius: numberOrString,
borderTopLeftRadius: numberOrString,
borderTopRightRadius: numberOrString,
borderBottomLeftRadius: numberOrString,
borderBottomRightRadius: numberOrString,
borderStyle: numberOrString,
borderBottomStyle: numberOrString,
borderLeftStyle: numberOrString,
borderRightStyle: numberOrString,
borderTopStyle: numberOrString,
borderWidth: numberOrString,
borderBottomWidth: numberOrString,
borderLeftWidth: numberOrString,
borderRightWidth: numberOrString,
borderTopWidth: numberOrString,
bottom: numberOrString,
boxSizing: string,
clear: string,
color: string,
cursor: string,
direction: string,
display: string,
flexBasis: string,
flexDirection: string,
flexGrow: numberOrString,
flexShrink: numberOrString,
flexWrap: string,
float: string,
font: string,
fontFamily: string,
fontSize: numberOrString,
fontStyle: string,
fontWeight: string,
height: numberOrString,
justifyContent: string,
left: numberOrString,
letterSpacing: string,
lineHeight: numberOrString,
margin: numberOrString,
marginBottom: numberOrString,
marginLeft: numberOrString,
marginRight: numberOrString,
marginTop: numberOrString,
maxHeight: numberOrString,
maxWidth: numberOrString,
minHeight: numberOrString,
minWidth: numberOrString,
opacity: numberOrString,
order: numberOrString,
overflow: string,
overflowX: string,
overflowY: string,
padding: numberOrString,
paddingBottom: numberOrString,
paddingLeft: numberOrString,
paddingRight: numberOrString,
paddingTop: numberOrString,
position: string,
right: numberOrString,
textAlign: string,
textDecoration: string,
textTransform: string,
top: numberOrString,
userSelect: string,
visibility: string,
whiteSpace: string,
width: numberOrString,
wordWrap: string,
zIndex: numberOrString
}

View File

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

View File

@@ -0,0 +1,217 @@
/* global window */
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import ImageStylePropTypes from './ImageStylePropTypes'
import React, { PropTypes } from 'react'
import View from '../View'
const STATUS_ERRORED = 'ERRORED'
const STATUS_LOADED = 'LOADED'
const STATUS_LOADING = 'LOADING'
const STATUS_PENDING = 'PENDING'
const STATUS_IDLE = 'IDLE'
const imageStyleKeys = Object.keys(ImageStylePropTypes)
const styles = {
initial: {
alignSelf: 'flex-start',
backgroundColor: 'lightgray',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: '100% 100%'
},
img: {
borderWidth: 0,
height: 'auto',
maxHeight: '100%',
maxWidth: '100%',
opacity: 0
},
children: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0
},
resizeMode: {
contain: {
backgroundSize: 'contain'
},
cover: {
backgroundSize: 'cover'
},
none: {
backgroundSize: 'auto'
},
stretch: {
backgroundSize: '100% 100%'
}
}
}
class Image extends React.Component {
constructor(props, context) {
super(props, context)
// state
this.state = { status: props.source.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,
children: PropTypes.any,
defaultSource: PropTypes.object,
onError: PropTypes.func,
onLoad: PropTypes.func,
onLoadEnd: PropTypes.func,
onLoadStart: PropTypes.func,
resizeMode: PropTypes.oneOf(['contain', 'cover', 'none', 'stretch']),
source: PropTypes.object,
style: PropTypes.shape(ImageStylePropTypes),
testID: CoreComponent.propTypes.testID
}
static stylePropTypes = ImageStylePropTypes
static defaultProps = {
accessible: true,
defaultSource: {},
resizeMode: 'stretch',
source: {},
style: styles.initial
}
_createImageLoader() {
const { source } = this.props
this._destroyImageLoader()
this.image = new window.Image()
this.image.onerror = this._onError
this.image.onload = this._onLoad
this.image.src = source.uri
this._onLoadStart()
}
_destroyImageLoader() {
if (this.image) {
this.image.onload = null
this.image.onerror = null
this.image = null
}
}
_onError(e) {
const { onError } = this.props
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_ERRORED })
this._onLoadEnd()
if (onError) onError(event)
}
_onLoad(e) {
const { onLoad } = this.props
const event = { nativeEvent: e }
this._destroyImageLoader()
this.setState({ status: STATUS_LOADED })
this._onLoadEnd()
if (onLoad) onLoad(event)
}
_onLoadEnd() {
const { onLoadEnd } = this.props
if (onLoadEnd) onLoadEnd()
}
_onLoadStart() {
const { onLoadStart } = this.props
this.setState({ status: STATUS_LOADING })
if (onLoadStart) onLoadStart()
}
componentDidMount() {
if (this.state.status === STATUS_PENDING) {
this._createImageLoader()
}
}
componentDidUpdate() {
if (this.state.status === STATUS_PENDING && !this.image) {
this._createImageLoader()
}
}
componentWillReceiveProps(nextProps) {
if (this.props.source.uri !== nextProps.source.uri) {
this.setState({
status: nextProps.source.uri ? STATUS_PENDING : STATUS_IDLE
})
}
}
componentWillUnmount() {
this._destroyImageLoader()
}
render() {
const {
accessibilityLabel,
accessible,
children,
defaultSource,
resizeMode,
source,
style,
testID
} = this.props
const isLoaded = this.state.status === STATUS_LOADED
const defaultImage = defaultSource.uri || null
const displayImage = !isLoaded ? defaultImage : source.uri
const resolvedStyle = pickProps(style, imageStyleKeys)
const backgroundImage = displayImage ? `url("${displayImage}")` : null
/**
* Image is a non-stretching View. The image is displayed as a background
* image to support `resizeMode`. The HTML image is hidden but used to
* provide the correct responsive image dimensions, and to support the
* image context menu. Child content is rendered into an element absolutely
* positioned over the image.
*/
return (
<View
_className='Image'
accessibilityLabel={accessibilityLabel}
accessibilityRole='img'
accessible={accessible}
component='div'
style={{
...(styles.initial),
...resolvedStyle,
...(backgroundImage && { backgroundImage }),
...(styles.resizeMode[resizeMode])
}}
testID={testID}
>
<img
src={displayImage}
style={styles.img}
/>
{children ? (
<View children={children} pointerEvents='box-none' style={styles.children} />
) : null}
</View>
)
}
}
export default Image

View File

@@ -0,0 +1,73 @@
import { assertProps, render, renderToDOM } from '../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import Image from '.'
suite('Image', () => {
test('default accessibility', () => {
const dom = renderToDOM(<Image />)
assert.equal(dom.getAttribute('role'), 'img')
})
test('prop "accessibilityLabel"', () => {
assertProps.accessibilityLabel(Image)
})
test('prop "accessible"', () => {
assertProps.accessible(Image)
})
test.skip('prop "children"', () => { })
test('prop "defaultSource"', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' }
const dom = renderToDOM(<Image defaultSource={defaultSource} />)
const backgroundImage = dom.style.backgroundImage
assert(backgroundImage.indexOf(defaultSource.uri) > -1)
})
test('prop "onError"', function (done) {
this.timeout(5000)
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)
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.skip('prop "onLoadStart"', () => { })
test.skip('prop "resizeMode"', () => { })
test.skip('prop "source"', () => { })
test('prop "style"', () => {
assertProps.style(Image)
})
test('prop "testID"', () => {
assertProps.testID(Image)
})
})

View File

@@ -0,0 +1,20 @@
import React, { PropTypes } from 'react'
import ScrollView from '../ScrollView'
class ListView extends React.Component {
static propTypes = {
children: PropTypes.any
}
static defaultProps = {
className: ''
}
render() {
return (
<ScrollView {...this.props} />
)
}
}
export default ListView

View File

@@ -0,0 +1,18 @@
/*
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,20 @@
import React, { PropTypes } from 'react'
import View from '../View'
class ScrollView extends React.Component {
static propTypes = {
children: PropTypes.any
}
static defaultProps = {
className: ''
}
render() {
return (
<View {...this.props} />
)
}
}
export default ScrollView

View File

@@ -0,0 +1,13 @@
/*
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

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

View File

@@ -0,0 +1,31 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
export default {
...pickProps(CoreComponent.stylePropTypes, [
'backgroundColor',
'color',
'direction',
'font',
'fontFamily',
'fontSize',
'fontWeight',
'letterSpacing',
'lineHeight',
'margin',
'marginBottom',
'marginLeft',
'marginRight',
'marginTop',
'padding',
'paddingBottom',
'paddingLeft',
'paddingRight',
'paddingTop',
'textAlign',
'textDecoration',
'textTransform',
'whiteSpace',
'wordWrap'
])
}

View File

@@ -0,0 +1,88 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import TextStylePropTypes from './TextStylePropTypes'
const textStyleKeys = Object.keys(TextStylePropTypes)
const styles = {
initial: {
color: 'inherit',
display: 'inline-block',
font: 'inherit',
margin: 0,
padding: 0,
wordWrap: 'break-word'
},
singleLineStyle: {
maxWidth: '100%',
overflow: 'hidden',
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,
children: PropTypes.any,
component: CoreComponent.propTypes.component,
numberOfLines: PropTypes.number,
onPress: PropTypes.func,
style: PropTypes.shape(TextStylePropTypes),
testID: CoreComponent.propTypes.testID
}
static stylePropTypes = TextStylePropTypes
static defaultProps = {
_className: '',
accessible: true,
component: 'span',
style: styles.initial
}
_onPress(e) {
if (this.props.onPress) this.props.onPress(e)
}
render() {
const {
_className,
accessibilityLabel,
accessible,
children,
component,
numberOfLines,
onPress,
style,
testID,
...other
} = this.props
const className = `Text ${_className}`.trim()
const resolvedStyle = pickProps(style, textStyleKeys)
return (
<CoreComponent
{...other}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
children={children}
className={className}
component={component}
onClick={this._onPress.bind(this)}
style={{
...styles.initial,
...resolvedStyle,
...(numberOfLines === 1 && styles.singleLineStyle)
}}
testID={testID}
/>
)
}
}
export default Text

View File

@@ -0,0 +1,49 @@
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

@@ -0,0 +1,20 @@
import { pickProps } from '../../modules/filterObjectProps'
import View from '../View'
import CoreComponent from '../CoreComponent'
export default {
...(View.stylePropTypes),
...pickProps(CoreComponent.stylePropTypes, [
'color',
'direction',
'fontFamily',
'fontSize',
'fontStyle',
'fontWeight',
'letterSpacing',
'lineHeight',
'textAlign',
'textDecoration',
'textTransform'
])
}

View File

@@ -0,0 +1,177 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputStylePropTypes from './TextInputStylePropTypes'
const textInputStyleKeys = Object.keys(TextInputStylePropTypes)
const styles = {
initial: {
appearance: 'none',
backgroundColor: 'transparent',
borderColor: 'black',
borderWidth: '1px',
boxSizing: 'border-box',
color: 'inherit',
font: 'inherit',
padding: 0
}
}
class TextInput extends React.Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
autoComplete: PropTypes.bool,
autoFocus: PropTypes.bool,
clearTextOnFocus: PropTypes.bool,
defaultValue: PropTypes.string,
editable: PropTypes.bool,
keyboardType: PropTypes.oneOf(['default', 'email-address', 'numeric', 'phone-pad', 'url']),
maxLength: PropTypes.number,
maxNumberOfLines: PropTypes.number,
multiline: PropTypes.bool,
numberOfLines: PropTypes.number,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onChangeText: PropTypes.func,
onFocus: PropTypes.func,
onSelectionChange: PropTypes.func,
placeholder: PropTypes.string,
placeholderTextColor: PropTypes.string,
secureTextEntry: PropTypes.bool,
selectTextOnFocus: PropTypes.bool,
style: PropTypes.shape(TextInputStylePropTypes),
testID: CoreComponent.propTypes.testID,
value: PropTypes.string
}
static stylePropTypes = TextInputStylePropTypes
static defaultProps = {
editable: true,
keyboardType: 'default',
multiline: false,
numberOfLines: 2,
secureTextEntry: false,
style: styles.initial
}
_onBlur(e) {
const { onBlur } = this.props
if (onBlur) onBlur(e)
}
_onChange(e) {
const { onChange, onChangeText } = this.props
if (onChangeText) onChangeText(e.target.value)
if (onChange) onChange(e)
}
_onFocus(e) {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = React.findDOMNode(this)
if (clearTextOnFocus) node.value = ''
if (selectTextOnFocus) node.select()
if (onFocus) onFocus(e)
}
_onSelectionChange(e) {
const { onSelectionChange } = this.props
const { selectionDirection, selectionEnd, selectionStart } = e.target
const event = {
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent: e.nativeEvent
}
if (onSelectionChange) onSelectionChange(event)
}
render() {
const {
accessibilityLabel,
autoComplete,
autoFocus,
defaultValue,
editable,
keyboardType,
maxLength,
maxNumberOfLines,
multiline,
numberOfLines,
onBlur,
onChange,
onChangeText,
onSelectionChange,
placeholder,
secureTextEntry,
style,
testID,
value
} = this.props
const resolvedStyle = pickProps(style, textInputStyleKeys)
let type
switch (keyboardType) {
case 'email-address':
type = 'email'
break
case 'numeric':
type = 'number'
break
case 'phone-pad':
type = 'tel'
break
case 'url':
type = 'url'
break
}
if (secureTextEntry) {
type = 'password'
}
const propsCommon = {
'aria-label': accessibilityLabel,
autoComplete: autoComplete && 'on',
autoFocus,
className: 'TextInput',
defaultValue,
maxLength,
onBlur: onBlur && this._onBlur.bind(this),
onChange: (onChange || onChangeText) && this._onChange.bind(this),
onFocus: this._onFocus.bind(this),
onSelect: onSelectionChange && this._onSelectionChange.bind(this),
placeholder,
readOnly: !editable,
style: {
...styles.initial,
...resolvedStyle
},
testID,
value
}
const propsMultiline = {
...propsCommon,
component: TextareaAutosize,
maxRows: maxNumberOfLines || numberOfLines,
minRows: numberOfLines
}
const propsSingleline = {
...propsCommon,
component: 'input',
type
}
return (multiline
? <CoreComponent {...propsMultiline} />
: <CoreComponent {...propsSingleline} />
)
}
}
export default TextInput

View File

@@ -0,0 +1,222 @@
import * as utils from '../../modules/specHelpers'
import assert from 'assert'
import React from 'react/addons'
import TextInput from '.'
const ReactTestUtils = React.addons.TestUtils
suite('TextInput', () => {
test('prop "accessibilityLabel"', () => {
utils.assertProps.accessibilityLabel(TextInput)
})
test('prop "autoComplete"', () => {
// off
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('autocomplete'), undefined)
// on
dom = utils.renderToDOM(<TextInput autoComplete />)
assert.equal(dom.getAttribute('autocomplete'), 'on')
})
test('prop "autoFocus"', () => {
// false
let dom = utils.renderToDOM(<TextInput />)
assert.deepEqual(document.activeElement, document.body)
// true
dom = utils.renderToDOM(<TextInput autoFocus />)
assert.deepEqual(document.activeElement, dom)
})
test('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, '')
})
})
test('prop "defaultValue"', () => {
const defaultValue = 'defaultValue'
const result = utils.shallowRender(<TextInput defaultValue={defaultValue} />)
assert.equal(result.props.defaultValue, defaultValue)
})
test('prop "editable"', () => {
// true
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('readonly'), undefined)
// false
dom = utils.renderToDOM(<TextInput editable={false} />)
assert.equal(dom.getAttribute('readonly'), '')
})
test('prop "keyboardType"', () => {
// default
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('type'), undefined)
dom = utils.renderToDOM(<TextInput keyboardType='default' />)
assert.equal(dom.getAttribute('type'), undefined)
// email-address
dom = utils.renderToDOM(<TextInput keyboardType='email-address' />)
assert.equal(dom.getAttribute('type'), 'email')
// numeric
dom = utils.renderToDOM(<TextInput keyboardType='numeric' />)
assert.equal(dom.getAttribute('type'), 'number')
// phone-pad
dom = utils.renderToDOM(<TextInput keyboardType='phone-pad' />)
assert.equal(dom.getAttribute('type'), 'tel')
// url
dom = utils.renderToDOM(<TextInput keyboardType='url' />)
assert.equal(dom.getAttribute('type'), 'url')
})
test('prop "maxLength"', () => {
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.getAttribute('maxlength'), undefined)
dom = utils.renderToDOM(<TextInput maxLength={10} />)
assert.equal(dom.getAttribute('maxlength'), '10')
})
test('prop "maxNumberOfLines"', () => {
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
const value = (() => {
let str = ''
while (str.length < 100) str += 'x'
return str
}())
let dom = utils.renderAndInject(
<TextInput
maxNumberOfLines={3}
multiline
style={style}
value={value}
/>
)
const height = dom.getBoundingClientRect().height
// need a range because of cross-browser differences
assert.ok(height >= 60, height)
assert.ok(height <= 66, height)
})
test('prop "multiline"', () => {
// false
let dom = utils.renderToDOM(<TextInput />)
assert.equal(dom.tagName, 'INPUT')
// true
dom = utils.renderToDOM(<TextInput multiline />)
assert.equal(dom.tagName, 'TEXTAREA')
})
test('prop "numberOfLines"', () => {
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
// missing multiline
let dom = utils.renderToDOM(<TextInput numberOfLines={2} />)
assert.equal(dom.tagName, 'INPUT')
// with multiline
dom = utils.renderAndInject(<TextInput multiline numberOfLines={2} style={style} />)
assert.equal(dom.tagName, 'TEXTAREA')
const height = dom.getBoundingClientRect().height
// need a range because of cross-browser differences
assert.ok(height >= 40)
assert.ok(height <= 45)
})
test('prop "onBlur"', (done) => {
const input = utils.renderToDOM(<TextInput onBlur={onBlur} />)
ReactTestUtils.Simulate.blur(input)
function onBlur(e) {
assert.ok(e)
done()
}
})
test('prop "onChange"', (done) => {
const input = utils.renderToDOM(<TextInput onChange={onChange} />)
ReactTestUtils.Simulate.change(input)
function onChange(e) {
assert.ok(e)
done()
}
})
test('prop "onChangeText"', (done) => {
const newText = 'newText'
const input = utils.renderToDOM(<TextInput onChangeText={onChangeText} />)
ReactTestUtils.Simulate.change(input, { target: { value: newText } })
function onChangeText(text) {
assert.equal(text, newText)
done()
}
})
test('prop "onFocus"', (done) => {
const input = utils.renderToDOM(<TextInput onFocus={onFocus} />)
ReactTestUtils.Simulate.focus(input)
function onFocus(e) {
assert.ok(e)
done()
}
})
test.skip('prop "onLayout"', () => {})
test('prop "onSelectionChange"', (done) => {
const input = utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />)
ReactTestUtils.Simulate.select(input, { target: { selectionStart: 0, selectionEnd: 3 } })
function onSelectionChange(e) {
assert.equal(e.selectionEnd, 3)
assert.equal(e.selectionStart, 0)
done()
}
})
test.skip('prop "placeholder"', () => {})
test.skip('prop "placeholderTextColor"', () => {})
test('prop "secureTextEntry"', () => {
let dom = utils.renderToDOM(<TextInput secureTextEntry />)
assert.equal(dom.getAttribute('type'), 'password')
// ignored for multiline
dom = utils.renderToDOM(<TextInput multiline secureTextEntry />)
assert.equal(dom.getAttribute('type'), undefined)
})
test('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)
})
})
test('prop "style"', () => {
utils.assertProps.style(TextInput)
})
test('prop "testID"', () => {
utils.assertProps.testID(TextInput)
})
test('prop "value"', () => {
const value = 'value'
const result = utils.shallowRender(<TextInput value={value} />)
assert.equal(result.props.value, value)
})
})

View File

@@ -0,0 +1,134 @@
import React, { PropTypes } from 'react'
import Tappable from 'react-tappable'
import View from '../View'
const styles = {
initial: {
...View.defaultProps.style,
cursor: 'pointer',
userSelect: undefined
}
}
class Touchable extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
isActive: false
}
this._onLongPress = this._onLongPress.bind(this)
this._onPress = this._onPress.bind(this)
this._onPressIn = this._onPressIn.bind(this)
this._onPressOut = this._onPressOut.bind(this)
}
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
activeOpacity: PropTypes.number,
activeUnderlayColor: PropTypes.string,
children: PropTypes.element,
delayLongPress: PropTypes.number,
delayPressIn: PropTypes.number,
delayPressOut: PropTypes.number,
onLongPress: PropTypes.func,
onPress: PropTypes.func,
onPressIn: PropTypes.func,
onPressOut: PropTypes.func,
style: View.propTypes.style
}
static defaultProps = {
accessibilityRole: 'button',
activeOpacity: 1,
activeUnderlayColor: 'transparent',
component: 'div',
delayLongPress: 1000,
delayPressIn: 0,
delayPressOut: 0,
style: styles.initial
}
_getChildren() {
const { activeOpacity, children } = this.props
return React.cloneElement(React.Children.only(children), {
style: {
...children.props.style,
...(this.state.isActive && { opacity: activeOpacity })
}
})
}
_onKeyEnter(e, callback) {
var ENTER = 13
if (e.keyCode === ENTER) {
callback(e)
}
}
_onLongPress(e) {
if (this.props.onLongPress) this.props.onLongPress(e)
}
_onPress(e) {
if (this.props.onPress) this.props.onPress(e)
}
_onPressIn(e) {
this.setState({ isActive: true })
if (this.props.onPressIn) this.props.onPressIn(e)
}
_onPressOut(e) {
this.setState({ isActive: false })
if (this.props.onPressOut) this.props.onPressOut(e)
}
render() {
const {
accessibilityLabel,
accessibilityRole,
accessible,
activeUnderlayColor,
delayLongPress,
style
} = this.props
/**
* Creates a wrapping element that can receive beyboard focus. The
* highlight is applied as a background color on this wrapper. The opacity
* is set on the child element, allowing it to have its own background
* color.
*/
return (
<Tappable
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessible={accessible}
children={this._getChildren()}
component={View}
onKeyDown={(e) => { this._onKeyEnter(e, this._onPressIn) }}
onKeyPress={this._onPress}
onKeyUp={(e) => { this._onKeyEnter(e, this._onPressOut) }}
onMouseDown={this._onPressIn}
onMouseUp={this._onPressOut}
onPress={this._onLongPress}
onTap={this._onPress}
onTouchEnd={this._onPressOut}
onTouchStart={this._onPressIn}
pressDelay={delayLongPress}
pressMoveThreshold={5}
style={{
...styles.initial,
...style,
backgroundColor: this.state.isActive ? activeUnderlayColor : style.backgroundColor
}}
tabIndex='0'
/>
)
}
}
export default Touchable

View File

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,86 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
export default {
...pickProps(CoreComponent.stylePropTypes, [
'alignContent',
'alignItems',
'alignSelf',
'backfaceVisibility',
// background
'backgroundAttachment',
'backgroundClip',
'backgroundColor',
'backgroundImage',
'backgroundPosition',
'backgroundOrigin',
'backgroundRepeat',
'backgroundSize',
// border-color
'borderColor',
'borderTopColor',
'borderRightColor',
'borderBottomColor',
'borderLeftColor',
// border-radius
'borderRadius',
'borderTopLeftRadius',
'borderTopRightRadius',
'borderBottomLeftRadius',
'borderBottomRightRadius',
// border style
'borderStyle',
'borderBottomStyle',
'borderLeftStyle',
'borderRightStyle',
'borderTopStyle',
// border width
'borderWidth',
'borderBottomWidth',
'borderLeftWidth',
'borderRightWidth',
'borderTopWidth',
'bottom',
'boxShadow',
'boxSizing',
'cursor',
'flexBasis',
'flexDirection',
'flexGrow',
'flexShrink',
'flexWrap',
'height',
'justifyContent',
'left',
// margin
'margin',
'marginBottom',
'marginLeft',
'marginRight',
'marginTop',
// max/min
'maxHeight',
'maxWidth',
'minHeight',
'minWidth',
'opacity',
'order',
'overflow',
'overflowX',
'overflowY',
// padding
'padding',
'paddingBottom',
'paddingLeft',
'paddingRight',
'paddingTop',
'position',
'right',
'top',
'transform',
'userSelect',
'visibility',
'width',
'zIndex'
])
}

View File

@@ -0,0 +1,90 @@
import { pickProps } from '../../modules/filterObjectProps'
import CoreComponent from '../CoreComponent'
import React, { PropTypes } from 'react'
import ViewStylePropTypes from './ViewStylePropTypes'
const viewStyleKeys = Object.keys(ViewStylePropTypes)
const styles = {
// https://github.com/facebook/css-layout#default-values
initial: {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
listStyle: 'none',
margin: 0,
padding: 0,
position: 'relative',
// 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,
children: PropTypes.any,
component: CoreComponent.propTypes.component,
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
style: PropTypes.shape(ViewStylePropTypes),
testID: CoreComponent.propTypes.testID
}
static stylePropTypes = ViewStylePropTypes
static defaultProps = {
_className: '',
accessible: true,
component: 'div',
style: styles.initial
}
render() {
const {
_className,
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible,
pointerEvents,
style,
testID,
...other
} = this.props
const className = `View ${_className}`.trim()
const pointerEventsStyle = pointerEvents && { pointerEvents }
const resolvedStyle = pickProps(style, viewStyleKeys)
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}
/>
)
}
}
export default View

View File

@@ -0,0 +1,46 @@
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)
})
})

241
src/example.js Normal file
View File

@@ -0,0 +1,241 @@
import React, { Image, Swipeable, Text, TextInput, Touchable, View } from '.'
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 {
static propTypes = {
style: View.propTypes.style
}
render() {
return (
<View accessibilityRole='main' style={styles.root}>
<Heading level='1' size='xlarge'>React Native Web</Heading>
<Text>React Native Web takes the core components from <Text
component='a' 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,
scroll views from which more complex components and apps can be
constructed.</Text>
<Heading level='2' size='large'>Image</Heading>
<Image
accessibilityLabel='accessible image'
children={<Text>Inner content</Text>}
defaultSource={{
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wkGESkdPWMDggAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAD5UlEQVR42u3UMQ0AAAgEMcC/x7eCCgaSVsIN10kK4IORADAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAkAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAALi04UQW9HF910gAAAABJRU5ErkJggg=='
}}
onError={(e) => { console.log('Image.onError', e) }}
onLoad={(e) => { console.log('Image.onLoad', e) }}
onLoadEnd={() => { console.log('Image.onLoadEnd') }}
onLoadStart={() => { console.log('Image.onLoadStart') }}
resizeMode={'contain'}
source={{
height: 400,
uri: 'http://facebook.github.io/react/img/logo_og.png',
width: 400
}}
style={{
borderWidth: '5px'
}}
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>
<Text
onPress={(e) => { console.log('Text.onPress', e) }}
testID={'Example.text'}
>
PRESS ME.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
hendrerit consequat. Aliquam lorem quam, elementum eget ex nec,
ultrices porttitor nibh. Nulla pellentesque urna leo, a aliquet elit
rhoncus a. Aenean ultricies, nunc a interdum dictum, dui odio
scelerisque mauris, a fringilla elit ligula vel sem. Sed vel aliquet
ipsum, sed rhoncus velit. Vivamus commodo pretium libero id placerat.
</Text>
<Text numberOfLines={1}>
TRUNCATED after 1 line.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
hendrerit consequat.
</Text>
<Heading level='2' size='large'>TextInput</Heading>
<TextInput
keyboardType='default'
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
onChange={(e) => { console.log('TextInput.onChange', e) }}
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
/>
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' />
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput keyboardType='url' />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
multiline
numberOfLines={5}
/>
<Heading level='2' size='large'>Touchable</Heading>
<Touchable
accessibilityLabel={'Touchable element'}
activeHighlight='lightblue'
activeOpacity={0.8}
onLongPress={(e) => { console.log('Touchable.onLongPress', e) }}
onPress={(e) => { console.log('Touchable.onPress', e) }}
onPressIn={(e) => { console.log('Touchable.onPressIn', e) }}
onPressOut={(e) => { console.log('Touchable.onPressOut', e) }}
>
<View style={styles.touchableArea}>
<Text>Touchable area (press, long press)</Text>
</View>
</Touchable>
<Heading level='2' size='large'>View</Heading>
<Heading level='3'>Default layout</Heading>
<View>
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
return (
<View key={i} style={styles.box}>
<Text>{item}</Text>
</View>
)
})}
</View>
<Heading level='3'>Row layout</Heading>
<View style={styles.row}>
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
return (
<View key={i} style={styles.box}>
<Text>{item}</Text>
</View>
)
})}
</View>
<Heading level='3'>pointerEvents</Heading>
<View style={styles.row}>
{['box-none', 'box-only', 'none'].map((value, i) => {
return (
<View
children={value}
component='a'
href='https://google.com'
key={i}
pointerEvents={value}
style={styles.pointerEventsBox}
/>
)
})}
</View>
</View>
)
}
}
const styles = {
root: {
maxWidth: '600px',
margin: '0 auto'
},
row: {
flexDirection: 'row',
flexWrap: 'wrap'
},
box: {
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
borderWidth: '1px'
},
boxFull: {
width: '100%'
},
pointerEventsBox: {
alignItems: 'center',
borderWidth: '1px',
flexGrow: '1',
height: '100px',
justifyContent: 'center'
},
touchableArea: {
alignItems: 'center',
borderWidth: 1,
height: '200px',
justifyContent: 'center'
}
}
React.render(<Example />, document.getElementById('react-root'))

8
src/index.html Normal file
View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>React Native for Web</title>
<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>
<div id="react-root"></div>
<script src="/example.js"></script>

View File

@@ -1,17 +1,24 @@
import { getOtherProps, omitProps, pickProps } from './modules/filterObjectProps';
import WebStyleComponent from './modules/WebStyleComponent';
import StylePropTypes from './modules/StylePropTypes';
import Image from './modules/Image';
import Text from './modules/Text';
import View from './modules/View';
import React from 'react'
export default {
getOtherProps,
omitProps,
pickProps,
StylePropTypes,
WebStyleComponent,
// 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'
import View from './components/View'
export default React
export {
Image,
ListView,
ScrollView,
Swipeable,
Text,
TextInput,
Touchable,
View
};
}

View File

@@ -1,61 +0,0 @@
import { pickProps } from '../filterObjectProps';
import StylePropTypes from '../StylePropTypes';
import React, { PropTypes } from 'react';
import WebStyleComponent from '../WebStyleComponent';
const ImageStyleDefaultProps = {
backgroundColor: 'lightGray',
borderWidth: 0,
maxWidth: '100%'
};
const ImageStylePropTypes = {
...StylePropTypes.BorderThemePropTypes,
...StylePropTypes.LayoutPropTypes,
backgroundColor: PropTypes.string,
opacity: PropTypes.string
};
class Image extends React.Component {
static _getPropTypes() {
return {
alt: PropTypes.string,
async: PropTypes.bool,
className: PropTypes.string,
src: PropTypes.string,
style: PropTypes.shape(ImageStylePropTypes)
};
}
static _getDefaultProps() {
return {
async: true,
className: '',
src: 'data:image/gif;base64,' +
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
style: {}
};
}
render() {
const { alt, className, src, style, ...other } = this.props;
const filteredStyle = pickProps(style, Object.keys(ImageStylePropTypes));
const mergedStyle = { ...ImageStyleDefaultProps, ...filteredStyle };
return (
<WebStyleComponent
{...other}
alt={alt}
className={`Image ${className}`}
element="img"
src={src}
style={mergedStyle}
/>
);
}
}
Image.propTypes = Image._getPropTypes();
Image.defaultProps = Image._getDefaultProps();
export default Image;

View File

@@ -1,9 +0,0 @@
import {PropTypes} from 'react';
export default {
backgroundColor: PropTypes.string,
backgroundImage: PropTypes.string,
backgroundPosition: PropTypes.string,
backgroundRepeat: PropTypes.string,
backgroundSize: PropTypes.string
};

View File

@@ -1,23 +0,0 @@
import {PropTypes} from 'react';
const numberOrString = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]);
export default {
// border-color
borderColor: PropTypes.string,
borderTopColor: PropTypes.string,
borderRightColor: PropTypes.string,
borderBottomColor: PropTypes.string,
borderLeftColor: PropTypes.string,
// border-style
borderStyle: PropTypes.string,
// border-radius
borderRadius: numberOrString,
borderTopLeftRadius: numberOrString,
borderTopRightRadius: numberOrString,
borderBottomLeftRadius: numberOrString,
borderBottomRightRadius: numberOrString
};

View File

@@ -1,42 +0,0 @@
import {PropTypes} from 'react';
const numberOrString = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]);
export default {
boxSizing: PropTypes.oneOf([
'border-box',
'content-box'
]),
// display
display: PropTypes.oneOf([
'block',
'flex',
'inline',
'inline-block',
'inline-flex'
]),
// dimensions
height: numberOrString,
width: numberOrString,
// border width
borderWidth: numberOrString,
borderTopWidth: numberOrString,
borderRightWidth: numberOrString,
borderBottomWidth: numberOrString,
borderLeftWidth: numberOrString,
// margin
margin: numberOrString,
marginTop: numberOrString,
marginBottom: numberOrString,
marginLeft: numberOrString,
marginRight: numberOrString,
// padding
padding: numberOrString,
paddingTop: numberOrString,
paddingBottom: numberOrString,
paddingLeft: numberOrString,
paddingRight: numberOrString
};

View File

@@ -1,49 +0,0 @@
import {PropTypes} from 'react';
export default {
alignContent: PropTypes.oneOf([
'center',
'flex-end',
'flex-start',
'stretch',
'space-around',
'space-between'
]),
alignItems: PropTypes.oneOf([
'baseline',
'center',
'flex-end',
'flex-start',
'stretch'
]),
alignSelf: PropTypes.oneOf([
'auto',
'baseline',
'center',
'flex-end',
'flex-start',
'stretch'
]),
flexBasis: PropTypes.string,
flexDirection: PropTypes.oneOf([
'column',
'column-reverse',
'row',
'row-reverse'
]),
flexGrow: PropTypes.number,
flexShrink: PropTypes.number,
flexWrap: PropTypes.oneOf([
'nowrap',
'wrap',
'wrap-reverse'
]),
justifyContent: PropTypes.oneOf([
'center',
'flex-end',
'flex-start',
'space-around',
'space-between'
]),
order: PropTypes.number
};

View File

@@ -1,9 +0,0 @@
import BoxModel from './BoxModel';
import Flexbox from './Flexbox';
import Position from './Position';
export default {
...BoxModel,
...Flexbox,
...Position
};

View File

@@ -1,19 +0,0 @@
import {PropTypes} from 'react';
const numberOrString = PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]);
export default {
position: PropTypes.oneOf([
'absolute',
'fixed',
'relative'
]),
bottom: numberOrString,
left: numberOrString,
right: numberOrString,
top: numberOrString,
zIndex: PropTypes.number
};

View File

@@ -1,30 +0,0 @@
import {PropTypes} from 'react';
export default {
direction: PropTypes.oneOf([
'auto', 'ltr', 'rtl'
]),
fontFamily: PropTypes.string,
fontSize: PropTypes.string,
fontWeight: PropTypes.oneOf([
'100', '200', '300', '400', '500', '600', '700', '800', '900',
'bold', 'normal'
]),
fontStyle: PropTypes.oneOf([
'normal', 'italic'
]),
letterSpacing: PropTypes.string,
lineHeight: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
textAlign: PropTypes.oneOf([
'auto', 'left', 'right', 'center'
]),
textDecoration: PropTypes.oneOf([
'none', 'underline'
]),
textTransform: PropTypes.oneOf([
'capitalize', 'lowercase', 'none', 'uppercase'
]),
wordWrap: PropTypes.oneOf([
'break-word', 'normal'
])
};

View File

@@ -1,13 +0,0 @@
import BackgroundPropTypes from './Background';
import BorderThemePropTypes from './BorderTheme';
import FlexboxPropTypes from './Flexbox';
import LayoutPropTypes from './Layout';
import TypographicPropTypes from './Typographic';
export default {
BackgroundPropTypes,
BorderThemePropTypes,
FlexboxPropTypes,
LayoutPropTypes,
TypographicPropTypes
};

View File

@@ -1,65 +0,0 @@
import { pickProps } from '../filterObjectProps';
import { ViewStylePropTypes } from '../View';
import StylePropTypes from '../StylePropTypes';
import React, { PropTypes } from 'react';
import WebStyleComponent from '../WebStyleComponent';
const TextStyleDefaultProps = {
alignItems: 'stretch', /* 1 */
borderWidth: '0',
borderStyle: 'solid',
boxSizing: 'border-box', /* 2 */
display: 'flex', /* 3 */
flexBasis: 'auto', /* 1 */
flexDirection: 'column', /* 1 */
flexShrink: 0, /* 1 */
listStyle: 'none',
margin: '0',
padding: '0',
position: 'relative' /* 4 */
};
const TextStylePropTypes = {
...ViewStylePropTypes,
...StylePropTypes.TypographicPropTypes
};
class Text extends React.Component {
static _getPropTypes() {
return {
className: PropTypes.string,
element: PropTypes.oneOfType([
PropTypes.string, PropTypes.func
]),
style: PropTypes.shape(TextStylePropTypes)
};
}
static _getDefaultProps() {
return {
className: '',
element: 'div',
style: {}
};
}
render() {
const { className, style, ...other } = this.props;
const filteredStyle = pickProps(style, Object.keys(TextStylePropTypes));
const mergedStyle = { ...TextStyleDefaultProps, ...filteredStyle };
return (
<WebStyleComponent
{...other}
className={`Text ${className}`}
style={mergedStyle}
/>
);
}
}
Text.propTypes = Text._getPropTypes();
Text.defaultProps = Text._getDefaultProps();
export default Text;
export { TextStylePropTypes };

View File

@@ -1,81 +0,0 @@
import { pickProps } from '../filterObjectProps';
import StylePropTypes from '../StylePropTypes';
import React, { PropTypes } from 'react';
import WebStyleComponent from '../WebStyleComponent';
// https://github.com/facebook/css-layout#default-values
const ViewStyleDefaultProps = {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
listStyle: 'none',
margin: 0,
padding: 0,
position: 'relative'
};
const ViewStylePropTypes = {
...StylePropTypes.BackgroundPropTypes,
...StylePropTypes.BorderThemePropTypes,
...StylePropTypes.LayoutPropTypes,
boxShadow: PropTypes.string,
opacity: PropTypes.number,
transform: PropTypes.string
};
class View extends React.Component {
static _getPropTypes() {
return {
className: PropTypes.string,
element: PropTypes.oneOfType([
PropTypes.string, PropTypes.func
]),
pointerEvents: PropTypes.oneOf([
'auto',
'box-none',
'box-only',
'none'
]),
style: PropTypes.shape(ViewStylePropTypes)
};
}
static _getDefaultProps() {
return {
className: '',
element: 'div',
style: {}
};
}
render() {
const { className, element, pointerEvents, style, ...other } = this.props;
const filteredStyle = pickProps(style, Object.keys(ViewStylePropTypes));
const pointerEventsStyle = pointerEvents && { pointerEvents };
const mergedStyle = {
...ViewStyleDefaultProps,
...filteredStyle,
...pointerEventsStyle
};
return (
<WebStyleComponent
{...other}
className={`View ${className}`}
element={element}
style={mergedStyle}
/>
);
}
}
View.propTypes = View._getPropTypes();
View.defaultProps = View._getDefaultProps();
export default View;
export { ViewStylePropTypes };

View File

@@ -1,70 +0,0 @@
import autoprefix from './lib/autoprefix';
import React, {PropTypes} from 'react';
import styleMap from './lib/styleMap';
class WebStyleComponent extends React.Component {
static _getPropTypes() {
return {
className: PropTypes.string,
element: PropTypes.oneOfType([
PropTypes.string, PropTypes.func
]),
style: PropTypes.object
};
}
static _getDefaultProps() {
return {
className: '',
element: 'div',
style: {}
};
}
render() {
const { element: Element, ...other } = this.props;
const { classNames, inlineStyles } = this._separateClassNamesAndStyles();
return (
<Element
{...other}
className={classNames.join(' ')}
style={autoprefix(inlineStyles)}
/>
);
}
_getSinglePurposeClassName(prop, style) {
const uniqueClassName = `${prop}-${style[prop]}`;
if (
style.hasOwnProperty(prop) &&
styleMap[uniqueClassName]
) {
return styleMap[uniqueClassName];
}
}
_separateClassNamesAndStyles() {
const styleProp = this.props.style;
let classNames = [ this.props.className ];
let inlineStyles = {};
for (let prop in styleProp) {
let singlePurposeClassName =
this._getSinglePurposeClassName(prop, styleProp);
if (singlePurposeClassName) {
classNames.push(singlePurposeClassName);
} else {
inlineStyles[prop] = styleProp[prop];
}
}
return { classNames, inlineStyles };
}
}
WebStyleComponent.propTypes = WebStyleComponent._getPropTypes();
WebStyleComponent.defaultProps = WebStyleComponent._getDefaultProps();
export default WebStyleComponent;

View File

@@ -1 +0,0 @@
.appearance-none { appearance: none; }

View File

@@ -1,44 +0,0 @@
.backgroundAttachment-fixed { background-attachment: fixed; }
.backgroundAttachment-inherit { background-attachment: inherit; }
.backgroundAttachment-local { background-attachment: local; }
.backgroundAttachment-scroll { background-attachment: scroll; }
.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; }
.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; }
.backgroundImage { background-image: none; }
.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; }
.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; }
.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; }
.backgroundSize-auto { background-size: auto; }
.backgroundSize-contain { background-size: contain; }
.backgroundSize-cover { background-size: cover; }
.backgroundSize-inherit { background-size: inherit; }

View File

@@ -1,100 +0,0 @@
/* border-color */
.borderColor-\#fff,
.borderColor-white { border-color: white; }
.borderColor-currentcolor { border-color: currentcolor; }
.borderColor-inherit { border-color: inherit; }
.borderColor-transparent { border-color: transparent; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }
.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; }

View File

@@ -1,4 +0,0 @@
.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; }

View File

@@ -1,5 +0,0 @@
.clear-both { clear: both; }
.clear-inherit { clear: inherit; }
.clear-left { clear: left; }
.clear-none { clear: none; }
.clear-right { clear: right; }

View File

@@ -1,6 +0,0 @@
.color-#000,
.color-black { color: black; }
.color-\#fff,
.color-white { color: white; }
.color-inherit { color: inherit; }
.color-transparent { color: transparent; }

View File

@@ -1,3 +0,0 @@
.direction-inherit { direction: inherit; }
.direction-ltr { direction: ltr; }
.direction-rtl { direction: rtl; }

View File

@@ -1,22 +0,0 @@
.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; }

View File

@@ -1,74 +0,0 @@
.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; }
.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; }
.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; }
.flexBasis-0 { flex-basis: 0%; }
.flexBasis-auto { flex-basis: auto; }
.flexDirection-column { flex-direction: column; }
.flexDirection-column-reverse { flex-direction: column-reverse; }
.flexDirection-row { flex-direction: row; }
.flexDirection-row-reverse { flex-direction: row-reverse; }
.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; }
.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; }
.flexWrap-nowrap { flex-wrap: nowrap; }
.flexWrap-wrap { flex-wrap: wrap; }
.flexWrap-wrap-reverse { flex-wrap: wrap-reverse; }
.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; }
.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; }

View File

@@ -1,3 +0,0 @@
.float-left { float: left; }
.float-none { float: none; }
.float-right { float: right; }

View File

@@ -1,37 +0,0 @@
.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; }

View File

@@ -1,20 +0,0 @@
.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%; }
.maxHeight-100\% { max-height: 100%; }
.minHeight-100\% { min-height: 100%; }

View File

@@ -1,2 +0,0 @@
.lineHeight-inherit { line-height: inherit; }
.lineHeight-normal { line-height: normal; }

View File

@@ -1 +0,0 @@
.listStyle-none { list-style: none; }

View File

@@ -1,22 +0,0 @@
.margin-0 { margin: 0; }
.margin-auto { margin: auto; }
.marginBottom-auto { margin-bottom: auto; }
.marginLeft-auto { margin-left: auto; }
.marginRight-auto { margin-right: auto; }
.marginTop-auto { margin-top: auto; }
.marginBottom-0 { margin-bottom: 0; }
.marginLeft-0 { margin-left: 0; }
.marginRight-0 { margin-right: 0; }
.marginTop-0 { margin-top: 0; }
.marginBottom-1em { margin-bottom: 1em; }
.marginLeft-1em { margin-left: 1em; }
.marginRight-1em { margin-right: 1em; }
.marginTop-1em { margin-top: 1em; }
.marginBottom-1rem { margin-bottom: 1rem; }
.marginLeft-1rem { margin-left: 1rem; }
.marginRight-1rem { margin-right: 1rem; }
.marginTop-1rem { margin-top: 1rem; }

View File

@@ -1,2 +0,0 @@
.opacity-0 { opacity: 0; }
.opacity-1 { opacity: 1; }

View File

@@ -1,17 +0,0 @@
.overflow-auto { overflow: auto; }
.overflow-hidden { overflow: hidden; }
.overflow-inherit { overflow: inherit; }
.overflow-scroll { overflow: scroll; }
.overflow-visible { overflow: visible; }
.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; }
.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; }

View File

@@ -1,17 +0,0 @@
.padding-0 { padding: 0; }
.paddingTop-0 { padding-top: 0; }
.paddingRight-0 { padding-right: 0; }
.paddingBottom-0 { padding-bottom: 0; }
.paddingLeft-0 { padding-left: 0; }
.padding-1em { padding: 1em; }
.paddingTop-1em { padding-top: 1em; }
.paddingRight-1em { padding-right: 1em; }
.paddingBottom-1em { padding-bottom: 1em; }
.paddingLeft-1em { padding-left: 1em; }
.padding-1rem { padding: 1rem; }
.paddingTop-1rem { padding-top: 1rem; }
.paddingRight-1rem { padding-right: 1rem; }
.paddingBottom-1rem { padding-bottom: 1rem; }
.paddingLeft-1rem { padding-left: 1rem; }

View File

@@ -1,6 +0,0 @@
.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; }

View File

@@ -1,18 +0,0 @@
.position-absolute { position: absolute; }
.position-fixed { position: fixed; }
.position-relative { position: relative; }
.bottom-0 { bottom: 0; }
.left-0 { left: 0; }
.right-0 { right: 0; }
.top-0 { top: 0; }
.bottom-50% { bottom: 50%; }
.left-50% { left: 50%; }
.right-50% { right: 50%; }
.top-50% { top: 50%; }
.bottom-100% { bottom: 100%; }
.left-100% { left: 100%; }
.right-100% { right: 100%; }
.top-100% { top: 100%; }

View File

@@ -1,25 +0,0 @@
.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; }
.textDecoration-inherit { text-decoration: inherit; }
.textDecoration-none { text-decoration: none; }
.textDecoration-underline { text-decoration: underline; }
.textOverflow-clip { text-overflow: clip; }
.textOverflow-ellipsis { text-overflow: ellipsis; }
.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; }
.textTransform-capitalize { text-transform: capitalize; }
.textTransform-lowercase { text-transform: lowercase; }
.textTransform-none { text-transform: none; }
.textTransform-uppercase { text-transform: uppercase; }

View File

@@ -1,7 +0,0 @@
.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; }

View File

@@ -1,4 +0,0 @@
.userSelect-all { user-select: all; }
.userSelect-inherit { user-select: inherit; }
.userSelect-none { user-select: none; }
.userSelect-text { user-select: text; }

View File

@@ -1,4 +0,0 @@
.visibility-collapse { visibility: collapse; }
.visibility-hidden { visibility: hidden; }
.visibility-inherit { visibility: inherit; }
.visibility-visible { visibility: visible; }

View File

@@ -1,5 +0,0 @@
.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; }

View File

@@ -1,20 +0,0 @@
.maxWidth-100\% { max-width: 100%; }
.minWidth-100\% { min-width: 100%; }
.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%; }

View File

@@ -1,2 +0,0 @@
.wordWrap-break-word { word-wrap: break-word; }
.wordWrap-normal { word-wrap: normal; }

View File

@@ -1,12 +0,0 @@
.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

@@ -1,60 +0,0 @@
import appearance from './css/appearance.css';
import background from './css/background.css';
import border from './css/border.css';
import boxSizing from './css/boxSizing.css';
import clear from './css/clear.css';
import color from './css/color.css';
import cssFloat from './css/float.css';
import direction from './css/direction.css';
import display from './css/display.css';
import flexbox from './css/flexbox.css';
import font from './css/font.css';
import height from './css/height.css';
import lineHeight from './css/lineHeight.css';
import list from './css/list.css';
import margin from './css/margin.css';
import opacity from './css/opacity.css';
import overflow from './css/overflow.css';
import padding from './css/padding.css';
import pointerEvents from './css/pointerEvents.css';
import position from './css/position.css';
import text from './css/text.css';
import unicodeBidi from './css/unicodeBidi.css';
import userSelect from './css/userSelect.css';
import visibility from './css/visibility.css';
import whiteSpace from './css/whiteSpace.css';
import width from './css/width.css';
import word from './css/word.css';
import zIndex from './css/zIndex.css';
const map = Object.assign({},
appearance,
background,
border,
boxSizing,
clear,
color,
cssFloat,
direction,
display,
flexbox,
font,
height,
list,
margin,
opacity,
overflow,
padding,
pointerEvents,
position,
text,
unicodeBidi,
userSelect,
visibility,
whiteSpace,
width,
word,
zIndex
);
export default map;

View File

@@ -1,35 +1,25 @@
function filterProps(obj, props, excluded=false) {
if (!Array.isArray(props)) {
throw new TypeError('props is not an Array');
}
let filtered = {};
for (let prop in obj) {
function filterProps(obj, propKeys: Array, excluded = false) {
const filtered = {}
for (const prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
let isMatch = props.indexOf(prop) > -1;
const isMatch = propKeys.indexOf(prop) > -1
if (excluded && isMatch) {
continue;
continue
} else if (!excluded && !isMatch) {
continue;
continue
}
filtered[prop] = obj[prop];
filtered[prop] = obj[prop]
}
}
return filtered;
return filtered
}
// Extract all props that are not part of the React Component's 'propTypes'
export function getOtherProps(componentInstance) {
const excludedProps = Object.keys(componentInstance.constructor.propTypes);
return omitProps(componentInstance.props, excludedProps);
export function pickProps(obj, propKeys) {
return filterProps(obj, propKeys)
}
export function pickProps(obj, props) {
return filterProps(obj, props);
}
export function omitProps(obj, props) {
return filterProps(obj, props, true);
export function omitProps(obj, propKeys) {
return filterProps(obj, propKeys, true)
}

Some files were not shown because too many files have changed in this diff Show More