mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-13 22:44:52 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65aa8a943 | ||
|
|
0aa60a3c29 | ||
|
|
8ac84f6da5 | ||
|
|
69166b1502 | ||
|
|
cc10de43eb | ||
|
|
c850a5fa8c | ||
|
|
1efe5a533f | ||
|
|
804132ce36 | ||
|
|
5335bcfd48 | ||
|
|
c0e7afc495 | ||
|
|
fa88548c3c | ||
|
|
39b2b2f979 |
@@ -1,25 +1,24 @@
|
||||
# Contributing to this project
|
||||
|
||||
The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||
[features requests](#features) and [submitting pull
|
||||
requests](#pull-requests).
|
||||
[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!
|
||||
Good bug reports are extremely helpful - thank you! You can compare the
|
||||
behaviour against that expected with React Native by using the [React Native
|
||||
Playground](https://rnplay.org/)
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. **Use the GitHub issue search** — check if the issue has already been
|
||||
reported.
|
||||
reported or fixed in `master`.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `master` or development branch in the repository.
|
||||
|
||||
3. **Isolate the problem** — create a [reduced test
|
||||
case](http://css-tricks.com/reduced-test-cases/) and a live example.
|
||||
2. **Isolate the problem** — create a [reduced test
|
||||
case](http://css-tricks.com/reduced-test-cases/) using this
|
||||
[codepen](https://codepen.io/necolas/pen/PZzwBR?editors=001).
|
||||
|
||||
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
|
||||
@@ -49,9 +48,9 @@ Example:
|
||||
## 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.
|
||||
fits with the scope and aims of the project (i.e., is this for parity with
|
||||
React Native? does it make sense on the Web?). Please provide as much detail
|
||||
and context as you think is necessary to make your case.
|
||||
|
||||
|
||||
<a name="pull-requests"></a>
|
||||
@@ -70,7 +69,8 @@ Development commands:
|
||||
* `npm run build` – build the library
|
||||
* `npm run examples` – start the dev server and develop against live examples
|
||||
* `npm run lint` – run the linter
|
||||
* `npm run test` – run the linter and unit tests
|
||||
* `npm run test:watch` – run and watch the unit tests
|
||||
* `npm test` – run the linter and unit tests
|
||||
|
||||
Please follow this process for submitting a patch:
|
||||
|
||||
|
||||
33
README.md
33
README.md
@@ -2,10 +2,13 @@
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![npm version][npm-image]][npm-url]
|
||||

|
||||

|
||||
|
||||
[React Native][react-native-url] components and APIs for the Web.
|
||||
|
||||
Try it out in the [React Native for Web
|
||||
Playground](http://codepen.io/necolas/pen/PZzwBR) on CodePen.
|
||||
|
||||
* [Discord: #react-native-web on reactiflux][discord-url]
|
||||
* [Gitter: react-native-web][gitter-url]
|
||||
|
||||
@@ -29,8 +32,9 @@ npm install --save react react-dom react-native-web
|
||||
|
||||
## Example
|
||||
|
||||
React Native for Web exports its components and a reference to the `React`
|
||||
installation. Styles are defined with, and used as JavaScript objects.
|
||||
React Native for Web exports its components, a reference to the `react`
|
||||
installation, and the `react-dom` methods (customized for Web). Styles are defined
|
||||
with, and used as JavaScript objects.
|
||||
|
||||
Component:
|
||||
|
||||
@@ -71,7 +75,7 @@ const styles = StyleSheet.create({
|
||||
width: 40,
|
||||
},
|
||||
text: {
|
||||
flex: 1,
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
title: {
|
||||
@@ -84,22 +88,20 @@ const styles = StyleSheet.create({
|
||||
})
|
||||
```
|
||||
|
||||
Pre-render styles on the server:
|
||||
Pre-rendering on the server automatically includes your app styles:
|
||||
|
||||
```js
|
||||
// server.js
|
||||
import App from './components/App'
|
||||
import React, { StyleSheet } from 'react-native-web'
|
||||
import React from 'react-native-web'
|
||||
|
||||
const html = React.renderToString(<App />);
|
||||
const css = StyleSheet.renderToString();
|
||||
|
||||
const Html = () => (
|
||||
<html>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta content="initial-scale=1,width=device-width" name="viewport" />
|
||||
<style id="react-stylesheet" dangerouslySetInnerHTML={{ __html: css } />
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-root" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
@@ -108,19 +110,15 @@ const Html = () => (
|
||||
)
|
||||
```
|
||||
|
||||
Render styles on the client:
|
||||
Rendering on the client automatically includes your app styles and supports
|
||||
progressive app loading (i.e. code-splitting / lazy bundle loading):
|
||||
|
||||
```js
|
||||
// client.js
|
||||
import App from './components/App'
|
||||
import React, { StyleSheet } from 'react-native-web'
|
||||
import ReactDOM from 'react-dom'
|
||||
import React from 'react-native-web'
|
||||
|
||||
const reactRoot = document.getElementById('react-root')
|
||||
const reactStyleSheet = document.getElementById('react-stylesheet')
|
||||
|
||||
ReactDOM.render(<App />, reactRoot)
|
||||
reactStyleSheet.textContent = StyleSheet.renderToString()
|
||||
React.render(<App />, document.getElementById('react-root'))
|
||||
```
|
||||
|
||||
## APIs
|
||||
@@ -174,7 +172,6 @@ flexbox][flexbox-guide-url].
|
||||
|
||||
### Media Queries, pseudo-classes, and pseudo-elements
|
||||
|
||||
|
||||
Changing styles and/or the render tree in response to device adaptation can be
|
||||
controlled in JavaScript, e.g.,
|
||||
[react-media-queries](https://github.com/bloodyowl/react-media-queries),
|
||||
@@ -185,7 +182,7 @@ benefit of co-locating breakpoint-specific DOM and style changes.
|
||||
Pseudo-classes like `:hover` and `:focus` can be implemented with the `onHover`
|
||||
and `onFocus` events.
|
||||
|
||||
Pseudo-elements are not supported.
|
||||
Pseudo-elements are not supported; elements can be used instead.
|
||||
|
||||
## Accessibility
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
var webpack = require('webpack')
|
||||
|
||||
var DedupePlugin = webpack.optimize.DedupePlugin
|
||||
var DefinePlugin = webpack.DefinePlugin
|
||||
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 DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
)
|
||||
plugins.push(
|
||||
new UglifyJsPlugin({
|
||||
compress: {
|
||||
dead_code: true,
|
||||
drop_console: true,
|
||||
screw_ie8: true,
|
||||
warnings: true
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: plugins
|
||||
}
|
||||
@@ -1,17 +1,31 @@
|
||||
var assign = require('object-assign')
|
||||
var base = require('./webpack.config.base')
|
||||
var constants = require('./constants')
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = assign({}, base, {
|
||||
module.exports = {
|
||||
devServer: {
|
||||
contentBase: constants.EXAMPLES_DIRECTORY
|
||||
},
|
||||
entry: {
|
||||
example: path.join(constants.EXAMPLES_DIRECTORY, 'index')
|
||||
example: constants.EXAMPLES_DIRECTORY
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: 'examples.js',
|
||||
path: constants.DIST_DIRECTORY
|
||||
}
|
||||
})
|
||||
filename: 'examples.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
}),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
new webpack.optimize.OccurenceOrderPlugin()
|
||||
]
|
||||
}
|
||||
|
||||
32
config/webpack.config.js
Normal file
32
config/webpack.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
var constants = require('./constants')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
main: constants.DIST_DIRECTORY
|
||||
},
|
||||
externals: [{
|
||||
'react': true,
|
||||
'react-dom': true,
|
||||
'react-dom/server': true
|
||||
}],
|
||||
output: {
|
||||
filename: 'react-native-web.js',
|
||||
library: 'ReactNativeWeb',
|
||||
libraryTarget: 'umd',
|
||||
path: constants.DIST_DIRECTORY
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
new webpack.optimize.OccurenceOrderPlugin(),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
dead_code: true,
|
||||
drop_console: true,
|
||||
screw_ie8: true,
|
||||
warnings: true
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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,
|
||||
'react-dom': true
|
||||
}],
|
||||
output: {
|
||||
filename: 'react-native-web.js',
|
||||
library: 'ReactNativeWeb',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: constants.DIST_DIRECTORY
|
||||
}
|
||||
})
|
||||
@@ -37,24 +37,10 @@ Use styles:
|
||||
</View>
|
||||
```
|
||||
|
||||
Render styles on the server or in the browser:
|
||||
|
||||
```js
|
||||
StyleSheet.renderToString()
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
**create**(obj: {[key: string]: any})
|
||||
|
||||
**destroy**()
|
||||
|
||||
Clears all style information.
|
||||
|
||||
**renderToString**()
|
||||
|
||||
Renders a CSS Style Sheet.
|
||||
|
||||
## About
|
||||
|
||||
### Strategy
|
||||
@@ -71,9 +57,9 @@ CSS](https://speakerdeck.com/vjeux/react-css-in-js):
|
||||
6. Non-deterministic resolution
|
||||
7. Breaking isolation
|
||||
|
||||
The strategy also minimizes the amount of generated CSS, making it more viable
|
||||
to inline the style sheet when pre-rendering pages on the server. There is one
|
||||
unique selector per unique style _declaration_.
|
||||
The strategy minimizes the amount of generated CSS, making it viable to inline
|
||||
the style sheet when pre-rendering pages on the server. There is one unique
|
||||
selector per unique style _declaration_.
|
||||
|
||||
```js
|
||||
// definition
|
||||
@@ -88,7 +74,7 @@ unique selector per unique style _declaration_.
|
||||
}
|
||||
}
|
||||
|
||||
// css
|
||||
// css output
|
||||
//
|
||||
// .a { color: gray; }
|
||||
// .b { font-size: 2rem; }
|
||||
@@ -130,16 +116,17 @@ In production the class names are obfuscated.
|
||||
(CSS libraries like [Atomic CSS](http://acss.io/),
|
||||
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
|
||||
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
|
||||
style sheet growth in a similar way. But they're CSS utility libraries, each with a
|
||||
particular set of classes and features to learn. All of them require developers
|
||||
to manually connect CSS classes for given styles.)
|
||||
style sheet growth in a similar way. But they're CSS utility libraries, each
|
||||
with a particular set of classes and features to learn. And all of them require
|
||||
developers to manually connect CSS classes for given styles.)
|
||||
|
||||
### Reset
|
||||
|
||||
React Native for Web includes a very small CSS reset taken from
|
||||
[normalize.css](https://necolas.github.io/normalize.css/). It removes unwanted
|
||||
User Agent styles from (pseudo-)elements beyond the reach of React (e.g.,
|
||||
`html`, `body`) or inline styles (e.g., `::-moz-focus-inner`).
|
||||
[normalize.css](https://necolas.github.io/normalize.css/) – **you do not need
|
||||
to include normalize.css**. It removes unwanted User Agent styles from
|
||||
(pseudo-)elements beyond the reach of React (e.g., `html`, `body`) or inline
|
||||
styles (e.g., `::-moz-focus-inner`).
|
||||
|
||||
```css
|
||||
html {
|
||||
@@ -162,4 +149,10 @@ input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
li {
|
||||
list-style:none
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,6 +61,7 @@ This function is called on press.
|
||||
+ `lineHeight`
|
||||
+ `textAlign`
|
||||
+ `textDecoration`
|
||||
+ `textShadow`
|
||||
+ `textTransform`
|
||||
+ `whiteSpace`
|
||||
+ `wordWrap`
|
||||
|
||||
@@ -20,7 +20,7 @@ Unsupported React Native props:
|
||||
Overrides the text that's read by the screen reader when the user interacts
|
||||
with the element.
|
||||
|
||||
(web) **accessibilityRole**: oneOf(roles)
|
||||
(web) **accessibilityRole**: oneOf(roles) = 'button'
|
||||
|
||||
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
|
||||
@@ -35,12 +35,12 @@ assistive technologies of a `role` value change.
|
||||
|
||||
When `false`, the view is hidden from screenreaders.
|
||||
|
||||
**activeOpacity**: number = 1
|
||||
**activeOpacity**: number = 0.8
|
||||
|
||||
Sets the opacity of the child view when `onPressIn` is called. The opacity is
|
||||
reset when `onPressOut` is called.
|
||||
|
||||
(web) **activeUnderlayColor**: string = 'transparent'
|
||||
(web) **activeUnderlayColor**: string = 'black'
|
||||
|
||||
Sets the color of the background highlight when `onPressIn` is called. The
|
||||
highlight is removed when `onPressOut` is called.
|
||||
@@ -49,7 +49,7 @@ highlight is removed when `onPressOut` is called.
|
||||
|
||||
A single child element.
|
||||
|
||||
**delayLongPress**: number = 1000
|
||||
**delayLongPress**: number = 500
|
||||
|
||||
Delay in ms, from `onPressIn`, before `onLongPress` is called.
|
||||
|
||||
@@ -59,7 +59,7 @@ Delay in ms, from `onPressIn`, before `onLongPress` is called.
|
||||
|
||||
Delay in ms, from the start of the touch, before `onPressIn` is called.
|
||||
|
||||
**delayPressOut**: number = 0
|
||||
**delayPressOut**: number = 100
|
||||
|
||||
(TODO)
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ from `style`.
|
||||
+ `boxShadow`
|
||||
+ `boxSizing`
|
||||
+ `cursor`
|
||||
+ `flex` (number)
|
||||
+ `flexBasis`
|
||||
+ `flexDirection`
|
||||
+ `flexGrow`
|
||||
@@ -108,7 +109,7 @@ from `style`.
|
||||
+ `height`
|
||||
+ `justifyContent`
|
||||
+ `left`
|
||||
+ `margin`
|
||||
+ `margin` (single value)
|
||||
+ `marginBottom`
|
||||
+ `marginHorizontal`
|
||||
+ `marginLeft`
|
||||
@@ -124,7 +125,7 @@ from `style`.
|
||||
+ `overflow`
|
||||
+ `overflowX`
|
||||
+ `overflowY`
|
||||
+ `padding`
|
||||
+ `padding` (single value)
|
||||
+ `paddingBottom`
|
||||
+ `paddingHorizontal`
|
||||
+ `paddingLeft`
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
<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 id="react-stylesheet"></style>
|
||||
<div id="react-root"></div>
|
||||
<script src="/examples.js"></script>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { MediaProvider, matchMedia } from 'react-media-queries'
|
||||
import App from './components/App'
|
||||
import createGetter from 'react-media-queries/lib/createMediaQueryGetter'
|
||||
import createListener from 'react-media-queries/lib/createMediaQueryListener'
|
||||
import React, { StyleSheet } from '../src'
|
||||
import ReactDOM from 'react-dom'
|
||||
import React from '../src'
|
||||
|
||||
const mediaQueries = {
|
||||
small: '(min-width: 300px)',
|
||||
@@ -12,11 +11,9 @@ const mediaQueries = {
|
||||
}
|
||||
const ResponsiveApp = matchMedia()(App)
|
||||
|
||||
ReactDOM.render(
|
||||
React.render(
|
||||
<MediaProvider getMedia={createGetter(mediaQueries)} listener={createListener(mediaQueries)}>
|
||||
<ResponsiveApp />
|
||||
</MediaProvider>,
|
||||
document.getElementById('react-root')
|
||||
)
|
||||
|
||||
document.getElementById('react-stylesheet').textContent = StyleSheet.renderToString()
|
||||
|
||||
27
package.json
27
package.json
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.12",
|
||||
"version": "0.0.13",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/react-native-web.js",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf ./dist && webpack --config config/webpack.config.publish.js --sort-assets-by --progress",
|
||||
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
|
||||
"build:umd": "webpack --config config/webpack.config.js --sort-assets-by --progress",
|
||||
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
|
||||
"lint": "eslint config examples src",
|
||||
"prepublish": "npm run build",
|
||||
"prepublish": "npm run build && npm run build:umd",
|
||||
"test": "npm run lint && npm run test:unit",
|
||||
"test:unit": "karma start config/karma.config.js",
|
||||
"test:watch": "npm run test:unit -- --no-single-run"
|
||||
@@ -22,20 +23,21 @@
|
||||
"react-textarea-autosize": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.2.4",
|
||||
"babel-cli": "^6.3.17",
|
||||
"babel-core": "^6.3.13",
|
||||
"babel-eslint": "^4.1.6",
|
||||
"babel-loader": "^6.2.0",
|
||||
"babel-preset-es2015": "^6.2.4",
|
||||
"babel-preset-react": "^6.2.4",
|
||||
"babel-preset-stage-1": "^6.2.4",
|
||||
"babel-runtime": "^6.2.4",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-1": "^6.3.13",
|
||||
"babel-runtime": "^6.3.19",
|
||||
"eslint": "^1.10.3",
|
||||
"eslint-config-standard": "^4.4.0",
|
||||
"eslint-config-standard-react": "^1.2.1",
|
||||
"eslint-plugin-react": "^3.11.2",
|
||||
"eslint-plugin-react": "^3.13.1",
|
||||
"eslint-plugin-standard": "^1.3.1",
|
||||
"karma": "^0.13.15",
|
||||
"karma-browserstack-launcher": "^0.1.7",
|
||||
"karma": "^0.13.16",
|
||||
"karma-browserstack-launcher": "^0.1.8",
|
||||
"karma-chrome-launcher": "^0.2.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-mocha": "^0.2.1",
|
||||
@@ -44,7 +46,6 @@
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.3.4",
|
||||
"node-libs-browser": "^0.5.3",
|
||||
"object-assign": "^4.0.1",
|
||||
"react": "^0.14.3",
|
||||
"react-addons-test-utils": "^0.14.3",
|
||||
"react-dom": "^0.14.3",
|
||||
|
||||
53
src/__tests__/index-test.js
Normal file
53
src/__tests__/index-test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import React from '..'
|
||||
|
||||
suite('ReactNativeWeb', () => {
|
||||
suite('exports', () => {
|
||||
test('React', () => {
|
||||
assert.ok(React)
|
||||
})
|
||||
|
||||
test('ReactDOM methods', () => {
|
||||
assert.ok(React.findDOMNode)
|
||||
assert.ok(React.render)
|
||||
assert.ok(React.unmountComponentAtNode)
|
||||
})
|
||||
|
||||
test('ReactDOM/server methods', () => {
|
||||
assert.ok(React.renderToString)
|
||||
assert.ok(React.renderToStaticMarkup)
|
||||
})
|
||||
})
|
||||
|
||||
suite('render methods', () => {
|
||||
const id = 'test'
|
||||
let div
|
||||
|
||||
setup(() => {
|
||||
div = document.createElement('div')
|
||||
div.id = id
|
||||
document.body.appendChild(div)
|
||||
})
|
||||
|
||||
teardown(() => {
|
||||
document.body.removeChild(div)
|
||||
})
|
||||
|
||||
test('"render" creates style sheet', () => {
|
||||
React.render(<div />, div)
|
||||
assert.ok(document.getElementById('react-stylesheet'))
|
||||
})
|
||||
|
||||
test('"renderToString" creates style sheet', () => {
|
||||
const result = React.renderToString(<div />)
|
||||
assert.ok(result.indexOf('react-stylesheet') > -1)
|
||||
})
|
||||
|
||||
test('"renderToStaticMarkup" creates style sheet', () => {
|
||||
const result = React.renderToStaticMarkup(<div />)
|
||||
assert.ok(result.indexOf('react-stylesheet') > -1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -21,14 +21,11 @@ const roleComponents = {
|
||||
class CoreComponent extends React.Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessibilityLiveRegion: PropTypes.oneOf(['assertive', 'off', 'polite']),
|
||||
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
|
||||
accessibilityRole: PropTypes.string,
|
||||
accessible: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
component: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string
|
||||
]),
|
||||
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
|
||||
style: PropTypes.object,
|
||||
testID: PropTypes.string,
|
||||
type: PropTypes.string
|
||||
|
||||
@@ -201,10 +201,7 @@ class Image extends React.Component {
|
||||
}}
|
||||
testID={testID}
|
||||
>
|
||||
<img
|
||||
src={displayImage}
|
||||
style={styles.img}
|
||||
/>
|
||||
<img src={displayImage} style={styles.img} />
|
||||
{children ? (
|
||||
<View children={children} pointerEvents='box-none' style={styles.children} />
|
||||
) : null}
|
||||
|
||||
@@ -3,11 +3,12 @@ import ScrollView from '../ScrollView'
|
||||
|
||||
class ListView extends React.Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.any
|
||||
children: PropTypes.any,
|
||||
style: PropTypes.style
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
className: ''
|
||||
style: {}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -14,6 +14,7 @@ export default {
|
||||
'lineHeight',
|
||||
'textAlign',
|
||||
'textDecoration',
|
||||
'textShadow',
|
||||
'textTransform',
|
||||
'whiteSpace',
|
||||
'wordWrap',
|
||||
|
||||
@@ -58,7 +58,7 @@ class Text extends React.Component {
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
const className = `Text ${_className}`.trim()
|
||||
const className = `${_className} Text`.trim()
|
||||
const resolvedStyle = pickProps(style, textStyleKeys)
|
||||
|
||||
return (
|
||||
|
||||
@@ -97,17 +97,14 @@ suite('components/TextInput', () => {
|
||||
return str
|
||||
}
|
||||
|
||||
let input = findInput(utils.renderAndInject(
|
||||
const result = utils.shallowRender(
|
||||
<TextInput
|
||||
maxNumberOfLines={3}
|
||||
multiline
|
||||
value={generateValue()}
|
||||
/>
|
||||
))
|
||||
const height = input.getBoundingClientRect().height
|
||||
// need a range because of cross-browser differences
|
||||
assert.ok(height >= 42, height)
|
||||
assert.ok(height <= 48, height)
|
||||
)
|
||||
assert.equal(findShallowInput(result).props.maxRows, 3)
|
||||
})
|
||||
|
||||
test('prop "multiline"', () => {
|
||||
@@ -126,10 +123,14 @@ suite('components/TextInput', () => {
|
||||
// with multiline
|
||||
input = findInput(utils.renderAndInject(<TextInput multiline numberOfLines={2} />))
|
||||
assert.equal(input.tagName, 'TEXTAREA')
|
||||
const height = input.getBoundingClientRect().height
|
||||
// need a range because of cross-browser differences
|
||||
assert.ok(height >= 30, height)
|
||||
assert.ok(height <= 36, height)
|
||||
|
||||
const result = utils.shallowRender(
|
||||
<TextInput
|
||||
multiline
|
||||
numberOfLines={3}
|
||||
/>
|
||||
)
|
||||
assert.equal(findShallowInput(result).props.minRows, 3)
|
||||
})
|
||||
|
||||
test('prop "onBlur"', (done) => {
|
||||
|
||||
@@ -43,11 +43,11 @@ class Touchable extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
accessibilityRole: 'button',
|
||||
activeOpacity: 1,
|
||||
activeUnderlayColor: 'transparent',
|
||||
delayLongPress: 1000,
|
||||
activeOpacity: 0.8,
|
||||
activeUnderlayColor: 'black',
|
||||
delayLongPress: 500,
|
||||
delayPressIn: 0,
|
||||
delayPressOut: 0,
|
||||
delayPressOut: 100,
|
||||
style: styles.initial
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export default {
|
||||
'boxShadow',
|
||||
'boxSizing',
|
||||
'cursor',
|
||||
'flex',
|
||||
'flexBasis',
|
||||
'flexDirection',
|
||||
'flexGrow',
|
||||
|
||||
@@ -59,7 +59,7 @@ class View extends React.Component {
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
const className = `View ${_className}`.trim()
|
||||
const className = `${_className} View`.trim()
|
||||
const pointerEventsStyle = pointerEvents && { pointerEvents }
|
||||
const resolvedStyle = pickProps(style, viewStyleKeys)
|
||||
|
||||
|
||||
41
src/index.js
41
src/index.js
@@ -1,5 +1,8 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
|
||||
// api
|
||||
import StyleSheet from './modules/StyleSheet'
|
||||
|
||||
// components
|
||||
@@ -11,7 +14,35 @@ import TextInput from './components/TextInput'
|
||||
import Touchable from './components/Touchable'
|
||||
import View from './components/View'
|
||||
|
||||
const renderStyle = () => {
|
||||
return `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
|
||||
}
|
||||
|
||||
const render = (element, container, callback) => {
|
||||
const styleElement = document.getElementById('react-stylesheet')
|
||||
if (!styleElement) {
|
||||
const style = renderStyle()
|
||||
container.insertAdjacentHTML('beforebegin', style)
|
||||
}
|
||||
return ReactDOM.render(element, container, callback)
|
||||
}
|
||||
|
||||
const renderToString = (element) => {
|
||||
const style = renderStyle()
|
||||
const html = ReactDOMServer.renderToString(element)
|
||||
return `${style}\n${html}`
|
||||
}
|
||||
|
||||
const renderToStaticMarkup = (element) => {
|
||||
const style = renderStyle()
|
||||
const html = ReactDOMServer.renderToStaticMarkup(element)
|
||||
return `${style}\n${html}`
|
||||
}
|
||||
|
||||
const ReactNative = {
|
||||
// apis
|
||||
StyleSheet,
|
||||
|
||||
// components
|
||||
Image,
|
||||
ListView,
|
||||
@@ -21,11 +52,13 @@ const ReactNative = {
|
||||
Touchable,
|
||||
View,
|
||||
|
||||
// apis
|
||||
StyleSheet,
|
||||
|
||||
// React
|
||||
...React
|
||||
...React,
|
||||
...ReactDOM,
|
||||
...ReactDOMServer,
|
||||
render,
|
||||
renderToString,
|
||||
renderToStaticMarkup
|
||||
}
|
||||
|
||||
module.exports = ReactNative
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { PropTypes } from 'react'
|
||||
|
||||
const { number, string } = PropTypes
|
||||
const numberOrString = PropTypes.oneOfType([ number, string ])
|
||||
const { number, oneOf, oneOfType, string } = PropTypes
|
||||
const numberOrString = oneOfType([ number, string ])
|
||||
|
||||
/**
|
||||
* Any properties marked @private are used internally in resets or property
|
||||
* mappings.
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/Reference
|
||||
*/
|
||||
export default {
|
||||
alignContent: string,
|
||||
alignItems: string,
|
||||
alignSelf: string,
|
||||
alignContent: oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between', 'stretch' ]),
|
||||
alignItems: oneOf([ 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
|
||||
alignSelf: oneOf([ 'auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
|
||||
appearance: string,
|
||||
backfaceVisibility: string,
|
||||
backgroundAttachment: string,
|
||||
backgroundAttachment: oneOf([ 'fixed', 'local', 'scroll' ]),
|
||||
backgroundClip: string,
|
||||
backgroundColor: string,
|
||||
backgroundImage: string,
|
||||
backgroundOrigin: string,
|
||||
backgroundOrigin: oneOf([ 'border-box', 'content-box', 'padding-box' ]),
|
||||
backgroundPosition: string,
|
||||
backgroundRepeat: string,
|
||||
backgroundSize: string,
|
||||
@@ -43,26 +45,26 @@ export default {
|
||||
borderTopWidth: numberOrString,
|
||||
bottom: numberOrString,
|
||||
boxShadow: string,
|
||||
boxSizing: string,
|
||||
boxSizing: oneOf([ 'border-box', 'content-box' ]),
|
||||
clear: string,
|
||||
color: string,
|
||||
cursor: string,
|
||||
display: string,
|
||||
direction: string, /* @private */
|
||||
flex: string, /* @private */
|
||||
flex: number,
|
||||
flexBasis: string,
|
||||
flexDirection: string,
|
||||
flexGrow: numberOrString,
|
||||
flexShrink: numberOrString,
|
||||
flexWrap: string,
|
||||
float: string,
|
||||
flexDirection: oneOf([ 'column', 'column-reverse', 'row', 'row-reverse' ]),
|
||||
flexGrow: number,
|
||||
flexShrink: number,
|
||||
flexWrap: oneOf([ 'nowrap', 'wrap', 'wrap-reverse' ]),
|
||||
float: oneOf([ 'left', 'none', 'right' ]),
|
||||
font: string, /* @private */
|
||||
fontFamily: string,
|
||||
fontSize: numberOrString,
|
||||
fontStyle: string,
|
||||
fontWeight: string,
|
||||
height: numberOrString,
|
||||
justifyContent: string,
|
||||
justifyContent: oneOf([ 'center', 'flex-end', 'flex-start', 'space-around', 'space-between' ]),
|
||||
left: numberOrString,
|
||||
letterSpacing: string,
|
||||
lineHeight: numberOrString,
|
||||
@@ -91,16 +93,17 @@ export default {
|
||||
paddingRight: numberOrString,
|
||||
paddingTop: numberOrString,
|
||||
paddingVertical: numberOrString,
|
||||
position: string,
|
||||
position: oneOf([ 'absolute', 'fixed', 'relative', 'static' ]),
|
||||
right: numberOrString,
|
||||
textAlign: string,
|
||||
textAlign: oneOf([ 'center', 'inherit', 'justify', 'justify-all', 'left', 'right' ]),
|
||||
textDecoration: string,
|
||||
textOverflow: string,
|
||||
textTransform: string,
|
||||
textShadow: string,
|
||||
textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]),
|
||||
top: numberOrString,
|
||||
userSelect: string,
|
||||
verticalAlign: string,
|
||||
visibility: string,
|
||||
visibility: oneOf([ 'hidden', 'visible' ]),
|
||||
whiteSpace: string,
|
||||
width: numberOrString,
|
||||
wordWrap: string,
|
||||
|
||||
@@ -4,7 +4,7 @@ import assert from 'assert'
|
||||
import expandStyle from '../expandStyle'
|
||||
|
||||
suite('modules/StyleSheet/expandStyle', () => {
|
||||
test('style property', () => {
|
||||
test('style resolution', () => {
|
||||
const initial = {
|
||||
borderTopWidth: 1,
|
||||
borderWidth: 2,
|
||||
@@ -13,7 +13,7 @@ suite('modules/StyleSheet/expandStyle', () => {
|
||||
margin: 10
|
||||
}
|
||||
|
||||
const expectedStyle = {
|
||||
const expected = {
|
||||
borderTopWidth: 1,
|
||||
borderLeftWidth: 2,
|
||||
borderRightWidth: 2,
|
||||
@@ -24,6 +24,22 @@ suite('modules/StyleSheet/expandStyle', () => {
|
||||
marginRight: 10
|
||||
}
|
||||
|
||||
assert.deepEqual(expandStyle(initial), expectedStyle)
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
|
||||
test('flex', () => {
|
||||
const value = 10
|
||||
|
||||
const initial = {
|
||||
flex: value
|
||||
}
|
||||
|
||||
const expected = {
|
||||
flexGrow: value,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
}
|
||||
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,24 +8,40 @@ const styles = { root: { borderWidth: 1 } }
|
||||
|
||||
suite('modules/StyleSheet', () => {
|
||||
setup(() => {
|
||||
StyleSheet.destroy()
|
||||
StyleSheet._destroy()
|
||||
})
|
||||
|
||||
test('create', () => {
|
||||
assert.equal(StyleSheet.create(styles), styles)
|
||||
})
|
||||
suite('create', () => {
|
||||
const div = document.createElement('div')
|
||||
|
||||
test('renderToString', () => {
|
||||
StyleSheet.create(styles)
|
||||
assert.equal(
|
||||
StyleSheet.renderToString(),
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 4 unique declarations */\n` +
|
||||
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
|
||||
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
|
||||
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
|
||||
`.borderTopWidth\\:1px{border-top-width:1px;}`
|
||||
)
|
||||
setup(() => {
|
||||
document.body.appendChild(div)
|
||||
StyleSheet.create(styles)
|
||||
div.innerHTML = `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
|
||||
})
|
||||
|
||||
teardown(() => {
|
||||
document.body.removeChild(div)
|
||||
})
|
||||
|
||||
test('returns styles object', () => {
|
||||
assert.equal(StyleSheet.create(styles), styles)
|
||||
})
|
||||
|
||||
test('updates already-rendered style sheet', () => {
|
||||
StyleSheet.create({ root: { color: 'red' } })
|
||||
|
||||
assert.equal(
|
||||
document.getElementById('react-stylesheet').textContent,
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 5 unique declarations */\n` +
|
||||
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
|
||||
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
|
||||
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
|
||||
`.borderTopWidth\\:1px{border-top-width:1px;}\n` +
|
||||
`.color\\:red{color:red;}`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('resolve', () => {
|
||||
@@ -34,4 +50,17 @@ suite('modules/StyleSheet', () => {
|
||||
StyleSheet.create(styles)
|
||||
assert.deepEqual(StyleSheet.resolve(props), expected)
|
||||
})
|
||||
|
||||
test('_renderToString', () => {
|
||||
StyleSheet.create(styles)
|
||||
assert.equal(
|
||||
StyleSheet._renderToString(),
|
||||
`${resetCSS}\n${predefinedCSS}\n` +
|
||||
`/* 4 unique declarations */\n` +
|
||||
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
|
||||
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
|
||||
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
|
||||
`.borderTopWidth\\:1px{border-top-width:1px;}`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -42,6 +42,10 @@ const expandStyle = (style) => {
|
||||
expandedProps.forEach((prop, i) => {
|
||||
resolvedStyle[expandedProps[i]] = value
|
||||
})
|
||||
} else if (key === 'flex') {
|
||||
resolvedStyle.flexGrow = value
|
||||
resolvedStyle.flexShrink = 1
|
||||
resolvedStyle.flexBasis = 'auto'
|
||||
} else {
|
||||
resolvedStyle[key] = value
|
||||
}
|
||||
|
||||
@@ -13,6 +13,24 @@ const initialState = { classNames: predefinedClassNames }
|
||||
const options = { obfuscateClassNames: process.env.NODE_ENV === 'production' }
|
||||
const createStore = () => new Store(initialState, options)
|
||||
let store = createStore()
|
||||
let isRendered = false
|
||||
|
||||
/**
|
||||
* Destroy existing styles
|
||||
*/
|
||||
const _destroy = () => {
|
||||
store = createStore()
|
||||
isRendered = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the styles as a CSS style sheet
|
||||
*/
|
||||
const _renderToString = () => {
|
||||
const css = store.toString()
|
||||
isRendered = true
|
||||
return `${resetCSS}\n${predefinedCSS}\n${css}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all unique declarations
|
||||
@@ -33,24 +51,20 @@ const create = (styles: Object): Object => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// update the style sheet in place
|
||||
if (isRendered) {
|
||||
const stylesheet = document.getElementById('react-stylesheet')
|
||||
if (stylesheet) {
|
||||
stylesheet.textContent = _renderToString()
|
||||
} else if (process.env.NODE_ENV !== 'production') {
|
||||
console.error('ReactNativeWeb: cannot find "react-stylesheet" element')
|
||||
}
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy existing styles
|
||||
*/
|
||||
const destroy = () => {
|
||||
store = createStore()
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the styles as a CSS style sheet
|
||||
*/
|
||||
const renderToString = () => {
|
||||
const css = store.toString()
|
||||
return `${resetCSS}\n${predefinedCSS}\n${css}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts React props and converts inline styles to single purpose classes
|
||||
* where possible.
|
||||
@@ -81,8 +95,8 @@ const resolve = ({ className = '', style = {} }) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
_destroy,
|
||||
_renderToString,
|
||||
create,
|
||||
destroy,
|
||||
renderToString,
|
||||
resolve
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user