Compare commits

...

33 Commits

Author SHA1 Message Date
Nicolas Gallagher
65055028c6 0.0.98 2017-06-07 16:03:26 -07:00
Nicolas Gallagher
93f425e414 [fix] event normalization
Fix #508
2017-06-07 16:02:45 -07:00
Nicolas Gallagher
ce4cc8a946 Remove avoidable vendor code
Updates the 'StyleSheetValidation' and 'ColorPropType' implementations
with the latest from React Native.
2017-06-05 20:06:22 -07:00
Nicolas Gallagher
77fd867421 [fix] correct types
Fix #465
2017-06-05 19:51:34 -07:00
Nicolas Gallagher
22999d7e5b Run lint and test before releasing new versions 2017-06-02 15:29:11 -07:00
Nicolas Gallagher
3c400a662b 0.0.97 2017-06-02 15:27:48 -07:00
Nick
e78ce713cb [fix] TextInput selection for Blink on Android
Close #492
2017-06-01 09:30:22 -07:00
Nicolas Gallagher
70282cb4ca Format 2017-06-01 09:20:07 -07:00
Nicolas Gallagher
7abdb33a1d 0.0.96 2017-06-01 08:51:53 -07:00
Tasveer Singh
a9c7b38df9 [fix] low-level performance tuning
createDOMProps: avoid using default parameters as Babel compiles the
function to calls using 'arguments', which Chrome flags as a deopt.
Replace 'typeof' calls with slightly faster calls to constructor.

onLayout: batch layout measurements in a single requestAnimationFrame.

Close #490
2017-06-01 08:45:40 -07:00
Karan Thakkar
d57fb6407a Fix link to getting started in AppRegistry doc
Fixes #498
2017-06-01 10:50:38 +05:30
Nicolas Gallagher
bcdeda5dab [fix] Flow type checking and annotations
Fixes dozens of Flow errors; adds type annotations; marks more files for
Flow type checking. Fixes a bug in 'AppState'.

15 Flow errors remaining. Several React Native files are still not type
checked (e.g., PanResponder, Touchables)

Ref #465
2017-05-27 10:44:33 -07:00
Nicolas Gallagher
edef737249 Move CONTRIBUTING.md 2017-05-27 08:29:22 -07:00
Nicolas Gallagher
9163b974db Reduce number of dotfiles 2017-05-25 23:10:37 -07:00
Nicolas Gallagher
a388ef3e26 Rename 'examples' to 'docs/storybook'
Also changes several npm script names
2017-05-25 22:22:20 -07:00
Nicolas Gallagher
a84ecefa5d Rename 'performance' to 'benchmarks' 2017-05-25 21:44:01 -07:00
Nicolas Gallagher
54af7e9da2 0.0.95 2017-05-25 13:11:36 -07:00
Peggy Rayzis
be3c78f317 Set up flow config; add third party libdefs 2017-05-25 11:23:58 -07:00
Nicolas Gallagher
6b85f5a22a Use lint-staged to help format & lint before commit 2017-05-25 11:10:21 -07:00
Nicolas Gallagher
875a2c98b3 Use lint-staged to help format & lint before commit 2017-05-25 11:09:17 -07:00
Nicolas Gallagher
6525d9d84a Fix lint errors in 'examples' directory 2017-05-25 11:01:51 -07:00
Nicolas Gallagher
61356a786b Format 'examples' directory 2017-05-24 15:23:13 -07:00
Nicolas Gallagher
864250f34d Format root .js files 2017-05-24 15:21:25 -07:00
Louis Lagrange
7ee570f0ed Add compatibility for BackHandler
Fixes #480
2017-05-22 11:23:59 -07:00
vaukalak
118b64a932 Add docs on platform-specific components 2017-05-22 11:22:02 -07:00
Nicolas Gallagher
3cc1e480a7 Update benchmark dependencies 2017-05-08 14:46:40 +01:00
Nicolas Gallagher
124de7562d Add 'advanced use' and 'style' docs
Fix #450
2017-05-06 16:06:20 +01:00
Nicolas Gallagher
7aef8f04c1 Use class components in benchmarks 2017-05-06 16:06:16 +01:00
Nicolas Gallagher
08a353fbef Update webpack 2017-05-05 10:59:07 -07:00
Nicolas Gallagher
51557d306b Update prettier 2017-05-05 10:58:12 -07:00
Nicolas Gallagher
6b910166b2 Update inline-style-prefixer and fbjs 2017-05-05 10:56:44 -07:00
Nicolas Gallagher
668d389035 Update React devDependencies 2017-05-05 10:51:17 -07:00
周中原
79a6a5a486 Fix presentation of props in 'Button' docs 2017-05-05 10:30:10 -07:00
165 changed files with 3449 additions and 2548 deletions

View File

@@ -1,8 +0,0 @@
{
"presets": [
"react-native"
],
"plugins": [
[ "transform-react-remove-prop-types", { "mode": "wrap" } ]
]
}

View File

@@ -1,9 +0,0 @@
# EditorConfig: http://editorconfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

View File

@@ -24,7 +24,16 @@
"globals": {
"document": false,
"navigator": false,
"window": false
"window": false,
// Flow global types
"HTMLInputElement": false,
"ReactClass": false,
"ReactComponent": false,
"ReactElement": false,
"ReactPropsChainableTypeChecker": false,
"ReactPropsCheckType": false,
"ReactPropTypes": false,
"SyntheticEvent": false
},
"rules": {
"camelcase": 0,
@@ -120,9 +129,6 @@
// react
"react/display-name": 0,
"react/jsx-handler-names": [2, {
"eventHandlerPrefix": "_handle"
}],
"react/jsx-no-bind": 2,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,

13
.flowconfig Normal file
View File

@@ -0,0 +1,13 @@
[ignore]
.*/__tests__/.*
.*/benchmarks/.*
.*/docs/.*
.*/node_modules/animated/*
[include]
[libs]
types
[options]
unsafe.enable_getters_and_setters=true

View File

@@ -6,7 +6,7 @@ Before opening an issue, please search the [issue
tracker](https://github.com/necolas/react-native-web/issues) to make sure your
issue hasn't already been reported.
## Development
## Getting started
Visit the [Issue tracker](https://github.com/necolas/react-native-web/issues)
to find a list of open issues that need attention.
@@ -23,47 +23,78 @@ Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
yarn
```
Run the examples:
## Automated tests
To run flow:
```
npm run examples
npm run flow
```
Run the benchmarks in a browser by opening `./performance/index.html` after running:
To run the unit tests:
```
npm run build:performance
npm run jest
```
### Building
…in watch mode:
```
npm run jest:watch
```
To run all automated tests:
```
npm test
```
## Visual tests
To run the interactive storybook:
```
npm run docs:start
```
To generate a static build of the storybook:
```
npm run docs:build
```
To run the performance benchmarks in a browser (opening `./benchmarks/index.html`):
```
npm run benchmarks
```
## Compile and build
To compile the source code to `dist`:
```
npm run compile
```
To create a UMD bundle of the library:
```
npm run build
```
To create a UMD build:
### Pre-commit
To format and lint code before commit:
```
npm run build:umd
npm run precommit
```
### Testing and Linting
To run the tests:
```
npm run test
```
To continuously watch and run tests, run the following:
```
npm run test:watch
```
To perform only linting, run the following:
To format and lint the entire project:
```
npm run fmt
npm run lint
```
@@ -81,8 +112,10 @@ that we won't want to accept.
* Make sure all tests pass and there are no linting errors.
* Submit a pull request, referencing any issues it addresses.
Please try to keep your pull request focused in scope and avoid including unrelated commits.
Please try to keep your pull request focused in scope and avoid including
unrelated commits.
After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or improvements.
After you have submitted your pull request, we'll try to get back to you as
soon as possible. We may suggest some changes or improvements.
Thank you for contributing!

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
node_modules
dist
dist-examples

View File

@@ -5,4 +5,5 @@ before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script:
- npm run lint
- npm test

View File

@@ -18,35 +18,31 @@ Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web. [Read more](#why).
Browse the UI Explorer to see React Native [examples running on
Web](https://necolas.github.io/react-native-web/storybook/). Or try it out
online with [React Native for Web: Playground](https://www.webpackbin.com/bins/-KgucwxRbn7HRU-V-3Bc).
Browse the [UI Explorer](https://necolas.github.io/react-native-web/storybook/)
to see React Native examples running on Web. Or try it out online with [React
Native for Web: Playground](https://www.webpackbin.com/bins/-KgucwxRbn7HRU-V-3Bc).
## Quick start
To install in your app:
```
npm install --save react@15.4 react-dom@15.4 react-native-web
npm install --save react@15.5 react-dom@15.5 react-native-web
```
Read the [Getting Started](docs/guides/getting-started.md) guide.
Alternatively, you can quickly setup a local project
using [create-react-app](https://github.com/facebookincubator/create-react-app)
(which supports `react-native-web` out-of-the-box once installed) and
[react-native-web-starter](https://github.com/grabcode/react-native-web-starter).
## Documentation
Guides:
* [Getting started](docs/guides/getting-started.md)
* [Style](docs/guides/style.md)
* [Accessibility](docs/guides/accessibility.md)
* [Direct manipulation](docs/guides/direct-manipulation.md)
* [Internationalization](docs/guides/internationalization.md)
* [Advanced use](docs/guides/advanced.md)
* [Known issues](docs/guides/known-issues.md)
* [Style](docs/guides/style.md)
Exported modules:
@@ -87,17 +83,16 @@ Exported modules:
There are many different teams at Twitter building web applications with React.
We want to share React components, libraries, and APIs between teams…much like
the OSS community tries to do. At our scale, this involves dealing with
multiple, inter-related problems including: a common way to handle style,
animation, touch, viewport adaptation, accessibility, themes, RTL layout, and
server-rendering.
multiple, inter-related problems including: component styles, animation, touch
interactions, layout adaptation, accessibility, RTL layout, theming, and build-
or server-rendering.
This is hard to do with React DOM, as the components are essentially the same
low-level building blocks that the browser provides. However, React Native
avoids, solves, or can solve almost all these problems facing Web teams.
Central to this is React Native's JavaScript style API (not strictly
"CSS-in-JS") which avoids the key [problems with
CSS](https://speakerdeck.com/vjeux/react-css-in-js) by giving up some of the
complexity of CSS.
avoids, solves, or can solve almost all these problems. Central to this is
React Native's JavaScript style API (not strictly "CSS-in-JS") which avoids the
key [problems with CSS](https://speakerdeck.com/vjeux/react-css-in-js) by
giving up some of the complexity of CSS.
## Example code
@@ -140,11 +135,11 @@ AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-ro
## Related projects
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack)
* [reactxp](https://github.com/microsoft/reactxp)
* [react-web](https://github.com/taobaofed/react-web)
* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack)
## License

View File

@@ -2,19 +2,19 @@
"name": "performance",
"private": true,
"dependencies": {
"aphrodite": "^1.2.0",
"aphrodite": "^1.2.1",
"classnames": "^2.2.5",
"glamor": "3.0.0-1",
"glamor": "2.20.25",
"marky": "^1.2.0",
"react-jss": "^6.1.1",
"reactxp": "^0.34.3",
"styled-components": "2.0.0-15",
"styletron-client": "^2.5.1",
"styled-components": "2.0.0-17",
"styletron-client": "^2.5.7",
"styletron-utils": "^2.5.4"
},
"devDependencies": {
"css-loader": "^0.28.0",
"css-loader": "^0.28.1",
"react-addons-perf": "^15.4.2",
"style-loader": "^0.16.1"
"style-loader": "^0.17.0"
}
}

View File

@@ -2,7 +2,12 @@
import React from 'react';
import { css, StyleSheet } from 'aphrodite';
const View = ({ style, ...other }) => <div {...other} className={css(styles.root, style)} />;
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={css(styles.root, style)} />;
}
}
const styles = StyleSheet.create({
root: {

View File

@@ -0,0 +1,13 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import React from 'react';
import styles from './styles.css';
class View extends React.Component {
render() {
const props = this.props;
return <div {...props} className={classnames(styles.initial, props.className)} />;
}
}
module.exports = View;

View File

@@ -2,7 +2,12 @@
import { css } from 'glamor';
import React from 'react';
const View = ({ style, ...other }) => <div {...other} className={css(viewStyle, ...style)} />;
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={css(viewStyle, ...style)} />;
}
}
const viewStyle = {
alignItems: 'stretch',

View File

@@ -3,9 +3,12 @@ import classnames from 'classnames';
import injectSheet from 'react-jss';
import React from 'react';
const View = ({ classes, className, ...other }) => (
<div {...other} className={classnames(classes.root, className)} />
);
class View extends React.Component {
render() {
const { classes, className, ...other } = this.props;
return <div {...other} className={classnames(classes.root, className)} />;
}
}
const styles = {
root: {

View File

@@ -2,11 +2,16 @@
import React from 'react';
import StyleSheet from 'react-native/apis/StyleSheet';
import registry from 'react-native/apis/StyleSheet/registry';
import createDOMProps from 'react-native/modules/createDOMProps';
const View = props => (
<div {...createDOMProps(props, style => registry.resolve([styles.root, style]))} />
);
const emptyObject = {};
class View extends React.Component {
render() {
const { style, ...other } = this.props;
const styleProps = registry.resolve([styles.root, style]) || emptyObject;
return <div {...other} {...styleProps} />;
}
}
const styles = StyleSheet.create({
root: {

View File

@@ -6,9 +6,12 @@ import React from 'react';
export const styletron = new Styletron();
const View = ({ style, ...other }) => (
<div {...other} className={classnames(viewStyle, ...style)} />
);
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={classnames(viewStyle, ...style)} />;
}
}
const viewStyle = injectStylePrefixed(styletron, {
alignItems: 'stretch',

View File

@@ -14,9 +14,9 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
aphrodite@^1.2.0:
version "1.2.0"
resolved "https://artifactory.twitter.biz:443/api/npm/js-virtual/aphrodite/-/aphrodite-1.2.0.tgz#c2f30bd1cdf6a550f4a29a0f1cf22ed10e825764"
aphrodite@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/aphrodite/-/aphrodite-1.2.1.tgz#a7b5066b198730be7b7a88f78dbefd77d4df5683"
dependencies:
asap "^2.0.3"
inline-style-prefixer "^3.0.1"
@@ -195,9 +195,9 @@ css-in-js-utils@^1.0.3:
dependencies:
hyphenate-style-name "^1.0.2"
css-loader@^0.28.0:
version "0.28.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.0.tgz#417cfa9789f8cde59a30ccbf3e4da7a806889bad"
css-loader@^0.28.1:
version "0.28.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.1.tgz#220325599f8f00452d9ceb4c3ca6c8a66798642d"
dependencies:
babel-code-frame "^6.11.0"
css-selector-tokenizer "^0.7.0"
@@ -210,6 +210,7 @@ css-loader@^0.28.0:
postcss-modules-local-by-default "^1.0.1"
postcss-modules-scope "^1.0.0"
postcss-modules-values "^1.1.0"
postcss-value-parser "^3.3.0"
source-list-map "^0.1.7"
css-selector-tokenizer@^0.6.0:
@@ -348,13 +349,14 @@ function-bind@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
glamor@3.0.0-1:
version "3.0.0-1"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-3.0.0-1.tgz#60f489e96d96c12620803d3677ac26413cb76a95"
glamor@2.20.25:
version "2.20.25"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.25.tgz#71b84b82b67a9327771ac59de53ee915d148a4a3"
dependencies:
babel-runtime "^6.18.0"
fbjs "^0.8.8"
object-assign "^4.1.0"
prop-types "^15.5.8"
has-ansi@^2.0.0:
version "2.0.0"
@@ -1008,19 +1010,20 @@ strip-ansi@^3.0.0:
dependencies:
ansi-regex "^2.0.0"
style-loader@^0.16.1:
version "0.16.1"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.16.1.tgz#50e325258d4e78421dd9680636b41e8661595d10"
style-loader@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.17.0.tgz#e8254bccdb7af74bd58274e36107b4d5ab4df310"
dependencies:
loader-utils "^1.0.2"
styled-components@2.0.0-15:
version "2.0.0-15"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.0.0-15.tgz#77843c9f5267c60a97e28c97719d1ee89ea28be1"
styled-components@2.0.0-17:
version "2.0.0-17"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.0.0-17.tgz#9d49e5b351f2c3e13698ee00d189a3c951735088"
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
fbjs "^0.8.9"
hoist-non-react-statics "^1.2.0"
inline-style-prefixer "^2.0.5"
is-function "^1.0.1"
is-plain-object "^2.0.1"
@@ -1028,15 +1031,15 @@ styled-components@2.0.0-15:
stylis "^2.0.0"
supports-color "^3.2.3"
styletron-client@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/styletron-client/-/styletron-client-2.5.1.tgz#df0b6fd65965b035d2ff58df61b422aa80e23577"
styletron-client@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/styletron-client/-/styletron-client-2.5.7.tgz#104fa4dc564cd3fe78eb92488e5ef9039c9e242f"
dependencies:
styletron-core "^2.5.1"
styletron-core "^2.5.7"
styletron-core@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/styletron-core/-/styletron-core-2.5.1.tgz#bf9e8aebc41461b81fdd22b1062f6e25862286fd"
styletron-core@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/styletron-core/-/styletron-core-2.5.7.tgz#2c4a1fae537b42235462e438c24ab619bbf8993e"
styletron-utils@^2.5.4:
version "2.5.4"

View File

@@ -1 +1 @@
module.exports = require('./dist/core')
module.exports = require('./dist/core');

View File

@@ -3,8 +3,7 @@
`AppRegistry` is the control point for registering, running, prerendering, and
unmounting all apps. App root components should register themselves with
`AppRegistry.registerComponent`. Apps can be run by invoking
`AppRegistry.runApplication` (see the [client and server rendering
guide](../guides/rendering.md) for more details).
`AppRegistry.runApplication` (see the [getting started guide](../guides/getting-started.md) for more details).
To "stop" an application when a view should be destroyed, call
`AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was passed

View File

@@ -1,9 +1,10 @@
# StyleSheet
The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed)
CSS without requiring a compile-time step. Some styles cannot be resolved
outside of the render loop and are applied as inline styles. Read more about
[how to style your application](../guides/style.md).
CSS without requiring a compile-time step. Styles that cannot be resolved
outside of the render loop (e.g., dynamic positioning) are usually applied as
inline styles. Read more about [how to style your
application](../guides/style.md).
## Methods
@@ -15,7 +16,7 @@ Each key of the object passed to `create` must define a style object.
Flattens an array of styles into a single style object.
**renderToString**: function
(web) **renderToString**: function
Returns a string of the stylesheet for use in server-side rendering.

View File

@@ -23,7 +23,7 @@ If `true`, disable all interactions for this element.
This function is called on press.
testID: ?string
**testID**: ?string
Used to locate this view in end-to-end tests.

104
docs/guides/advanced.md Normal file
View File

@@ -0,0 +1,104 @@
# Advanced use
## Use with existing React DOM components
React Native for Web exports a web-specific module called `createDOMElement`,
which can be used to wrap React DOM components. This allows you to use React
Native's accessibility and style optimizations.
In the example below, `Video` will now accept common React Native props such as
`accessibilityLabel`, `accessible`, `style`, and even the Responder event
props.
```js
import { createDOMElement } from 'react-native';
const Video = (props) => createDOMElement('video', props);
```
This also works with composite components defined in your existing component
gallery or dependencies ([live example](https://www.webpackbin.com/bins/-KiTSGFw3fB9Szg7quLI)).
```js
import RaisedButton from 'material-ui/RaisedButton';
import { createDOMElement, StyleSheet } from 'react-native';
const CustomButton = (props) => createDOMElement(RaisedButton, {
...props,
style: [ styles.button, props.style ]
});
const styles = StyleSheet.create({
button: {
padding: 20
}
});
```
Remember that React Native styles are not the same as React DOM styles, and
care needs to be taken not to pass React DOM styles into your React Native
wrapped components.
## Use as a library framework
The React Native (for Web) building blocks can be used to create higher-level
components and abstractions. In the example below, a `styled` function provides
an API inspired by styled-components ([live
example](https://www.webpackbin.com/bins/-KjT9ziwv4O7FDZdvsnX)).
```js
const { createDOMElement, StyleSheet } = ReactNative;
/**
* styled API
*/
const styled = (Component, styler) => {
const isDOMComponent = typeof Component === 'string';
class Styled extends React.Component {
static contextTypes = {
getTheme: React.PropTypes.func
};
render() {
const theme = this.context.getTheme && this.context.getTheme();
const localProps = { ...this.props, theme };
const nextProps = { ...this.props }
const style = typeof styler === 'function' ? styler(localProps) : styler;
nextProps.style = [ style, this.props.style ];
return (
isDOMComponent
? createDOMElement(Component, nextProps)
: <Component {...nextProps} />
);
}
}
return Styled;
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
backgroundColor: '#2196F3',
flex: 1,
justifyContent: 'center'
}
});
const StyledView = styled(View, styles.container);
```
## Use with react-sketchapp
Use with [react-sketchapp](http://airbnb.io/react-sketchapp/) requires that you
alias `react-native` to `react-sketchapp`. This will allow you to render your
existing React Native components in Sketch. Sketch-specific components like
`Artboard` should be imported from `react-sketchapp`.
If you're using `skpm`, you can rely on an [undocumented
feature](https://github.com/sketch-pm/skpm/blob/master/lib/utils/webpackConfig.js)
which will merge your `webpack.config.js`, `.babelrc`, or `package.json` Babel
config into its internal webpack config. The simplest option may be to use the
[babel-plugin-module-alias](https://www.npmjs.com/package/babel-plugin-module-alias)
and configure it in your `package.json`.

View File

@@ -1,5 +1,14 @@
# Getting started
This guide will help you to correctly configure build and test tools to work
with React Native for Web.
Alternatively, you can quickly setup a local project using
[create-react-app](https://github.com/facebookincubator/create-react-app)
(which supports `react-native-web` out-of-the-box once installed),
[react-native-web-starter](https://github.com/grabcode/react-native-web-starter),
or [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack).
It is recommended that your application provide a `Promise` and `Array.from`
polyfill.
@@ -76,10 +85,6 @@ module.exports = {
}
```
A more complex example setup for web apps can be found in various starter kits
(e.g., create-react-app and
[react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack))
Please refer to the Webpack documentation for more information.
## Jest
@@ -112,6 +117,26 @@ if (Platform.OS === 'web') {
}
```
More significant platform differences should use platform-specific files (see
the webpack configuration above for resolving `*.web.js` files):
For example, with the following files in your project:
```
MyComponent.android.js
MyComponent.ios.js
MyComponent.web.js
```
And the following import:
```js
import MyComponent from './MyComponent';
```
React Native will automatically import the correct variant for each specific
target platform.
## Client-side rendering
Rendering using `ReactNative`:
@@ -144,8 +169,8 @@ AppRegistry.runApplication('App', {
})
```
Rendering within `ReactDOM.render` also works when introduce `react-native-web`
to an existing web app, but it is not recommended oherwise.
Rendering within `ReactDOM.render` also works when introducing
`react-native-web` to an existing web app, but otherwise it is not recommended.
## Server-side rendering
@@ -164,4 +189,15 @@ AppRegistry.registerComponent('App', () => AppContainer)
// prerender the app
const { element, stylesheet } = AppRegistry.getApplication('App', { initialProps });
const initialHTML = ReactDOMServer.renderToString(element);
// construct HTML document
const document = `
<!DOCTYPE html>
<html>
<head>
${stylesheet}
</head>
<body>
${initialHTML}
`
```

View File

@@ -1,73 +1,90 @@
# Style
React Native for Web relies on JavaScript to define styles for your
application. This allows you to avoid issues arising from the [7 deadly sins of
CSS](https://speakerdeck.com/vjeux/react-css-in-js):
React Native relies on JavaScript to define and resolve the styles of your
application. React Native for Web implements the React Native style API in a
way that avoids *all* the [problems with CSS at
scale](https://speakerdeck.com/vjeux/react-css-in-js):
1. Global namespace
2. Dependency hell
1. No local variables
2. Implicit dependencies
3. No dead code elimination
4. No code minification
5. No sharing of constants
6. Non-deterministic resolution
7. Lack of isolation
7. No isolation
At the same time, it has several benefits:
1. Simple API and expressive subset of CSS
2. Generates CSS; the minimum required
3. Good runtime performance
4. Support for static and dynamic styles
5. Support for RTL layouts
6. Easy pre-rendering of critical CSS
## Defining styles
Styles should be defined outside of the component:
Styles should be defined outside of the component. Using `StyleSheet.create` is
optional but provides the best performance (by relying on generated CSS
stylesheets). Avoid creating unregistered style objects.
```js
class Example extends React.Component {}
const styles = StyleSheet.create({
heading: {
color: 'gray',
fontSize: '2rem'
},
text: {
color: 'gray',
fontSize: '1.25rem'
marginTop: '1rem',
margin: 10
}
})
```
Using `StyleSheet.create` is optional but provides the best performance
(`style` is resolved to CSS stylesheets). Avoid creating unregistered style
objects.
The attribute names and values are a subset of CSS. See the `style`
documentation of individual components.
See the `style` documentation of individual components for supported properties.
## Using styles
All the React Native components accept a `style` attribute.
All the React Native components accept a `style` property. The value can be a
registered object, a plain object, or an array.
```js
<Text style={styles.text} />
// registered object
<View style={styles.view} />
// plain object
<View style={{ transform: [ { translateX } ] }} />
// array of registered or plain objects
<View style={[ styles.container, this.props.style ]} />
```
A common pattern is to conditionally add style based on a condition:
The array syntax will merge styles from left-to-right as normal JavaScript
objects, and can be used to conditionally apply styles:
```js
// either
<View style={[
styles.base,
styles.container,
this.state.active && styles.active
]} />
```
When styles are registered with `StyleSheet.create`, the return value is a
number and not a style object. This is important for performance optimizations,
but still allows you to merge styles in a deterministic manner at runtime. If
you need access to the underlying style objects you need to use
`StyleSheet.flatten` (but be aware that this is not the optimized path).
## Composing styles
In order to let a call site customize the style of your component children, you
can pass styles around. Use `View.propTypes.style` and `Text.propTypes.style` in
order to make sure only valid styles are being passed.
To let other components customize the style of a component's children you can
expose a prop so styles can be explicitly passed into the component.
```js
class List extends React.Component {
static propTypes = {
style: View.propTypes.style,
elementStyle: View.propTypes.style,
style: ViewPropTypes.style,
elementStyle: ViewPropTypes.style,
}
render() {
@@ -90,56 +107,125 @@ In another file:
You also have much greater control over how styles are composed when compared
to using class names. For example, you may choose to accept a limited subset
of style props in the component's API, and control when they are applied:
of style props in the component's API, and control when they are applied.
```js
class List extends React.Component {
static propTypes = {
children: React.PropTypes.any,
// limit which styles are accepted
style: React.PropTypes.shape({
borderColor: View.propTypes.borderColor,
borderWidth: View.propTypes.borderWidth
})
}
## How styles are resolved
render() {
return (
<View
children={children}
style={[
this.props.style,
// override border-color when scrolling
isScrolling && { borderColor: 'transparent' }
]}
/>
)
}
}
React Native style resolution is deterministic and slightly different from CSS.
In the following HTML/CSS example, the `.margin` selector is defined last in
the CSS and takes precedence over the previous rules, resulting in a margin of
`0, 0, 0, 0`.
```html
<style>
.marginTop { margin-top: 10px; }
.marginBottom { margin-bottom: 20px; }
.margin { margin: 0; }
</style>
<div class="marginTop marginBottom margin"></div>
```
## Media Queries
But in React Native the most *specific* style property takes precedence,
resulting in margins of `10, 0, 20, 0`.
```js
const style = [
{ marginTop: 10 },
{ marginBottom: 20 },
{ margin: 0 }
];
const Box = () => <View style={style} />
```
## Implementation details
React Native for Web transforms React Native styles into React DOM styles. Any
styles defined using `StyleSheet.create` will ultimately be rendered using CSS
class names.
React Native for Web introduced a novel strategy to achieve this. Each rule is
broken down into declarations, properties are expanded to their long-form, and
the resulting key-value pairs are mapped to unique "atomic CSS" class names.
Input:
```js
const Box = () => <View style={styles.box} />
const styles = StyleSheet.create({
box: {
margin: 0
}
});
```
Output:
```html
<style>
.rn-1mnahxq { margin-top: 0px; }
.rn-61z16t { margin-right: 0px; }
.rn-p1pxzi { margin-bottom: 0px; }
.rn-11wrixw { margin-left: 0px; }
</style>
<div class="rn-156q2ks rn-61z16t rn-p1pxzi rn-11wrixw"></div>
```
This ensures that CSS order doesn't impact rendering and CSS rules are
efficiently deduplicated. Rather than the total CSS growing in proportion to
the number of *rules*, it grows in proportion to the number of *unique
declarations*. As a result, the DOM style sheet is only written to when new
unique declarations are defined and it is usually small enough to be
pre-rendered and inlined.
Class names are deterministic, which means that the resulting CSS and HTML is
consistent across builds important for large apps using code-splitting and
deploying incremental updates.
At runtime registered styles are resolved to DOM style props and memoized.
Any dynamic styles that contain declarations previously registered as static
styles can also be converted to CSS class names. Otherwise, they render as
inline styles.
All this allows React Native for Web to support the rich functionality of React
Native styles (including RTL layouts and `setNativeProps`) while providing one
of the [fastest](https://github.com/necolas/react-native-web/blob/master/benchmarks/README.md),
safest, and most efficient styles-in-JavaScript solutions.
## FAQs
### What about Media Queries?
`StyleSheet.create` is a way of defining the styles your application requires;
it does not concern itself with _where_ or _when_ those styles are applied to
elements.
There are various React libraries wrapping JavaScript Media Query API's, e.g.,
Media Queries may not be most appropriate for component-based designs. React
Native provides the `Dimensions` API and `onLayout` props. If you do need Media
Queries, using the `matchMedia` DOM API has the benefit of allowing you to swap
out entire components, not just styles. There are also many React libraries
wrapping JavaScript Media Query API's, e.g.,
[react-media](https://github.com/reacttraining/react-media),
[react-media-queries](https://github.com/bloodyowl/react-media-queries),
[media-query-fascade](https://github.com/tanem/media-query-facade), or
[react-responsive](https://github.com/contra/react-responsive). This has the
benefit of co-locating breakpoint-specific DOM and style changes.
[react-responsive](https://github.com/contra/react-responsive).
## Pseudo-classes and pseudo-elements
### What about pseudo-classes and pseudo-elements?
Pseudo-classes like `:hover` and `:focus` can be implemented with events (e.g.
`onFocus`). Pseudo-elements are not supported; elements should be used instead.
### Reset
### Do I need a CSS reset?
You **do not** need to include a CSS reset or
[normalize.css](https://necolas.github.io/normalize.css/).
No. React Native for Web includes a very small CSS reset that 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`). The rest is
handled at the component-level.
React Native for Web includes a very small CSS reset taken from 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`).
### What about using DevTools?
It's recommended that you rely more on React DevTools and live/hot-reloading
rather than inspecting and editing the DOM directly.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,12 @@
import { configure, addDecorator } from '@kadira/storybook';
import centered from './decorator-centered';
const context = require.context('../', true, /Example\.js$/);
addDecorator(centered);
function loadStories() {
context.keys().forEach(context);
}
configure(loadStories, module);

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { StyleSheet, View } from 'react-native'
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
root: {
@@ -9,9 +9,9 @@ const styles = StyleSheet.create({
}
});
export default function (renderStory) {
export default function(renderStory) {
return (
<View style={[ StyleSheet.absoluteFill, styles.root ]}>
<View style={[StyleSheet.absoluteFill, styles.root]}>
{renderStory()}
</View>
);

View File

@@ -1,5 +1,5 @@
const path = require('path')
const webpack = require('webpack')
const path = require('path');
const webpack = require('webpack');
const DEV = process.env.NODE_ENV !== 'production';
@@ -27,7 +27,7 @@ module.exports = {
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../../src/module')
'react-native': path.join(__dirname, '../../../src/module')
}
}
}
};

View File

@@ -1,6 +1,6 @@
import { Clipboard, Text, TextInput, View } from 'react-native'
import { Clipboard, Text, TextInput, View } from 'react-native';
import React, { Component } from 'react';
import { action, storiesOf } from '@kadira/storybook';
import { storiesOf } from '@kadira/storybook';
class ClipboardExample extends Component {
render() {
@@ -12,13 +12,17 @@ class ClipboardExample extends Component {
placeholder={'Try pasting here afterwards'}
style={{ borderWidth: 1, height: 200, marginVertical: 20 }}
/>
<Text onPress={this._handleGet}>(Clipboard.getString returns a Promise that always resolves to an empty string on web)</Text>
<Text onPress={this._handleGet}>
(Clipboard.getString returns a Promise that always resolves to an empty string on web)
</Text>
</View>
)
);
}
_handleGet() {
Clipboard.getString().then((value) => { console.log(`Clipboard value: ${value}`) });
Clipboard.getString().then(value => {
console.log(`Clipboard value: ${value}`);
});
}
_handleSet() {
@@ -27,7 +31,4 @@ class ClipboardExample extends Component {
}
}
storiesOf('api: Clipboard', module)
.add('setString', () => (
<ClipboardExample />
));
storiesOf('api: Clipboard', module).add('setString', () => <ClipboardExample />);

View File

@@ -1,50 +1,50 @@
import { storiesOf } from '@kadira/storybook';
import { I18nManager, StyleSheet, TouchableHighlight, Text, View } from 'react-native'
import { I18nManager, StyleSheet, TouchableHighlight, Text, View } from 'react-native';
import React, { Component } from 'react';
class I18nManagerExample extends Component {
componentWillUnmount() {
I18nManager.setPreferredLanguageRTL(false)
I18nManager.setPreferredLanguageRTL(false);
}
render() {
return (
<View style={styles.container}>
<Text accessibilityRole='heading' style={styles.welcome}>
<Text accessibilityRole="heading" style={styles.welcome}>
LTR/RTL layout example!
</Text>
<Text style={styles.text}>
The writing direction of text is automatically determined by the browser, independent of the global writing direction of the app.
</Text>
<Text style={[ styles.text, styles.rtlText ]}>
<Text style={[styles.text, styles.rtlText]}>
أحب اللغة العربية
</Text>
<Text style={[ styles.text, styles.textAlign ]}>
<Text style={[styles.text, styles.textAlign]}>
textAlign toggles
</Text>
<View style={styles.horizontal}>
<View style={[ styles.box, { backgroundColor: 'lightblue' } ]}>
<View style={[styles.box, { backgroundColor: 'lightblue' }]}>
<Text>One</Text>
</View>
<View style={[ styles.box ]}>
<View style={[styles.box]}>
<Text>Two</Text>
</View>
</View>
<TouchableHighlight
onPress={this._handleToggle}
style={styles.toggle}
underlayColor='rgba(0,0,0,0.25)'
underlayColor="rgba(0,0,0,0.25)"
>
<Text>Toggle LTR/RTL</Text>
</TouchableHighlight>
</View>
)
);
}
_handleToggle = () => {
I18nManager.setPreferredLanguageRTL(!I18nManager.isRTL)
I18nManager.setPreferredLanguageRTL(!I18nManager.isRTL);
this.forceUpdate();
}
};
}
const styles = StyleSheet.create({
@@ -82,9 +82,6 @@ const styles = StyleSheet.create({
marginTop: 10,
padding: 10
}
})
});
storiesOf('api: I18nManager', module)
.add('RTL layout', () => (
<I18nManagerExample />
))
storiesOf('api: I18nManager', module).add('RTL layout', () => <I18nManagerExample />);

View File

@@ -1,12 +1,12 @@
import { Linking, StyleSheet, Text, View } from 'react-native'
import { Linking, StyleSheet, Text, View } from 'react-native';
import React, { Component } from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { storiesOf } from '@kadira/storybook';
const url = 'https://mathiasbynens.github.io/rel-noopener/malicious.html';
class LinkingExample extends Component {
handlePress() {
Linking.canOpenURL(url).then((supported) => {
Linking.canOpenURL(url).then(supported => {
return Linking.openURL(url);
});
}
@@ -17,7 +17,12 @@ class LinkingExample extends Component {
<Text onPress={this.handlePress} style={styles.text}>
Linking.openURL (Expect: "The previous tab is safe and intact")
</Text>
<Text accessibilityRole='link' href='https://mathiasbynens.github.io/rel-noopener/malicious.html' style={styles.text} target='_blank'>
<Text
accessibilityRole="link"
href="https://mathiasbynens.github.io/rel-noopener/malicious.html"
style={styles.text}
target="_blank"
>
target="_blank" (Expect: "The previous tab is safe and intact")
</Text>
</View>
@@ -31,7 +36,4 @@ const styles = StyleSheet.create({
}
});
storiesOf('api: Linking', module)
.add('Safe linking', () => (
<LinkingExample />
));
storiesOf('api: Linking', module).add('Safe linking', () => <LinkingExample />);

View File

@@ -1,24 +1,18 @@
'use strict';
/* eslint-disable react/jsx-no-bind */
import createReactClass from 'create-react-class';
import { storiesOf, action } from '@kadira/storybook';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { PanResponder, StyleSheet, View } from 'react-native';
var React = require('react');
var ReactNative = require('react-native');
var {
PanResponder,
StyleSheet,
View
} = ReactNative;
const CIRCLE_SIZE = 80;
var CIRCLE_SIZE = 80;
var PanResponderExample = createReactClass({
const PanResponderExample = createReactClass({
_panResponder: {},
_previousLeft: 0,
_previousTop: 0,
_circleStyles: {},
circle: (null : ?{ setNativeProps(props: Object): void }),
circle: (null: ?{ setNativeProps(props: Object): void }),
componentWillMount: function() {
this._panResponder = PanResponder.create({
@@ -27,7 +21,7 @@ var PanResponderExample = createReactClass({
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd
});
this._previousLeft = 20;
this._previousTop = 84;
@@ -35,7 +29,7 @@ var PanResponderExample = createReactClass({
style: {
left: this._previousLeft,
top: this._previousTop,
backgroundColor: 'green',
backgroundColor: 'green'
}
};
},
@@ -46,10 +40,9 @@ var PanResponderExample = createReactClass({
render: function() {
return (
<View
style={styles.container}>
<View style={styles.container}>
<View
ref={(circle) => {
ref={circle => {
this.circle = circle;
}}
style={styles.circle}
@@ -95,23 +88,21 @@ var PanResponderExample = createReactClass({
this._unHighlight();
this._previousLeft += gestureState.dx;
this._previousTop += gestureState.dy;
},
}
});
var styles = StyleSheet.create({
const styles = StyleSheet.create({
circle: {
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
borderRadius: CIRCLE_SIZE / 2,
position: 'absolute',
top: 0,
top: 0
},
container: {
flex: 1,
paddingTop: 64,
},
paddingTop: 64
}
});
storiesOf('api: PanResponder', module)
.add('example', () => <PanResponderExample />)
storiesOf('api: PanResponder', module).add('example', () => <PanResponderExample />);

View File

@@ -1,9 +1,3 @@
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { ActivityIndicator, StyleSheet, View } from 'react-native'
import TimerMixin from 'react-timer-mixin';
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
@@ -27,18 +21,24 @@ import TimerMixin from 'react-timer-mixin';
* @flow
*/
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import TimerMixin from 'react-timer-mixin';
const ToggleAnimatingActivityIndicator = createReactClass({
mixins: [TimerMixin],
getInitialState() {
return {
animating: true,
animating: true
};
},
setToggleTimeout() {
this.setTimeout(() => {
this.setState({animating: !this.state.animating});
this.setState({ animating: !this.state.animating });
this.setToggleTimeout();
}, 2000);
},
@@ -51,9 +51,9 @@ const ToggleAnimatingActivityIndicator = createReactClass({
return (
<ActivityIndicator
animating={this.state.animating}
style={styles.centering}
hidesWhenStopped={this.props.hidesWhenStopped}
size="large"
style={styles.centering}
/>
);
}
@@ -63,11 +63,7 @@ const examples = [
{
title: 'Default',
render() {
return (
<ActivityIndicator
style={[styles.centering]}
/>
);
return <ActivityIndicator style={[styles.centering]} />;
}
},
{
@@ -87,11 +83,7 @@ const examples = [
title: 'Large',
render() {
return (
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="white"
size="large"
/>
<ActivityIndicator color="white" size="large" style={[styles.centering, styles.gray]} />
);
}
},
@@ -100,22 +92,10 @@ const examples = [
render() {
return (
<View style={styles.horizontal}>
<ActivityIndicator
size="large"
color="#0000ff"
/>
<ActivityIndicator
size="large"
color="#aa00aa"
/>
<ActivityIndicator
size="large"
color="#aa3300"
/>
<ActivityIndicator
size="large"
color="#00aa00"
/>
<ActivityIndicator color="#0000ff" size="large" />
<ActivityIndicator color="#aa00aa" size="large" />
<ActivityIndicator color="#aa3300" size="large" />
<ActivityIndicator color="#00aa00" size="large" />
</View>
);
}
@@ -137,33 +117,29 @@ const examples = [
return (
<View style={[styles.horizontal, styles.centering]}>
<ActivityIndicator size={40} />
<ActivityIndicator
style={{ marginLeft: 20, transform: [ {scale: 1.5} ] }}
size="large"
/>
<ActivityIndicator size="large" style={{ marginLeft: 20, transform: [{ scale: 1.5 }] }} />
</View>
);
}
},
}
];
const styles = StyleSheet.create({
centering: {
alignItems: 'center',
justifyContent: 'center',
padding: 8,
padding: 8
},
gray: {
backgroundColor: '#cccccc',
backgroundColor: '#cccccc'
},
horizontal: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 8,
},
padding: 8
}
});
examples.forEach((example) => {
storiesOf('component: ActivityIndicator', module)
.add(example.title, () => example.render())
})
examples.forEach(example => {
storiesOf('component: ActivityIndicator', module).add(example.title, () => example.render());
});

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { action, storiesOf } from '@kadira/storybook';
import { Button, StyleSheet, View } from 'react-native';
import { Button, View } from 'react-native';
const onButtonPress = action('Button has been pressed!');
@@ -13,12 +13,12 @@ const examples = [
render: function() {
return (
<Button
accessibilityLabel="See an informative alert"
onPress={onButtonPress}
title="Press Me"
accessibilityLabel="See an informative alert"
/>
);
},
}
},
{
title: 'Adjusted color',
@@ -28,35 +28,34 @@ const examples = [
render: function() {
return (
<Button
accessibilityLabel="Learn more about purple"
color="#841584"
onPress={onButtonPress}
title="Press Purple"
color="#841584"
accessibilityLabel="Learn more about purple"
/>
);
},
}
},
{
title: 'Fit to text layout',
description: 'This layout strategy lets the title define the width of ' +
'the button',
description: 'This layout strategy lets the title define the width of the button',
render: function() {
return (
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Button
accessibilityLabel="This sounds great!"
onPress={onButtonPress}
title="This looks great!"
accessibilityLabel="This sounds great!"
/>
<Button
accessibilityLabel="Ok, Great!"
color="#841584"
onPress={onButtonPress}
title="Ok!"
color="#841584"
accessibilityLabel="Ok, Great!"
/>
</View>
);
},
}
},
{
title: 'Disabled Button',
@@ -64,17 +63,16 @@ const examples = [
render: function() {
return (
<Button
accessibilityLabel="See an informative alert"
disabled
onPress={onButtonPress}
title="I Am Disabled"
accessibilityLabel="See an informative alert"
/>
);
},
},
}
}
];
examples.forEach((example) => {
storiesOf('component: Button', module)
.add(example.title, () => example.render());
examples.forEach(example => {
storiesOf('component: Button', module).add(example.title, () => example.render());
});

View File

@@ -1,7 +1,4 @@
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf, action, addDecorator } from '@kadira/storybook';
import { ActivityIndicator, Image, Platform, StyleSheet, Text, View } from 'react-native'
/* eslint-disable react/jsx-no-bind */
/**
* Copyright (c) 2013-present, Facebook, Inc.
@@ -26,56 +23,69 @@ import { ActivityIndicator, Image, Platform, StyleSheet, Text, View } from 'reac
* @flow
*/
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { ActivityIndicator, Image, StyleSheet, Text, View } from 'react-native';
const base64Icon =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
//var ImageCapInsetsExample = require('./ImageCapInsetsExample');
const IMAGE_PREFETCH_URL = 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now();
var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
const prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
var NetworkImageCallbackExample = createReactClass({
const NetworkImageCallbackExample = createReactClass({
getInitialState: function() {
return {
events: [],
startLoadPrefetched: false,
mountTime: new Date(),
mountTime: new Date()
};
},
componentWillMount() {
this.setState({mountTime: new Date()});
this.setState({ mountTime: new Date() });
},
render: function() {
var { mountTime } = this.state;
const { mountTime } = this.state;
return (
<View>
<Image
source={this.props.source}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)}
onLoadEnd={() => {
this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`);
this.setState({startLoadPrefetched: true}, () => {
prefetchTask.then(() => {
this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`);
}, error => {
this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`);
});
this.setState({ startLoadPrefetched: true }, () => {
prefetchTask.then(
() => {
this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`);
},
error => {
this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`);
console.log(error);
}
);
});
}}
onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
source={this.props.source}
style={[styles.base, { overflow: 'visible' }]}
/>
{this.state.startLoadPrefetched ?
<Image
source={this.props.prefetchedSource}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={() => this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)}
onLoad={() => this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`)}
onLoadEnd={() => this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)}
/>
{this.state.startLoadPrefetched
? <Image
onLoad={() =>
this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`)}
onLoadEnd={() =>
this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)}
onLoadStart={() =>
this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)}
source={this.props.prefetchedSource}
style={[styles.base, { overflow: 'visible' }]}
/>
: null}
<Text style={{marginTop: 20}}>
<Text style={{ marginTop: 20 }}>
{this.state.events.join('\n')}
</Text>
</View>
@@ -83,13 +93,13 @@ var NetworkImageCallbackExample = createReactClass({
},
_loadEventFired(event) {
this.setState((state) => {
return state.events = [...state.events, event];
this.setState(state => {
return (state.events = [...state.events, event]);
});
}
});
var NetworkImageExample = createReactClass({
const NetworkImageExample = createReactClass({
getInitialState: function() {
return {
error: false,
@@ -98,35 +108,40 @@ var NetworkImageExample = createReactClass({
};
},
render: function() {
var loader = this.state.loading ?
<View style={styles.progress}>
<Text>{this.state.progress}%</Text>
<ActivityIndicator style={{marginLeft:5}} />
</View> : null;
return this.state.error ?
<Text>{this.state.error}</Text> :
<Image
source={this.props.source}
style={[styles.base, {overflow: 'visible'}]}
onLoadStart={(e) => this.setState({loading: true})}
onError={(e) => this.setState({error: e.nativeEvent.error, loading: false})}
onProgress={(e) => this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})}
onLoad={() => this.setState({loading: false, error: false})}>
{loader}
</Image>;
const loader = this.state.loading
? <View style={styles.progress}>
<Text>{this.state.progress}%</Text>
<ActivityIndicator style={{ marginLeft: 5 }} />
</View>
: null;
return this.state.error
? <Text>{this.state.error}</Text>
: <Image
onError={e => this.setState({ error: e.nativeEvent.error, loading: false })}
onLoad={() => this.setState({ loading: false, error: false })}
onLoadStart={e => this.setState({ loading: true })}
onProgress={e =>
this.setState({
progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)
})}
source={this.props.source}
style={[styles.base, { overflow: 'visible' }]}
>
{loader}
</Image>;
}
});
var ImageSizeExample = createReactClass({
const ImageSizeExample = createReactClass({
getInitialState: function() {
return {
width: 0,
height: 0,
height: 0
};
},
componentDidMount: function() {
Image.getSize(this.props.source.uri, (width, height) => {
this.setState({width, height});
this.setState({ width, height });
});
},
render: function() {
@@ -147,7 +162,7 @@ var ImageSizeExample = createReactClass({
/>
</View>
);
},
}
});
/*
@@ -213,20 +228,24 @@ const examples = [
{
title: 'Plain Network Image',
description: 'If the `source` prop `uri` property is prefixed with ' +
'"http", then it will be downloaded from the network.',
'"http", then it will be downloaded from the network.',
render: function() {
return (
<Image
source={{ uri: 'http://facebook.github.io/react/img/logo_og.png', width: 1200, height: 630 }}
source={{
uri: 'http://facebook.github.io/react/img/logo_og.png',
width: 1200,
height: 630
}}
style={styles.base}
/>
);
},
}
},
{
title: 'Plain Static Image',
description: 'Static assets should be placed in the source code tree, and ' +
'required in the same way as JavaScript modules.',
'required in the same way as JavaScript modules.',
render: function() {
return (
<View style={styles.horizontal}>
@@ -236,36 +255,40 @@ const examples = [
{/*<Image source={require('./uie_comment_highlighted.png')} style={styles.icon} />*/}
</View>
);
},
}
},
{
title: 'Image Loading Events',
render: function() {
return (
<NetworkImageCallbackExample
source={{uri: 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now()}}
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}
prefetchedSource={{ uri: IMAGE_PREFETCH_URL }}
source={{ uri: 'http://origami.design/public/images/bird-logo.png?r=1&t=' + Date.now() }}
/>
);
},
}
},
{
title: 'Error Handler',
render: function() {
return (
<NetworkImageExample source={{uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png'}} />
<NetworkImageExample
source={{ uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png' }}
/>
);
},
platform: 'ios',
platform: 'ios'
},
{
title: 'Image Download Progress',
render: function() {
return (
<NetworkImageExample source={{uri: 'http://origami.design/public/images/bird-logo.png?r=1'}}/>
<NetworkImageExample
source={{ uri: 'http://origami.design/public/images/bird-logo.png?r=1' }}
/>
);
},
platform: 'ios',
platform: 'ios'
},
{
title: 'defaultSource',
@@ -274,12 +297,12 @@ const examples = [
return (
<Image
defaultSource={require('./bunny.png')}
source={{uri: 'http://facebook.github.io/origami/public/images/birds.jpg'}}
source={{ uri: 'http://facebook.github.io/origami/public/images/birds.jpg' }}
style={styles.base}
/>
);
},
platform: 'ios',
platform: 'ios'
},
{
title: 'Border Color',
@@ -288,15 +311,11 @@ const examples = [
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[
styles.base,
styles.background,
{borderWidth: 3, borderColor: '#f099f0'}
]}
style={[styles.base, styles.background, { borderWidth: 3, borderColor: '#f099f0' }]}
/>
</View>
);
},
}
},
{
title: 'Border Width',
@@ -305,32 +324,25 @@ const examples = [
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[
styles.base,
styles.background,
{borderWidth: 5, borderColor: '#f099f0'}
]}
style={[styles.base, styles.background, { borderWidth: 5, borderColor: '#f099f0' }]}
/>
</View>
);
},
}
},
{
title: 'Border Radius',
render: function() {
return (
<View style={styles.horizontal}>
<Image source={fullImage} style={[styles.base, { borderRadius: 5 }]} />
<Image
style={[styles.base, {borderRadius: 5}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {borderRadius: 19}]}
source={fullImage}
style={[styles.base, styles.leftMargin, { borderRadius: 19 }]}
/>
</View>
);
},
}
},
{
title: 'Background Color',
@@ -339,71 +351,47 @@ const examples = [
<View style={styles.horizontal}>
<Image source={smallImage} style={styles.base} />
<Image
style={[
styles.base,
styles.leftMargin,
{backgroundColor: 'rgba(0, 0, 100, 0.25)'}
]}
source={smallImage}
style={[styles.base, styles.leftMargin, { backgroundColor: 'rgba(0, 0, 100, 0.25)' }]}
/>
<Image
style={[styles.base, styles.leftMargin, {backgroundColor: 'red'}]}
source={smallImage}
style={[styles.base, styles.leftMargin, { backgroundColor: 'red' }]}
/>
<Image
style={[styles.base, styles.leftMargin, {backgroundColor: 'black'}]}
source={smallImage}
style={[styles.base, styles.leftMargin, { backgroundColor: 'black' }]}
/>
</View>
);
},
}
},
{
title: 'Opacity',
render: function() {
return (
<View style={styles.horizontal}>
<Image
style={[styles.base, {opacity: 1}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.8}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.6}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.4}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0.2}]}
source={fullImage}
/>
<Image
style={[styles.base, styles.leftMargin, {opacity: 0}]}
source={fullImage}
/>
<Image source={fullImage} style={[styles.base, { opacity: 1 }]} />
<Image source={fullImage} style={[styles.base, styles.leftMargin, { opacity: 0.8 }]} />
<Image source={fullImage} style={[styles.base, styles.leftMargin, { opacity: 0.6 }]} />
<Image source={fullImage} style={[styles.base, styles.leftMargin, { opacity: 0.4 }]} />
<Image source={fullImage} style={[styles.base, styles.leftMargin, { opacity: 0.2 }]} />
<Image source={fullImage} style={[styles.base, styles.leftMargin, { opacity: 0 }]} />
</View>
);
},
}
},
{
title: 'Nesting',
render: function() {
return (
<Image
style={{width: 60, height: 60, backgroundColor: 'transparent'}}
source={fullImage}>
<Image source={fullImage} style={{ width: 60, height: 60, backgroundColor: 'transparent' }}>
<Text style={styles.nestedText}>
React
</Text>
</Image>
);
},
}
},
/*
{
@@ -466,91 +454,80 @@ const examples = [
<View>
{[smallImage, fullImage].map((image, index) => {
return (
<View key={index}>
<View style={styles.horizontal}>
<View>
<Text style={[styles.resizeModeText]}>
Contain
</Text>
<Image
style={styles.resizeMode}
resizeMode={Image.resizeMode.contain}
source={image}
/>
<View key={index}>
<View style={styles.horizontal}>
<View>
<Text style={[styles.resizeModeText]}>
Contain
</Text>
<Image
resizeMode={Image.resizeMode.contain}
source={image}
style={styles.resizeMode}
/>
</View>
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>
Cover
</Text>
<Image
resizeMode={Image.resizeMode.cover}
source={image}
style={styles.resizeMode}
/>
</View>
</View>
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>
Cover
</Text>
<Image
style={styles.resizeMode}
resizeMode={Image.resizeMode.cover}
source={image}
/>
<View style={styles.horizontal}>
<View>
<Text style={[styles.resizeModeText]}>
Stretch
</Text>
<Image
resizeMode={Image.resizeMode.stretch}
source={image}
style={styles.resizeMode}
/>
</View>
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>
Repeat
</Text>
<Image resizeMode={'repeat'} source={image} style={styles.resizeMode} />
</View>
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>
Center
</Text>
<Image resizeMode={'center'} source={image} style={styles.resizeMode} />
</View>
</View>
</View>
<View style={styles.horizontal}>
<View>
<Text style={[styles.resizeModeText]}>
Stretch
</Text>
<Image
style={styles.resizeMode}
resizeMode={Image.resizeMode.stretch}
source={image}
/>
</View>
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>
Repeat
</Text>
<Image
style={styles.resizeMode}
resizeMode={'repeat'}
source={image}
/>
</View>
<View style={styles.leftMargin}>
<Text style={[styles.resizeModeText]}>
Center
</Text>
<Image
style={styles.resizeMode}
resizeMode={'center'}
source={image}
/>
</View>
</View>
</View>
);
})}
);
})}
</View>
);
},
}
},
{
title: 'Animated GIF',
render: function() {
return (
<Image
source={{
uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'
}}
style={styles.gif}
source={{uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'}}
/>
);
},
platform: 'ios',
platform: 'ios'
},
{
title: 'Base64 image',
render: function() {
return (
<Image
style={styles.base64}
source={{uri: base64Icon, scale: 3}}
/>
);
return <Image source={{ uri: base64Icon, scale: 3 }} style={styles.base64} />;
},
platform: 'ios',
platform: 'ios'
},
/*
{
@@ -569,9 +546,15 @@ const examples = [
{
title: 'Image Size',
render: function() {
return <ImageSizeExample source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/d/d7/Chestnut-mandibled_Toucan.jpg' }} />;
},
},
return (
<ImageSizeExample
source={{
uri: 'https://upload.wikimedia.org/wikipedia/commons/d/d7/Chestnut-mandibled_Toucan.jpg'
}}
/>
);
}
}
/*
{
title: 'MultipleSourcesExample',
@@ -586,13 +569,13 @@ const examples = [
*/
];
var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'};
var smallImage = {uri: 'http://facebook.github.io/react/img/logo_small_2x.png'};
const fullImage = { uri: 'http://facebook.github.io/react/img/logo_og.png' };
const smallImage = { uri: 'http://facebook.github.io/react/img/logo_small_2x.png' };
var styles = StyleSheet.create({
const styles = StyleSheet.create({
base: {
width: 38,
height: 38,
height: 38
},
progress: {
flex: 1,
@@ -601,13 +584,13 @@ var styles = StyleSheet.create({
width: 100
},
leftMargin: {
marginLeft: 10,
marginLeft: 10
},
background: {
backgroundColor: '#222222'
},
sectionText: {
marginVertical: 6,
marginVertical: 6
},
nestedText: {
marginLeft: 12,
@@ -623,32 +606,32 @@ var styles = StyleSheet.create({
},
resizeModeText: {
fontSize: 11,
marginBottom: 3,
marginBottom: 3
},
icon: {
width: 15,
height: 15,
height: 15
},
horizontal: {
flexDirection: 'row',
flexDirection: 'row'
},
gif: {
flex: 1,
height: 200,
height: 200
},
base64: {
flex: 1,
height: 50,
resizeMode: 'contain',
resizeMode: 'contain'
},
touchableText: {
fontWeight: '500',
color: 'blue',
},
color: 'blue'
}
});
examples.forEach((example) => {
examples.forEach(example => {
storiesOf('component: Image', module)
.addDecorator((renderStory) => <View style={{ width: '100%' }}>{renderStory()}</View>)
.add(example.title, () => example.render())
})
.addDecorator(renderStory => <View style={{ width: '100%' }}>{renderStory()}</View>)
.add(example.title, () => example.render());
});

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 850 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { ListView, StyleSheet, Text, View } from 'react-native';
const generateData = (length) => Array.from({ length }).map((item, i) => i);
const generateData = length => Array.from({ length }).map((item, i) => i);
const dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
storiesOf('component: ListView', module)
@@ -13,11 +13,11 @@ storiesOf('component: ListView', module)
dataSource={dataSource.cloneWithRows(generateData(100))}
initialListSize={100}
// eslint-disable-next-line react/jsx-no-bind
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
onScroll={e => {
console.log('ScrollView.onScroll', e);
}}
// eslint-disable-next-line react/jsx-no-bind
renderRow={(row) => (
<View><Text>{row}</Text></View>
)}
renderRow={row => <View><Text>{row}</Text></View>}
scrollEventThrottle={1000} // 1 event per second
style={styles.scrollViewStyle}
/>
@@ -30,12 +30,12 @@ storiesOf('component: ListView', module)
dataSource={dataSource.cloneWithRows(generateData(5000))}
initialListSize={100}
// eslint-disable-next-line react/jsx-no-bind
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
onScroll={e => {
console.log('ScrollView.onScroll', e);
}}
pageSize={50}
// eslint-disable-next-line react/jsx-no-bind
renderRow={(row) => (
<View><Text>{row}</Text></View>
)}
renderRow={row => <View><Text>{row}</Text></View>}
scrollEventThrottle={1000} // 1 event per second
style={styles.scrollViewStyle}
/>
@@ -48,12 +48,12 @@ storiesOf('component: ListView', module)
dataSource={dataSource.cloneWithRows(generateData(5000))}
initialListSize={5}
// eslint-disable-next-line react/jsx-no-bind
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
onScroll={e => {
console.log('ScrollView.onScroll', e);
}}
pageSize={1}
// eslint-disable-next-line react/jsx-no-bind
renderRow={(row) => (
<View><Text>{row}</Text></View>
)}
renderRow={row => <View><Text>{row}</Text></View>}
scrollEventThrottle={1000} // 1 event per second
style={styles.scrollViewStyle}
/>

View File

@@ -1,7 +1,7 @@
import createReactClass from 'create-react-class';
import { ProgressBar, StyleSheet, View } from 'react-native'
import { ProgressBar, StyleSheet, View } from 'react-native';
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { storiesOf } from '@kadira/storybook';
import TimerMixin from 'react-timer-mixin';
/**
@@ -27,12 +27,12 @@ import TimerMixin from 'react-timer-mixin';
* @flow
*/
var ProgressBarExample = createReactClass({
const ProgressBarExample = createReactClass({
mixins: [TimerMixin],
getInitialState() {
return {
progress: 0,
progress: 0
};
},
@@ -41,49 +41,48 @@ var ProgressBarExample = createReactClass({
},
updateProgress() {
var progress = this.state.progress + 0.01;
const progress = this.state.progress + 0.01;
this.setState({ progress });
this.requestAnimationFrame(() => this.updateProgress());
},
getProgress(offset) {
var progress = this.state.progress + offset;
const progress = this.state.progress + offset;
return Math.sin(progress % Math.PI) % 1;
},
render() {
return (
<View style={styles.container}>
<ProgressBar style={styles.progressView} color="purple" progress={this.getProgress(0.2)} />
<ProgressBar style={styles.progressView} color="red" progress={this.getProgress(0.4)} />
<ProgressBar style={styles.progressView} color="orange" progress={this.getProgress(0.6)} />
<ProgressBar style={styles.progressView} color="yellow" progress={this.getProgress(0.8)} />
<ProgressBar color="purple" progress={this.getProgress(0.2)} style={styles.progressView} />
<ProgressBar color="red" progress={this.getProgress(0.4)} style={styles.progressView} />
<ProgressBar color="orange" progress={this.getProgress(0.6)} style={styles.progressView} />
<ProgressBar color="yellow" progress={this.getProgress(0.8)} style={styles.progressView} />
</View>
);
},
}
});
const examples = [{
title: 'progress',
render() {
return (
<ProgressBarExample />
);
const examples = [
{
title: 'progress',
render() {
return <ProgressBarExample />;
}
},
}, {
title: 'indeterminate',
render() {
return (
<ProgressBar indeterminate style={styles.progressView} trackColor='#D1E3F6' />
);
{
title: 'indeterminate',
render() {
return <ProgressBar indeterminate style={styles.progressView} trackColor="#D1E3F6" />;
}
}
}];
];
var styles = StyleSheet.create({
const styles = StyleSheet.create({
container: {
minWidth: 200,
marginTop: -20,
backgroundColor: 'transparent',
backgroundColor: 'transparent'
},
progressView: {
marginTop: 20,
@@ -91,7 +90,6 @@ var styles = StyleSheet.create({
}
});
examples.forEach((example) => {
storiesOf('component: ProgressBar', module)
.add(example.title, () => example.render())
})
examples.forEach(example => {
storiesOf('component: ProgressBar', module).add(example.title, () => example.render());
});

View File

@@ -1,6 +1,8 @@
/* eslint-disable react/jsx-no-bind */
import React from 'react';
import { action, storiesOf } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'
import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native';
const onScroll = action('ScrollView.onScroll');
@@ -31,13 +33,13 @@ storiesOf('component: ScrollView', module)
style={styles.scrollViewStyle}
>
{Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
<View key={i} style={[styles.box, styles.horizontalBox]}>
<Text>{i}</Text>
</View>
))}
</ScrollView>
</View>
))
));
const styles = StyleSheet.create({
box: {
@@ -56,4 +58,4 @@ const styles = StyleSheet.create({
backgroundColor: '#eee',
padding: 10
}
})
});

View File

@@ -1,7 +1,4 @@
import createReactClass from 'create-react-class';
import { Platform, Switch, Text, View } from 'react-native'
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
/* eslint-disable react/jsx-no-bind */
/**
* Copyright (c) 2013-present, Facebook, Inc.
@@ -26,23 +23,28 @@ import { storiesOf, action } from '@kadira/storybook';
* @flow
*/
var BasicSwitchExample = createReactClass({
import createReactClass from 'create-react-class';
import { Switch, Text, View } from 'react-native';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
const BasicSwitchExample = createReactClass({
getInitialState() {
return {
trueSwitchIsOn: true,
falseSwitchIsOn: false,
falseSwitchIsOn: false
};
},
render() {
return (
<View>
<Switch
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
style={{marginBottom: 10}}
onValueChange={value => this.setState({ falseSwitchIsOn: value })}
style={{ marginBottom: 10 }}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
onValueChange={value => this.setState({ trueSwitchIsOn: value })}
value={this.state.trueSwitchIsOn}
/>
</View>
@@ -50,27 +52,22 @@ var BasicSwitchExample = createReactClass({
}
});
var DisabledSwitchExample = createReactClass({
const DisabledSwitchExample = createReactClass({
render() {
return (
<View>
<Switch
disabled={true}
style={{marginBottom: 10}}
value={true} />
<Switch
disabled={true}
value={false} />
<Switch disabled={true} style={{ marginBottom: 10 }} value={true} />
<Switch disabled={true} value={false} />
</View>
);
},
}
});
var ColorSwitchExample = createReactClass({
const ColorSwitchExample = createReactClass({
getInitialState() {
return {
colorTrueSwitchIsOn: true,
colorFalseSwitchIsOn: false,
colorFalseSwitchIsOn: false
};
},
render() {
@@ -79,28 +76,28 @@ var ColorSwitchExample = createReactClass({
<Switch
activeThumbColor="#428bca"
activeTrackColor="#A0C4E3"
onValueChange={(value) => this.setState({colorFalseSwitchIsOn: value})}
style={{marginBottom: 10}}
onValueChange={value => this.setState({ colorFalseSwitchIsOn: value })}
style={{ marginBottom: 10 }}
value={this.state.colorFalseSwitchIsOn}
/>
<Switch
activeThumbColor="#5CB85C"
activeTrackColor="#ADDAAD"
onValueChange={(value) => this.setState({colorTrueSwitchIsOn: value})}
onValueChange={value => this.setState({ colorTrueSwitchIsOn: value })}
thumbColor="#EBA9A7"
trackColor="#D9534F"
value={this.state.colorTrueSwitchIsOn}
/>
</View>
);
},
}
});
var EventSwitchExample = createReactClass({
const EventSwitchExample = createReactClass({
getInitialState() {
return {
eventSwitchIsOn: false,
eventSwitchRegressionIsOn: true,
eventSwitchRegressionIsOn: true
};
},
render() {
@@ -108,24 +105,28 @@ var EventSwitchExample = createReactClass({
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
<View>
<Switch
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchIsOn} />
onValueChange={value => this.setState({ eventSwitchIsOn: value })}
style={{ marginBottom: 10 }}
value={this.state.eventSwitchIsOn}
/>
<Switch
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchIsOn} />
onValueChange={value => this.setState({ eventSwitchIsOn: value })}
style={{ marginBottom: 10 }}
value={this.state.eventSwitchIsOn}
/>
<Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
</View>
<View>
<Switch
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchRegressionIsOn} />
onValueChange={value => this.setState({ eventSwitchRegressionIsOn: value })}
style={{ marginBottom: 10 }}
value={this.state.eventSwitchRegressionIsOn}
/>
<Switch
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
style={{marginBottom: 10}}
value={this.state.eventSwitchRegressionIsOn} />
onValueChange={value => this.setState({ eventSwitchRegressionIsOn: value })}
style={{ marginBottom: 10 }}
value={this.state.eventSwitchRegressionIsOn}
/>
<Text>{this.state.eventSwitchRegressionIsOn ? 'On' : 'Off'}</Text>
</View>
</View>
@@ -133,24 +134,24 @@ var EventSwitchExample = createReactClass({
}
});
var SizeSwitchExample = createReactClass({
const SizeSwitchExample = createReactClass({
getInitialState() {
return {
trueSwitchIsOn: true,
falseSwitchIsOn: false,
falseSwitchIsOn: false
};
},
render() {
return (
<View>
<Switch
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
style={{marginBottom: 10, height: '3rem' }}
onValueChange={value => this.setState({ falseSwitchIsOn: value })}
style={{ marginBottom: 10, height: '3rem' }}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
style={{marginBottom: 10, width: 150 }}
onValueChange={value => this.setState({ trueSwitchIsOn: value })}
style={{ marginBottom: 10, width: 150 }}
value={this.state.trueSwitchIsOn}
/>
</View>
@@ -158,34 +159,45 @@ var SizeSwitchExample = createReactClass({
}
});
var examples = [
const examples = [
{
title: 'set to true or false',
render(): ReactElement<any> { return <BasicSwitchExample />; }
render() {
return <BasicSwitchExample />;
}
},
{
title: 'disabled',
render(): ReactElement<any> { return <DisabledSwitchExample />; }
render() {
return <DisabledSwitchExample />;
}
},
{
title: 'change events',
render(): ReactElement<any> { return <EventSwitchExample />; }
render() {
return <EventSwitchExample />;
}
},
{
title: 'custom colors',
render(): ReactElement<any> { return <ColorSwitchExample />; }
render() {
return <ColorSwitchExample />;
}
},
{
title: 'custom size',
render(): ReactElement<any> { return <SizeSwitchExample />; }
render() {
return <SizeSwitchExample />;
}
},
{
title: 'controlled component',
render(): ReactElement<any> { return <Switch />; }
render() {
return <Switch />;
}
}
];
examples.forEach((example) => {
storiesOf('component: Switch', module)
.add(example.title, () => example.render())
})
examples.forEach(example => {
storiesOf('component: Switch', module).add(example.title, () => example.render());
});

View File

@@ -0,0 +1,565 @@
/* eslint-disable react/jsx-no-bind */
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* 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 NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK 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.
*
* @flow
*/
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { Image, Text, View } from 'react-native';
const Entity = createReactClass({
render: function() {
return (
<Text style={{ fontWeight: '500', color: '#527fe4' }}>
{this.props.children}
</Text>
);
}
});
const AttributeToggler = createReactClass({
getInitialState: function() {
return { fontWeight: 'bold', fontSize: 15 };
},
toggleWeight: function() {
this.setState({
fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold'
});
},
increaseSize: function() {
this.setState({
fontSize: this.state.fontSize + 1
});
},
render: function() {
const curStyle = { fontWeight: this.state.fontWeight, fontSize: this.state.fontSize };
return (
<View>
<Text style={curStyle}>
Tap the controls below to change attributes.
</Text>
<Text>
<Text>See how it will even work on <Text style={curStyle}>this nested text</Text></Text>
</Text>
<Text onPress={this.toggleWeight} style={{ backgroundColor: '#ffaaaa', marginTop: 5 }}>
Toggle Weight
</Text>
<Text onPress={this.increaseSize} style={{ backgroundColor: '#aaaaff', marginTop: 5 }}>
Increase Size
</Text>
</View>
);
}
});
const examples = [
{
title: 'Wrap',
render: function() {
return (
<Text style={{ WebkitFontSmoothing: 'antialiased' }}>
The text should wrap if it goes on multiple lines. See, this is going to
the next line.
</Text>
);
}
},
{
title: 'Padding',
render: function() {
return (
<Text style={{ padding: 10 }}>
This text is indented by 10px padding on all sides.
</Text>
);
}
},
{
title: 'Font Family',
render: function() {
return (
<View>
<Text style={{ fontFamily: 'Cochin' }}>
Cochin
</Text>
<Text style={{ fontFamily: 'Cochin', fontWeight: 'bold' }}>
Cochin bold
</Text>
<Text style={{ fontFamily: 'Helvetica' }}>
Helvetica
</Text>
<Text style={{ fontFamily: 'Helvetica', fontWeight: 'bold' }}>
Helvetica bold
</Text>
<Text style={{ fontFamily: 'Verdana' }}>
Verdana
</Text>
<Text style={{ fontFamily: 'Verdana', fontWeight: 'bold' }}>
Verdana bold
</Text>
</View>
);
}
},
{
title: 'Font Size',
render: function() {
return (
<View>
<Text style={{ fontSize: 23 }}>
Size 23
</Text>
<Text style={{ fontSize: 8 }}>
Size 8
</Text>
</View>
);
}
},
{
title: 'Color',
render: function() {
return (
<View>
<Text style={{ color: 'red' }}>
Red color
</Text>
<Text style={{ color: 'blue' }}>
Blue color
</Text>
</View>
);
}
},
{
title: 'Font Weight',
render: function() {
return (
<View>
<Text style={{ fontSize: 20, fontWeight: '100' }}>
Move fast and be ultralight
</Text>
<Text style={{ fontSize: 20, fontWeight: '200' }}>
Move fast and be light
</Text>
<Text style={{ fontSize: 20, fontWeight: 'normal' }}>
Move fast and be normal
</Text>
<Text style={{ fontSize: 20, fontWeight: 'bold' }}>
Move fast and be bold
</Text>
<Text style={{ fontSize: 20, fontWeight: '900' }}>
Move fast and be ultrabold
</Text>
</View>
);
}
},
{
title: 'Font Style',
render: function() {
return (
<View>
<Text style={{ fontStyle: 'normal' }}>
Normal text
</Text>
<Text style={{ fontStyle: 'italic' }}>
Italic text
</Text>
</View>
);
}
},
{
title: 'Text Decoration',
render: function() {
return (
<View>
<Text style={{ textDecorationLine: 'underline', textDecorationStyle: 'solid' }}>
Solid underline
</Text>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'double',
textDecorationColor: '#ff0000'
}}
>
Double underline with custom color
</Text>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'dashed',
textDecorationColor: '#9CDC40'
}}
>
Dashed underline with custom color
</Text>
<Text
style={{
textDecorationLine: 'underline',
textDecorationStyle: 'dotted',
textDecorationColor: 'blue'
}}
>
Dotted underline with custom color
</Text>
<Text style={{ textDecorationLine: 'none' }}>
None textDecoration
</Text>
<Text style={{ textDecorationLine: 'line-through', textDecorationStyle: 'solid' }}>
Solid line-through
</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'double',
textDecorationColor: '#ff0000'
}}
>
Double line-through with custom color
</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'dashed',
textDecorationColor: '#9CDC40'
}}
>
Dashed line-through with custom color
</Text>
<Text
style={{
textDecorationLine: 'line-through',
textDecorationStyle: 'dotted',
textDecorationColor: 'blue'
}}
>
Dotted line-through with custom color
</Text>
<Text style={{ textDecorationLine: 'underline line-through' }}>
Both underline and line-through
</Text>
</View>
);
}
},
{
title: 'Nested',
description: 'Nested text components will inherit the styles of their ' +
'parents (only backgroundColor is inherited from non-Text parents). ' +
'<Text> only supports other <Text> and raw text (strings) as children.',
render: function() {
return (
<View>
<Text>
(Normal text,
<Text style={{ fontWeight: 'bold' }}>
(and bold
<Text style={{ fontSize: 11, color: '#527fe4' }}>
(and tiny inherited bold blue)
</Text>
)
</Text>
)
</Text>
<Text style={{ opacity: 0.7 }}>
(opacity
<Text>
(is inherited
<Text style={{ opacity: 0.7 }}>
(and accumulated
<Text style={{ backgroundColor: '#ffaaaa' }}>
(and also applies to the background)
</Text>
)
</Text>
)
</Text>
)
</Text>
<Text style={{ fontSize: 12 }}>
<Entity>Entity Name</Entity>
</Text>
</View>
);
}
},
{
title: 'Text Align',
render: function() {
return (
<View>
<Text>
auto (default) - english LTR
</Text>
<Text>
أحب اللغة العربية auto (default) - arabic RTL
</Text>
<Text style={{ textAlign: 'left' }}>
left left left left left left left left left left left left left left left
</Text>
<Text style={{ textAlign: 'center' }}>
center center center center center center center center center center center
</Text>
<Text style={{ textAlign: 'right' }}>
right right right right right right right right right right right right right
</Text>
<Text style={{ textAlign: 'justify' }}>
justify: this text component{"'"}s contents are laid out with "textAlign: justify"
and as you can see all of the lines except the last one span the
available width of the parent container.
</Text>
</View>
);
}
},
{
title: 'Letter Spacing',
render: function() {
return (
<View>
<Text style={{ letterSpacing: 0 }}>
letterSpacing = 0
</Text>
<Text style={{ letterSpacing: 2, marginTop: 5 }}>
letterSpacing = 2
</Text>
<Text style={{ letterSpacing: 9, marginTop: 5 }}>
letterSpacing = 9
</Text>
<Text style={{ letterSpacing: -1, marginTop: 5 }}>
letterSpacing = -1
</Text>
</View>
);
}
},
{
title: 'Spaces',
render: function() {
return (
<Text>
A {'generated'} {' '} {'string'} and some &nbsp;&nbsp;&nbsp; spaces
</Text>
);
}
},
{
title: 'Line Height',
render: function() {
return (
<Text>
<Text style={{ lineHeight: 35 }}>
A lot of space between the lines of this long passage that should
wrap once.
</Text>
</Text>
);
}
},
{
title: 'Empty Text',
description: "It's ok to have Text with zero or null children.",
render: function() {
return <Text />;
}
},
{
title: 'Toggling Attributes',
render: function() {
return <AttributeToggler />;
}
},
{
title: 'backgroundColor attribute',
description: 'backgroundColor is inherited from all types of views.',
render: function() {
return (
<Text style={{ backgroundColor: 'yellow' }}>
Yellow container background,
<Text style={{ backgroundColor: '#ffaaaa' }}>
{' '}red background,
<Text style={{ backgroundColor: '#aaaaff' }}>
{' '}blue background,
<Text>
{' '}inherited blue background,
<Text style={{ backgroundColor: '#aaffaa' }}>
{' '}nested green background.
</Text>
</Text>
</Text>
</Text>
</Text>
);
}
},
{
title: 'numberOfLines attribute',
render: function() {
return (
<View>
<Text numberOfLines={1}>
Maximum of one line, no matter how much I write here. If I keep writing, it
{"'"}
ll just truncate after one line.
</Text>
<Text numberOfLines={2} style={{ marginTop: 20 }}>
Maximum of two lines, no matter how much I write here. If I keep writing, it
{"'"}
ll just truncate after two lines.
</Text>
<Text style={{ marginTop: 20 }}>
No maximum lines specified, no matter how much I write here. If I keep writing, it
{"'"}
ll just keep going and going.
</Text>
</View>
);
}
},
{
title: 'Text highlighting (tap the link to see highlight)',
render: function() {
return (
<View>
<Text>
Lorem ipsum dolor sit amet,
{' '}
<Text
onPress={() => null}
style={{ backgroundColor: 'white', textDecorationLine: 'underline', color: 'blue' }}
suppressHighlighting={false}
>
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
</Text>
{' '}
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</Text>
</View>
);
}
},
{
title: 'allowFontScaling attribute',
render: function() {
return (
<View>
<Text>
By default, text will respect Text Size accessibility setting on iOS.
It means that all font sizes will be increased or descreased depending on the value of Text Size setting in
{' '}
<Text style={{ fontWeight: 'bold' }}>
Settings.app - Display & Brightness - Text Size
</Text>
</Text>
<Text style={{ marginTop: 10 }}>
You can disable scaling for your Text component by passing
{' '}
{'"'}
allowFontScaling=
{'{'}
false
{'}"'}
{' '}
prop.
</Text>
<Text allowFontScaling={false} style={{ marginTop: 20 }}>
This text will not scale.
</Text>
</View>
);
}
},
{
title: 'Inline views',
render: function() {
return (
<View>
<Text>
This text contains an inline blue view
{' '}
<View style={{ width: 25, height: 25, backgroundColor: 'steelblue' }} />
{' '}
and
an inline image
{' '}
<Image
source={{ uri: 'http://lorempixel.com/30/11' }}
style={{ width: 30, height: 11, resizeMode: 'cover' }}
/>
. Neat, huh?
</Text>
</View>
);
}
},
{
title: 'Text shadow',
render: function() {
return (
<View>
<Text
style={{
fontSize: 20,
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 1,
textShadowColor: '#00cccc'
}}
>
Demo text shadow
</Text>
</View>
);
}
},
{
title: 'Line break mode',
render: function() {
return (
<View>
<Text numberOfLines={1}>
This very long text should be truncated with dots in the end.
</Text>
<Text lineBreakMode="middle" numberOfLines={1}>
This very long text should be truncated with dots in the middle.
</Text>
<Text lineBreakMode="head" numberOfLines={1}>
This very long text should be truncated with dots in the beginning.
</Text>
<Text lineBreakMode="clip" numberOfLines={1}>
This very looooooooooooooooooooooooooooong text should be clipped.
</Text>
</View>
);
}
}
];
examples.forEach(example => {
storiesOf('component: Text', module)
.addDecorator(renderStory => <View style={{ width: 320 }}>{renderStory()}</View>)
.add(example.title, () => example.render());
});

View File

@@ -1,6 +1,4 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, Text, TextInput, View } from 'react-native'
/* eslint-disable react/jsx-no-bind */
/**
* Copyright (c) 2013-present, Facebook, Inc.
@@ -25,7 +23,17 @@ import { StyleSheet, Text, TextInput, View } from 'react-native'
* @flow
*/
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { any, bool, object, string } from 'prop-types';
import { StyleSheet, Text, TextInput, View } from 'react-native';
class WithLabel extends React.Component {
static propTypes = {
children: any,
label: string
};
render() {
return (
<View style={styles.labelContainer}>
@@ -43,16 +51,16 @@ class TextEventsExample extends React.Component {
curText: '<No Event>',
prevText: '<No Event>',
prev2Text: '<No Event>',
prev3Text: '<No Event>',
prev3Text: '<No Event>'
};
updateText = (text) => {
this.setState((state) => {
updateText = text => {
this.setState(state => {
return {
curText: text,
prevText: state.curText,
prev2Text: state.prevText,
prev3Text: state.prev2Text,
prev3Text: state.prev2Text
};
});
};
@@ -62,28 +70,25 @@ class TextEventsExample extends React.Component {
<View style={{ alignItems: 'center' }}>
<TextInput
autoCapitalize="none"
placeholder="Enter text to see events"
autoCorrect={false}
onFocus={() => this.updateText('onFocus')}
onBlur={() => this.updateText('onBlur')}
onChange={(event) => this.updateText(
'onChange text: ' + event.nativeEvent.text
)}
onEndEditing={(event) => this.updateText(
'onEndEditing text: ' + event.nativeEvent.text
)}
onSubmitEditing={(event) => this.updateText(
'onSubmitEditing text: ' + event.nativeEvent.text
)}
onSelectionChange={(event) => this.updateText(
'onSelectionChange range: ' +
event.nativeEvent.selection.start + ',' +
event.nativeEvent.selection.end
)}
onKeyPress={(event) => {
onChange={event => this.updateText('onChange text: ' + event.nativeEvent.text)}
onEndEditing={event => this.updateText('onEndEditing text: ' + event.nativeEvent.text)}
onFocus={() => this.updateText('onFocus')}
onKeyPress={event => {
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
}}
style={[ styles.default, { maxWidth: 200 } ]}
onSelectionChange={event =>
this.updateText(
'onSelectionChange range: ' +
event.nativeEvent.selection.start +
',' +
event.nativeEvent.selection.end
)}
onSubmitEditing={event =>
this.updateText('onSubmitEditing text: ' + event.nativeEvent.text)}
placeholder="Enter text to see events"
style={[styles.default, { maxWidth: 200 }]}
/>
<Text style={styles.eventLabel}>
{this.state.curText}{'\n'}
@@ -103,21 +108,22 @@ class AutoExpandingTextInput extends React.Component {
super(props);
this.state = {
text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.',
height: 0,
height: 0
};
}
render() {
return (
<TextInput
{...this.props}
multiline={true}
onChangeText={(text) => {
this.setState({text});
onChangeText={text => {
this.setState({ text });
}}
onContentSizeChange={(event) => {
this.setState({height: event.nativeEvent.contentSize.height});
onContentSizeChange={event => {
this.setState({ height: event.nativeEvent.contentSize.height });
}}
style={[styles.default, {height: Math.max(35, this.state.height)}]}
style={[styles.default, { height: Math.max(35, this.state.height) }]}
value={this.state.text}
/>
);
@@ -129,25 +135,26 @@ class RewriteExample extends React.Component {
constructor(props) {
super(props);
this.state = {text: ''};
this.state = { text: '' };
}
render() {
var limit = 20;
var remainder = limit - this.state.text.length;
var remainderColor = remainder > 5 ? 'blue' : 'red';
const limit = 20;
const remainder = limit - this.state.text.length;
const remainderColor = remainder > 5 ? 'blue' : 'red';
return (
<View style={styles.rewriteContainer}>
<TextInput
multiline={false}
maxLength={limit}
onChangeText={(text) => {
multiline={false}
onChangeText={text => {
text = text.replace(/ /g, '_');
this.setState({text});
this.setState({ text });
}}
style={styles.default}
value={this.state.text}
/>
<Text style={[styles.remainder, {color: remainderColor}]}>
<Text style={[styles.remainder, { color: remainderColor }]}>
{remainder}
</Text>
</View>
@@ -160,15 +167,16 @@ class RewriteExampleInvalidCharacters extends React.Component {
constructor(props) {
super(props);
this.state = {text: ''};
this.state = { text: '' };
}
render() {
return (
<View style={styles.rewriteContainer}>
<TextInput
multiline={false}
onChangeText={(text) => {
this.setState({text: text.replace(/\s/g, '')});
onChangeText={text => {
this.setState({ text: text.replace(/\s/g, '') });
}}
style={styles.default}
value={this.state.text}
@@ -183,16 +191,17 @@ class TokenizedTextExample extends React.Component {
constructor(props) {
super(props);
this.state = {text: 'Hello #World'};
this.state = { text: 'Hello #World' };
}
render() {
render() {
//define delimiter
let delimiter = /\s+/;
const delimiter = /\s+/;
//split string
let _text = this.state.text;
let token, index, parts = [];
let token, index;
const parts = [];
while (_text) {
delimiter.lastIndex = 0;
token = delimiter.exec(_text);
@@ -213,12 +222,12 @@ class TokenizedTextExample extends React.Component {
return (
<View>
<TextInput
value={parts.join('')}
multiline={true}
style={styles.multiline}
onChangeText={(text) => {
this.setState({text});
onChangeText={text => {
this.setState({ text });
}}
style={styles.multiline}
value={parts.join('')}
/>
</View>
);
@@ -226,53 +235,68 @@ class TokenizedTextExample extends React.Component {
}
class BlurOnSubmitExample extends React.Component {
focusNextField = (nextField) => {
this.refs[nextField].focus();
constructor(props) {
super(props);
this._nodes = {};
}
focusNextField = nextField => {
this._nodes[nextField].focus();
};
render() {
return (
<View>
<TextInput
ref="1"
style={styles.default}
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('2')}
placeholder="blurOnSubmit = false"
ref={c => {
this._nodes['1'] = c;
}}
returnKeyType="next"
style={styles.default}
/>
<TextInput
ref="2"
style={styles.default}
blurOnSubmit={false}
keyboardType="email-address"
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('3')}
/>
<TextInput
ref="3"
style={styles.default}
keyboardType="url"
placeholder="blurOnSubmit = false"
ref={c => {
this._nodes['2'] = c;
}}
returnKeyType="next"
style={styles.default}
/>
<TextInput
blurOnSubmit={false}
keyboardType="url"
onSubmitEditing={() => this.focusNextField('4')}
/>
<TextInput
ref="4"
style={styles.default}
keyboardType="numeric"
placeholder="blurOnSubmit = false"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('5')}
ref={c => {
this._nodes['3'] = c;
}}
returnKeyType="next"
style={styles.default}
/>
<TextInput
ref="5"
blurOnSubmit={false}
keyboardType="numeric"
onSubmitEditing={() => this.focusNextField('5')}
placeholder="blurOnSubmit = false"
ref={c => {
this._nodes['4'] = c;
}}
style={styles.default}
/>
<TextInput
keyboardType="numeric"
placeholder="blurOnSubmit = true"
ref={c => {
this._nodes['5'] = c;
}}
returnKeyType="done"
style={styles.default}
/>
</View>
);
@@ -281,10 +305,10 @@ class BlurOnSubmitExample extends React.Component {
type SelectionExampleState = {
selection: {
start: number;
end?: number;
};
value: string;
start: number,
end?: number
},
value: string
};
class SelectionExample extends React.Component {
@@ -292,30 +316,36 @@ class SelectionExample extends React.Component {
_textInput: any;
static propTypes = {
multiline: bool,
style: object,
value: string
};
constructor(props) {
super(props);
this.state = {
selection: {start: 0, end: 0},
selection: { start: 0, end: 0 },
value: props.value
};
}
onSelectionChange({nativeEvent: {selection}}) {
this.setState({selection});
onSelectionChange({ nativeEvent: { selection } }) {
this.setState({ selection });
}
getRandomPosition() {
var length = this.state.value.length;
const length = this.state.value.length;
return Math.round(Math.random() * length);
}
select(start, end) {
this._textInput.focus();
this.setState({selection: {start, end}});
this.setState({ selection: { start, end } });
}
selectRandom() {
var positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
const positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
this.select(...positions);
}
@@ -328,13 +358,13 @@ class SelectionExample extends React.Component {
}
render() {
var length = this.state.value.length;
const length = this.state.value.length;
return (
<View>
<TextInput
multiline={this.props.multiline}
onChangeText={(value) => this.setState({value})}
onChangeText={value => this.setState({ value })}
onSelectionChange={this.onSelectionChange.bind(this)}
ref={textInput => (this._textInput = textInput)}
selection={this.state.selection}
@@ -366,9 +396,9 @@ class SelectionExample extends React.Component {
}
}
var styles = StyleSheet.create({
const styles = StyleSheet.create({
page: {
paddingBottom: 300,
paddingBottom: 300
},
default: {
height: 26,
@@ -376,7 +406,7 @@ var styles = StyleSheet.create({
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
padding: 4,
padding: 4
},
multiline: {
borderWidth: 0.5,
@@ -385,49 +415,49 @@ var styles = StyleSheet.create({
fontSize: 13,
height: 50,
padding: 4,
marginBottom: 4,
marginBottom: 4
},
multilineWithFontStyles: {
color: 'blue',
fontWeight: 'bold',
fontSize: 18,
fontFamily: 'Cochin',
height: 60,
height: 60
},
multilineChild: {
width: 50,
height: 40,
position: 'absolute',
right: 5,
backgroundColor: 'red',
backgroundColor: 'red'
},
eventLabel: {
margin: 3,
fontSize: 12,
fontSize: 12
},
labelContainer: {
flexDirection: 'row',
marginVertical: 2,
flex: 1,
flex: 1
},
label: {
width: 115,
alignItems: 'flex-end',
marginRight: 10,
paddingTop: 2,
paddingTop: 2
},
rewriteContainer: {
flexDirection: 'row',
alignItems: 'center',
alignItems: 'center'
},
remainder: {
textAlign: 'right',
width: 24,
width: 24
},
hashtag: {
color: 'blue',
fontWeight: 'bold',
},
fontWeight: 'bold'
}
});
const examples = [
@@ -437,9 +467,9 @@ const examples = [
return (
<View>
<TextInput
accessibilityLabel="I am the accessibility label for text input"
autoFocus={true}
style={styles.default}
accessibilityLabel="I am the accessibility label for text input"
/>
</View>
);
@@ -463,28 +493,16 @@ const examples = [
return (
<View>
<WithLabel label="none">
<TextInput
autoCapitalize="none"
style={styles.default}
/>
<TextInput autoCapitalize="none" style={styles.default} />
</WithLabel>
<WithLabel label="sentences">
<TextInput
autoCapitalize="sentences"
style={styles.default}
/>
<TextInput autoCapitalize="sentences" style={styles.default} />
</WithLabel>
<WithLabel label="words">
<TextInput
autoCapitalize="words"
style={styles.default}
/>
<TextInput autoCapitalize="words" style={styles.default} />
</WithLabel>
<WithLabel label="characters">
<TextInput
autoCapitalize="characters"
style={styles.default}
/>
<TextInput autoCapitalize="characters" style={styles.default} />
</WithLabel>
</View>
);
@@ -508,7 +526,7 @@ const examples = [
{
title: 'Keyboard types',
render: function() {
var keyboardTypes = [
const keyboardTypes = [
'default',
//'ascii-capable',
//'numbers-and-punctuation',
@@ -520,15 +538,12 @@ const examples = [
//'decimal-pad',
//'twitter',
'web-search',
'numeric',
'numeric'
];
var examples = keyboardTypes.map((type) => {
const examples = keyboardTypes.map(type => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardType={type}
style={styles.default}
/>
<TextInput keyboardType={type} style={styles.default} />
</WithLabel>
);
});
@@ -538,18 +553,11 @@ const examples = [
{
title: 'Keyboard appearance',
render: function() {
var keyboardAppearance = [
'default',
'light',
'dark',
];
var examples = keyboardAppearance.map((type) => {
const keyboardAppearance = ['default', 'light', 'dark'];
const examples = keyboardAppearance.map(type => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardAppearance={type}
style={styles.default}
/>
<TextInput keyboardAppearance={type} style={styles.default} />
</WithLabel>
);
});
@@ -559,7 +567,7 @@ const examples = [
{
title: 'Return key types',
render: function() {
var returnKeyTypes = [
const returnKeyTypes = [
'default',
'go',
'google',
@@ -570,15 +578,12 @@ const examples = [
'send',
'yahoo',
'done',
'emergency-call',
'emergency-call'
];
var examples = returnKeyTypes.map((type) => {
const examples = returnKeyTypes.map(type => {
return (
<WithLabel key={type} label={type}>
<TextInput
returnKeyType={type}
style={styles.default}
/>
<TextInput returnKeyType={type} style={styles.default} />
</WithLabel>
);
});
@@ -603,7 +608,7 @@ const examples = [
return (
<View>
<WithLabel label="true">
<TextInput secureTextEntry={true} style={styles.default} defaultValue="abc" />
<TextInput defaultValue="abc" secureTextEntry={true} style={styles.default} />
</WithLabel>
</View>
);
@@ -611,21 +616,17 @@ const examples = [
},
{
title: 'Event handling',
render: function(): React.Element<any> { return <TextEventsExample />; },
render: function(): React.Element<any> {
return <TextEventsExample />;
}
},
{
title: 'Colored input text',
render: function() {
return (
<View>
<TextInput
style={[styles.default, {color: 'blue'}]}
defaultValue="Blue"
/>
<TextInput
style={[styles.default, {color: 'green'}]}
defaultValue="Green"
/>
<TextInput defaultValue="Blue" style={[styles.default, { color: 'blue' }]} />
<TextInput defaultValue="Green" style={[styles.default, { color: 'green' }]} />
</View>
);
}
@@ -635,15 +636,11 @@ const examples = [
render: function() {
return (
<View>
<TextInput defaultValue="Highlight me" selectionColor={'green'} style={styles.default} />
<TextInput
style={styles.default}
selectionColor={"green"}
defaultValue="Highlight me"
/>
<TextInput
selectionColor={'rgba(86, 76, 205, 1)'}
style={styles.default}
selectionColor={"rgba(86, 76, 205, 1)"}
defaultValue="Highlight me"
/>
</View>
);
@@ -651,32 +648,20 @@ const examples = [
},
{
title: 'Clear button mode',
render: function () {
render: function() {
return (
<View>
<WithLabel label="never">
<TextInput
style={styles.default}
clearButtonMode="never"
/>
<TextInput clearButtonMode="never" style={styles.default} />
</WithLabel>
<WithLabel label="while editing">
<TextInput
style={styles.default}
clearButtonMode="while-editing"
/>
<TextInput clearButtonMode="while-editing" style={styles.default} />
</WithLabel>
<WithLabel label="unless editing">
<TextInput
style={styles.default}
clearButtonMode="unless-editing"
/>
<TextInput clearButtonMode="unless-editing" style={styles.default} />
</WithLabel>
<WithLabel label="always">
<TextInput
style={styles.default}
clearButtonMode="always"
/>
<TextInput clearButtonMode="always" style={styles.default} />
</WithLabel>
</View>
);
@@ -689,18 +674,18 @@ const examples = [
<View>
<WithLabel label="clearTextOnFocus">
<TextInput
placeholder="text is cleared on focus"
defaultValue="text is cleared on focus"
style={styles.default}
clearTextOnFocus={true}
defaultValue="text is cleared on focus"
placeholder="text is cleared on focus"
style={styles.default}
/>
</WithLabel>
<WithLabel label="selectTextOnFocus">
<TextInput
placeholder="text is selected on focus"
defaultValue="text is selected on focus"
style={styles.default}
placeholder="text is selected on focus"
selectTextOnFocus={true}
style={styles.default}
/>
</WithLabel>
</View>
@@ -709,7 +694,9 @@ const examples = [
},
{
title: 'Blur on submit',
render: function(): React.Element<any> { return <BlurOnSubmitExample />; },
render: function(): React.Element<any> {
return <BlurOnSubmitExample />;
}
},
{
title: 'Multiline blur on submit',
@@ -717,12 +704,14 @@ const examples = [
return (
<View>
<TextInput
style={styles.multiline}
placeholder="blurOnSubmit = true"
returnKeyType="next"
blurOnSubmit={true}
multiline={true}
onSubmitEditing={event => alert(event.nativeEvent.text)}
onSubmitEditing={event => {
console.log(event.nativeEvent.text);
}}
placeholder="blurOnSubmit = true"
returnKeyType="next"
style={styles.multiline}
/>
</View>
);
@@ -733,39 +722,35 @@ const examples = [
render: function() {
return (
<View>
<TextInput multiline={true} placeholder="multiline text input" style={styles.multiline} />
<TextInput
placeholder="multiline text input"
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="multiline text input with font styles and placeholder"
multiline={true}
clearTextOnFocus={true}
autoCorrect={true}
autoCapitalize="words"
placeholderTextColor="red"
autoCorrect={true}
clearTextOnFocus={true}
keyboardType="url"
multiline={true}
placeholder="multiline text input with font styles and placeholder"
placeholderTextColor="red"
style={[styles.multiline, styles.multilineWithFontStyles]}
/>
<TextInput
placeholder="multiline text input with max length"
maxLength={5}
multiline={true}
placeholder="multiline text input with max length"
style={styles.multiline}
/>
<TextInput
placeholder="uneditable multiline text input"
editable={false}
multiline={true}
placeholder="uneditable multiline text input"
style={styles.multiline}
/>
<TextInput
dataDetectorTypes="phoneNumber"
defaultValue="uneditable multiline text input with phone number detection: 88888888."
editable={false}
multiline={true}
style={styles.multiline}
dataDetectorTypes="phoneNumber"
/>
</View>
);
@@ -779,7 +764,7 @@ const examples = [
<TextInput
multiline={true}
numberOfLines={4}
style={[ styles.multiline, { height: 'auto' } ]}
style={[styles.multiline, { height: 'auto' }]}
/>
</View>
);
@@ -791,8 +776,8 @@ const examples = [
return (
<View>
<AutoExpandingTextInput
placeholder="height increases with content"
enablesReturnKeyAutomatically={true}
placeholder="height increases with content"
returnKeyType="default"
/>
</View>
@@ -810,14 +795,11 @@ const examples = [
render: function() {
return (
<View>
<SelectionExample
style={styles.default}
value="text selection can be changed"
/>
<SelectionExample style={styles.default} value="text selection can be changed" />
<SelectionExample
multiline
style={styles.multiline}
value={"multiline text selection\ncan also be changed"}
value={'multiline text selection\ncan also be changed'}
/>
</View>
);
@@ -829,31 +811,16 @@ const examples = [
return (
<View>
<WithLabel label="maxLength: 5">
<TextInput
maxLength={5}
style={styles.default}
/>
<TextInput maxLength={5} style={styles.default} />
</WithLabel>
<WithLabel label="maxLength: 5 with placeholder">
<TextInput
maxLength={5}
placeholder="ZIP code entry"
style={styles.default}
/>
<TextInput maxLength={5} placeholder="ZIP code entry" style={styles.default} />
</WithLabel>
<WithLabel label="maxLength: 5 with default value already set">
<TextInput
maxLength={5}
defaultValue="94025"
style={styles.default}
/>
<TextInput defaultValue="94025" maxLength={5} style={styles.default} />
</WithLabel>
<WithLabel label="maxLength: 5 with very long default value already set">
<TextInput
maxLength={5}
defaultValue="9402512345"
style={styles.default}
/>
<TextInput defaultValue="9402512345" maxLength={5} style={styles.default} />
</WithLabel>
</View>
);
@@ -861,7 +828,6 @@ const examples = [
}
];
examples.forEach((example) => {
storiesOf('component: TextInput', module)
.add(example.title, () => example.render())
examples.forEach(example => {
storiesOf('component: TextInput', module).add(example.title, () => example.render());
});

View File

@@ -0,0 +1,473 @@
/* eslint-disable react/jsx-no-bind */
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* 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 NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK 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.
*
* @flow
*/
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import {
Image,
StyleSheet,
Text,
TouchableHighlight,
TouchableOpacity,
Platform,
TouchableNativeFeedback,
View
} from 'react-native';
const examples = [
{
title: '<TouchableHighlight>',
description: 'TouchableHighlight works by adding an extra view with a ' +
'black background under the single child view. This works best when the ' +
'child view is fully opaque, although it can be made to work as a simple ' +
'background color change as well with the activeOpacity and ' +
'underlayColor props.',
render: function() {
return (
<View>
<View style={styles.row}>
<TouchableHighlight
onPress={() => console.log('stock THW image - highlight')}
style={styles.wrapper}
>
<Image source={heartImage} style={styles.image} />
</TouchableHighlight>
<TouchableHighlight
activeOpacity={1}
onPress={() => console.log('custom THW text - highlight')}
style={styles.wrapper}
underlayColor="rgb(210, 230, 255)"
>
<View style={styles.wrapperCustom}>
<Text style={styles.text}>
Tap Here For Custom Highlight!
</Text>
</View>
</TouchableHighlight>
</View>
</View>
);
}
},
{
title: '<Text onPress={fn}> with highlight',
render: function() {
return <TextOnPressBox />;
}
},
{
title: 'Touchable feedback events',
description: '<Touchable*> components accept onPress, onPressIn, ' +
'onPressOut, and onLongPress as props.',
render: function() {
return <TouchableFeedbackEvents />;
}
},
{
title: 'Touchable delay for events',
description: '<Touchable*> components also accept delayPressIn, ' +
'delayPressOut, and delayLongPress as props. These props impact the ' +
'timing of feedback events.',
render: function() {
return <TouchableDelayEvents />;
}
},
{
title: '3D Touch / Force Touch',
description: 'iPhone 6s and 6s plus support 3D touch, which adds a force property to touches',
render: function() {
return <ForceTouchExample />;
},
platform: 'ios'
},
{
title: 'Touchable Hit Slop',
description: '<Touchable*> components accept hitSlop prop which extends the touch area ' +
'without changing the view bounds.',
render: function() {
return <TouchableHitSlop />;
}
},
{
title: 'Disabled Touchable*',
description: '<Touchable*> components accept disabled prop which prevents ' +
'any interaction with component',
render: function() {
return <TouchableDisabled />;
}
}
];
class TextOnPressBox extends React.Component {
constructor(props) {
super(props);
this.state = { timesPressed: 0 };
}
_handlePress = () => {
this.setState({
timesPressed: this.state.timesPressed + 1
});
};
render() {
let textLog = '';
if (this.state.timesPressed > 1) {
textLog = this.state.timesPressed + 'x text onPress';
} else if (this.state.timesPressed > 0) {
textLog = 'text onPress';
}
return (
<View>
<Text onPress={this._handlePress} style={styles.textBlock}>
Text has built-in onPress handling
</Text>
<View style={styles.logBox}>
<Text>{textLog}</Text>
</View>
</View>
);
}
}
class TouchableFeedbackEvents extends React.Component {
constructor(props) {
super(props);
this.state = { eventLog: [] };
}
render() {
return (
<View testID="touchable_feedback_events">
<View style={[styles.row, { justifyContent: 'center' }]}>
<TouchableOpacity
accessibilityComponentType="button"
accessibilityLabel="touchable feedback events"
accessibilityTraits="button"
onLongPress={this._createPressHandler('longPress')}
onPress={this._createPressHandler('press')}
onPressIn={this._createPressHandler('pressIn')}
onPressOut={this._createPressHandler('pressOut')}
style={styles.wrapper}
testID="touchable_feedback_events_button"
>
<Text style={styles.button}>
Press Me
</Text>
</TouchableOpacity>
</View>
<View style={styles.eventLogBox} testID="touchable_feedback_events_console">
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
</View>
</View>
);
}
_createPressHandler = eventName => {
return () => {
const limit = 6;
const eventLog = this.state.eventLog.slice(0, limit - 1);
eventLog.unshift(eventName);
this.setState({ eventLog });
};
};
}
class TouchableDelayEvents extends React.Component {
constructor(props) {
super(props);
this.state = { eventLog: [] };
}
render() {
return (
<View testID="touchable_delay_events">
<View style={[styles.row, { justifyContent: 'center' }]}>
<TouchableOpacity
delayLongPress={800}
delayPressIn={400}
delayPressOut={1000}
onLongPress={this._createPressHandler('longPress - 800ms delay')}
onPress={this._createPressHandler('press')}
onPressIn={this._createPressHandler('pressIn - 400ms delay')}
onPressOut={this._createPressHandler('pressOut - 1000ms delay')}
style={styles.wrapper}
testID="touchable_delay_events_button"
>
<Text style={styles.button}>
Press Me
</Text>
</TouchableOpacity>
</View>
<View style={styles.eventLogBox} testID="touchable_delay_events_console">
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
</View>
</View>
);
}
_createPressHandler = eventName => {
return () => {
const limit = 6;
const eventLog = this.state.eventLog.slice(0, limit - 1);
eventLog.unshift(eventName);
this.setState({ eventLog });
};
};
}
class ForceTouchExample extends React.Component {
constructor(props) {
super(props);
this.state = { force: 0 };
}
_renderConsoleText() {
return View.forceTouchAvailable
? 'Force: ' + this.state.force.toFixed(3)
: '3D Touch is not available on this device';
}
render() {
return (
<View testID="touchable_3dtouch_event">
<View style={styles.forceTouchBox} testID="touchable_3dtouch_output">
<Text>{this._renderConsoleText()}</Text>
</View>
<View style={[styles.row, { justifyContent: 'center' }]}>
<View
onResponderMove={event => this.setState({ force: event.nativeEvent.force })}
onResponderRelease={event => this.setState({ force: 0 })}
onStartShouldSetResponder={() => true}
style={styles.wrapper}
testID="touchable_3dtouch_button"
>
<Text style={styles.button}>
Press Me
</Text>
</View>
</View>
</View>
);
}
}
class TouchableHitSlop extends React.Component {
constructor(props) {
super(props);
this.state = { timesPressed: 0 };
}
_handlePress = () => {
this.setState({
timesPressed: this.state.timesPressed + 1
});
};
render() {
let log = '';
if (this.state.timesPressed > 1) {
log = this.state.timesPressed + 'x onPress';
} else if (this.state.timesPressed > 0) {
log = 'onPress';
}
return (
<View testID="touchable_hit_slop">
<View style={[styles.row, { justifyContent: 'center' }]}>
<TouchableOpacity
hitSlop={{ top: 30, bottom: 30, left: 60, right: 60 }}
onPress={this._handlePress}
style={styles.hitSlopWrapper}
testID="touchable_hit_slop_button"
>
<Text style={styles.hitSlopButton}>
Press Outside This View
</Text>
</TouchableOpacity>
</View>
<View style={styles.logBox}>
<Text>
{log}
</Text>
</View>
</View>
);
}
}
class TouchableDisabled extends React.Component {
render() {
return (
<View>
<TouchableOpacity
disabled={true}
onPress={action('TouchableOpacity')}
style={[styles.row, styles.block]}
>
<Text style={styles.disabledButton}>Disabled TouchableOpacity</Text>
</TouchableOpacity>
<TouchableOpacity
disabled={false}
onPress={action('TouchableOpacity')}
style={[styles.row, styles.block]}
>
<Text style={styles.button}>Enabled TouchableOpacity</Text>
</TouchableOpacity>
<TouchableHighlight
activeOpacity={1}
disabled={true}
onPress={action('TouchableHighlight')}
style={[styles.row, styles.block]}
underlayColor="rgb(210, 230, 255)"
>
<Text style={styles.disabledButton}>
Disabled TouchableHighlight
</Text>
</TouchableHighlight>
<TouchableHighlight
activeOpacity={1}
onPress={action('TouchableHighlight')}
style={[styles.row, styles.block]}
underlayColor="rgb(210, 230, 255)"
>
<Text style={styles.button}>
Enabled TouchableHighlight
</Text>
</TouchableHighlight>
{Platform.OS === 'android' &&
<TouchableNativeFeedback
background={TouchableNativeFeedback.SelectableBackground()}
onPress={() => console.log('custom TNF has been clicked')}
style={[styles.row, styles.block]}
>
<View>
<Text style={[styles.button, styles.nativeFeedbackButton]}>
Enabled TouchableNativeFeedback
</Text>
</View>
</TouchableNativeFeedback>}
{Platform.OS === 'android' &&
<TouchableNativeFeedback
background={TouchableNativeFeedback.SelectableBackground()}
disabled={true}
onPress={() => console.log('custom TNF has been clicked')}
style={[styles.row, styles.block]}
>
<View>
<Text style={[styles.disabledButton, styles.nativeFeedbackButton]}>
Disabled TouchableNativeFeedback
</Text>
</View>
</TouchableNativeFeedback>}
</View>
);
}
}
const heartImage = { uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small' };
const styles = StyleSheet.create({
row: {
justifyContent: 'center',
flexDirection: 'row'
},
icon: {
width: 24,
height: 24
},
image: {
width: 50,
height: 50
},
text: {
fontSize: 16
},
block: {
padding: 10
},
button: {
color: '#007AFF'
},
disabledButton: {
color: '#007AFF',
opacity: 0.5
},
nativeFeedbackButton: {
textAlign: 'center',
margin: 10
},
hitSlopButton: {
color: 'white'
},
wrapper: {
borderRadius: 8
},
wrapperCustom: {
borderRadius: 8,
padding: 6
},
hitSlopWrapper: {
backgroundColor: 'red',
marginVertical: 30
},
logBox: {
padding: 20,
margin: 10,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9'
},
eventLogBox: {
padding: 10,
margin: 10,
height: 120,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9'
},
forceTouchBox: {
padding: 10,
margin: 10,
borderWidth: StyleSheet.hairlineWidth,
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
alignItems: 'center'
},
textBlock: {
fontWeight: '500',
color: 'blue'
}
});
examples.forEach(example => {
storiesOf('component: Touchable*', module).add(example.title, () => example.render());
});

View File

@@ -1,7 +1,7 @@
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
import { storiesOf } from '@kadira/storybook';
import { StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
/**
* Copyright (c) 2013-present, Facebook, Inc.
@@ -26,32 +26,32 @@ import { StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
* @flow
*/
var styles = StyleSheet.create({
const styles = StyleSheet.create({
box: {
backgroundColor: '#527FE4',
borderColor: '#000033',
borderWidth: 1,
borderWidth: 1
},
shadowBox: {
width: 100,
height: 100,
borderWidth: 2,
borderWidth: 2
},
shadow: {
shadowOpacity: 0.5,
shadowColor: 'red',
shadowRadius: 3,
shadowOffset: { width: 3, height: 3 },
shadowOffset: { width: 3, height: 3 }
},
zIndex: {
justifyContent: 'space-around',
width: 100,
height: 50,
marginTop: -10,
},
marginTop: -10
}
});
var ViewBorderStyleExample = createReactClass({
const ViewBorderStyleExample = createReactClass({
getInitialState() {
return {
showBorder: true
@@ -62,23 +62,27 @@ var ViewBorderStyleExample = createReactClass({
return (
<TouchableWithoutFeedback onPress={this._handlePress}>
<View>
<View style={{
borderWidth: 1,
borderStyle: this.state.showBorder ? 'dashed' : null,
padding: 5
}}>
<Text style={{fontSize: 11}}>
<View
style={{
borderWidth: 1,
borderStyle: this.state.showBorder ? 'dashed' : null,
padding: 5
}}
>
<Text style={{ fontSize: 11 }}>
Dashed border style
</Text>
</View>
<View style={{
marginTop: 5,
borderWidth: 1,
borderRadius: 5,
borderStyle: this.state.showBorder ? 'dotted' : null,
padding: 5
}}>
<Text style={{fontSize: 11}}>
<View
style={{
marginTop: 5,
borderWidth: 1,
borderRadius: 5,
borderStyle: this.state.showBorder ? 'dotted' : null,
padding: 5
}}
>
<Text style={{ fontSize: 11 }}>
Dotted border style
</Text>
</View>
@@ -88,11 +92,11 @@ var ViewBorderStyleExample = createReactClass({
},
_handlePress() {
this.setState({showBorder: !this.state.showBorder});
this.setState({ showBorder: !this.state.showBorder });
}
});
var ZIndexExample = createReactClass({
const ZIndexExample = createReactClass({
getInitialState() {
return {
flipped: false
@@ -104,29 +108,37 @@ var ZIndexExample = createReactClass({
return (
<TouchableWithoutFeedback onPress={this._handlePress}>
<View>
<Text style={{paddingBottom: 10}}>Tap to flip sorting order</Text>
<View style={[
styles.zIndex,
{marginTop: 0, backgroundColor: '#E57373', zIndex: indices[0]}
]}>
<Text style={{ paddingBottom: 10 }}>Tap to flip sorting order</Text>
<View
style={[
styles.zIndex,
{ marginTop: 0, backgroundColor: '#E57373', zIndex: indices[0] }
]}
>
<Text>ZIndex {indices[0]}</Text>
</View>
<View style={[
styles.zIndex,
{marginLeft: 50, backgroundColor: '#FFF176', zIndex: indices[1]}
]}>
<View
style={[
styles.zIndex,
{ marginLeft: 50, backgroundColor: '#FFF176', zIndex: indices[1] }
]}
>
<Text>ZIndex {indices[1]}</Text>
</View>
<View style={[
styles.zIndex,
{marginLeft: 100, backgroundColor: '#81C784', zIndex: indices[2]}
]}>
<View
style={[
styles.zIndex,
{ marginLeft: 100, backgroundColor: '#81C784', zIndex: indices[2] }
]}
>
<Text>ZIndex {indices[2]}</Text>
</View>
<View style={[
styles.zIndex,
{marginLeft: 150, backgroundColor: '#64B5F6', zIndex: indices[3]}
]}>
<View
style={[
styles.zIndex,
{ marginLeft: 150, backgroundColor: '#64B5F6', zIndex: indices[3] }
]}
>
<Text>ZIndex {indices[3]}</Text>
</View>
</View>
@@ -135,7 +147,7 @@ var ZIndexExample = createReactClass({
},
_handlePress() {
this.setState({flipped: !this.state.flipped});
this.setState({ flipped: !this.state.flipped });
}
});
@@ -144,74 +156,78 @@ const examples = [
title: 'Background Color',
render: function() {
return (
<View style={{backgroundColor: '#527FE4', padding: 5}}>
<Text style={{fontSize: 11}}>
<View style={{ backgroundColor: '#527FE4', padding: 5 }}>
<Text style={{ fontSize: 11 }}>
Blue background
</Text>
</View>
);
},
}, {
}
},
{
title: 'Border',
render: function() {
return (
<View style={{borderColor: '#527FE4', borderWidth: 5, padding: 10}}>
<Text style={{fontSize: 11}}>5px blue border</Text>
<View style={{ borderColor: '#527FE4', borderWidth: 5, padding: 10 }}>
<Text style={{ fontSize: 11 }}>5px blue border</Text>
</View>
);
},
}, {
}
},
{
title: 'Padding/Margin',
render: function() {
return (
<View style={{borderColor: '#bb0000', borderWidth: 0.5}}>
<View style={[styles.box, {padding: 5}]}>
<Text style={{fontSize: 11}}>5px padding</Text>
<View style={{ borderColor: '#bb0000', borderWidth: 0.5 }}>
<View style={[styles.box, { padding: 5 }]}>
<Text style={{ fontSize: 11 }}>5px padding</Text>
</View>
<View style={[styles.box, {margin: 5}]}>
<Text style={{fontSize: 11}}>5px margin</Text>
<View style={[styles.box, { margin: 5 }]}>
<Text style={{ fontSize: 11 }}>5px margin</Text>
</View>
<View style={[styles.box, {margin: 5, padding: 5, alignSelf: 'flex-start'}]}>
<Text style={{fontSize: 11}}>
<View style={[styles.box, { margin: 5, padding: 5, alignSelf: 'flex-start' }]}>
<Text style={{ fontSize: 11 }}>
5px margin and padding,
</Text>
<Text style={{fontSize: 11}}>
<Text style={{ fontSize: 11 }}>
widthAutonomous=true
</Text>
</View>
</View>
);
},
}, {
}
},
{
title: 'Border Radius',
render: function() {
return (
<View style={{borderWidth: 0.5, borderRadius: 5, padding: 5}}>
<Text style={{fontSize: 11}}>
<View style={{ borderWidth: 0.5, borderRadius: 5, padding: 5 }}>
<Text style={{ fontSize: 11 }}>
Too much use of `borderRadius` (especially large radii) on
anything which is scrolling may result in dropped frames.
Use sparingly.
</Text>
</View>
);
},
}, {
}
},
{
title: 'Border Style',
render: function() {
return <ViewBorderStyleExample />;
},
}, {
}
},
{
title: 'Circle with Border Radius',
render: function() {
return (
<View style={{borderRadius: 10, borderWidth: 1, width: 20, height: 20}} />
);
},
}, {
return <View style={{ borderRadius: 10, borderWidth: 1, width: 20, height: 20 }} />;
}
},
{
title: 'Overflow',
render: function() {
return (
<View style={{flexDirection: 'row'}}>
<View style={{ flexDirection: 'row' }}>
<View
style={{
width: 95,
@@ -219,57 +235,59 @@ const examples = [
marginRight: 10,
marginBottom: 5,
overflow: 'hidden',
borderWidth: 0.5,
}}>
<View style={{width: 200, height: 20}}>
borderWidth: 0.5
}}
>
<View style={{ width: 200, height: 20 }}>
<Text>Overflow hidden</Text>
</View>
</View>
<View style={{width: 95, height: 10, marginBottom: 5, borderWidth: 0.5}}>
<View style={{width: 200, height: 20}}>
<View style={{ width: 95, height: 10, marginBottom: 5, borderWidth: 0.5 }}>
<View style={{ width: 200, height: 20 }}>
<Text>Overflow visible</Text>
</View>
</View>
</View>
);
},
}, {
}
},
{
title: 'Opacity',
render: function() {
return (
<View>
<View style={{opacity: 0}}><Text>Opacity 0</Text></View>
<View style={{opacity: 0.1}}><Text>Opacity 0.1</Text></View>
<View style={{opacity: 0.3}}><Text>Opacity 0.3</Text></View>
<View style={{opacity: 0.5}}><Text>Opacity 0.5</Text></View>
<View style={{opacity: 0.7}}><Text>Opacity 0.7</Text></View>
<View style={{opacity: 0.9}}><Text>Opacity 0.9</Text></View>
<View style={{opacity: 1}}><Text>Opacity 1</Text></View>
<View style={{ opacity: 0 }}><Text>Opacity 0</Text></View>
<View style={{ opacity: 0.1 }}><Text>Opacity 0.1</Text></View>
<View style={{ opacity: 0.3 }}><Text>Opacity 0.3</Text></View>
<View style={{ opacity: 0.5 }}><Text>Opacity 0.5</Text></View>
<View style={{ opacity: 0.7 }}><Text>Opacity 0.7</Text></View>
<View style={{ opacity: 0.9 }}><Text>Opacity 0.9</Text></View>
<View style={{ opacity: 1 }}><Text>Opacity 1</Text></View>
</View>
);
},
}, {
}
},
{
title: 'ZIndex',
render: function() {
return <ZIndexExample />;
},
}
},
{
title: 'Basic shadow',
render() {
return <View style={[ styles.shadowBox, styles.shadow ]} />;
return <View style={[styles.shadowBox, styles.shadow]} />;
}
},
{
title: 'Shaped shadow',
description: 'borderRadius: 50',
render() {
return <View style={[ styles.shadowBox, styles.shadow, {borderRadius: 50} ]} />;
return <View style={[styles.shadowBox, styles.shadow, { borderRadius: 50 }]} />;
}
}
];
examples.forEach((example) => {
storiesOf('component: View', module)
.add(example.title, () => example.render())
})
examples.forEach(example => {
storiesOf('component: View', module).add(example.title, () => example.render());
});

View File

@@ -1,7 +1,7 @@
import createReactClass from 'create-react-class';
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { Animated, StyleSheet, Text, View } from 'react-native'
import { storiesOf } from '@kadira/storybook';
import { Animated, StyleSheet, Text, View } from 'react-native';
/**
* Copyright (c) 2013-present, Facebook, Inc.
@@ -25,10 +25,10 @@ import { Animated, StyleSheet, Text, View } from 'react-native'
* @flow
*/
var Flip = createReactClass({
const Flip = createReactClass({
getInitialState() {
return {
theta: new Animated.Value(45),
theta: new Animated.Value(45)
};
},
@@ -40,37 +40,52 @@ var Flip = createReactClass({
this.state.theta.setValue(0);
Animated.timing(this.state.theta, {
toValue: 360,
duration: 5000,
duration: 5000
}).start(this._animate);
},
render() {
return (
<View style={styles.flipCardContainer}>
<Animated.View style={[
styles.flipCard,
{transform: [
{perspective: 850},
{rotateX: this.state.theta.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg']
})},
]}]}>
<Animated.View
style={[
styles.flipCard,
{
transform: [
{ perspective: 850 },
{
rotateX: this.state.theta.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg']
})
}
]
}
]}
>
<Text style={styles.flipText}>
This text is flipping great.
</Text>
</Animated.View>
<Animated.View style={[styles.flipCard, {
position: 'absolute',
top: 0,
backgroundColor: 'red',
transform: [
{perspective: 850},
{rotateX: this.state.theta.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})},
]}]}>
<Animated.View
style={[
styles.flipCard,
{
position: 'absolute',
top: 0,
backgroundColor: 'red',
transform: [
{ perspective: 850 },
{
rotateX: this.state.theta.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
}
]
}
]}
>
<Text style={styles.flipText}>
On the flip side...
</Text>
@@ -80,20 +95,20 @@ var Flip = createReactClass({
}
});
var styles = StyleSheet.create({
const styles = StyleSheet.create({
box1: {
left: 0,
backgroundColor: 'green',
height: 50,
top: 0,
transform: [
{translateX: 100},
{translateY: 50},
{rotate: '30deg'},
{scaleX: 2},
{scaleY: 2},
{ translateX: 100 },
{ translateY: 50 },
{ rotate: '30deg' },
{ scaleX: 2 },
{ scaleY: 2 }
],
width: 50,
width: 50
},
box2: {
left: 0,
@@ -101,23 +116,21 @@ var styles = StyleSheet.create({
height: 50,
top: 0,
transform: [
{scaleX: 2},
{scaleY: 2},
{translateX: 100},
{translateY: 50},
{rotate: '30deg'},
{ scaleX: 2 },
{ scaleY: 2 },
{ translateX: 100 },
{ translateY: 50 },
{ rotate: '30deg' }
],
width: 50,
width: 50
},
box3step1: {
left: 0,
backgroundColor: 'lightpink',
height: 50,
top: 0,
transform: [
{rotate: '30deg'},
],
width: 50,
transform: [{ rotate: '30deg' }],
width: 50
},
box3step2: {
left: 0,
@@ -125,12 +138,8 @@ var styles = StyleSheet.create({
height: 50,
opacity: 0.5,
top: 0,
transform: [
{rotate: '30deg'},
{scaleX: 2},
{scaleY: 2},
],
width: 50,
transform: [{ rotate: '30deg' }, { scaleX: 2 }, { scaleY: 2 }],
width: 50
},
box3step3: {
left: 0,
@@ -139,46 +148,36 @@ var styles = StyleSheet.create({
opacity: 0.5,
top: 0,
transform: [
{rotate: '30deg'},
{scaleX: 2},
{scaleY: 2},
{translateX: 10},
{translateY: 50},
{ rotate: '30deg' },
{ scaleX: 2 },
{ scaleY: 2 },
{ translateX: 10 },
{ translateY: 50 }
],
width: 50,
width: 50
},
box4: {
left: 0,
backgroundColor: 'darkorange',
height: 50,
top: 0,
transform: [
{translateX: 20},
{translateY: 35},
{scale: 2.5},
{rotate: '-0.2rad'},
],
width: 100,
transform: [{ translateX: 20 }, { translateY: 35 }, { scale: 2.5 }, { rotate: '-0.2rad' }],
width: 100
},
box5: {
backgroundColor: 'maroon',
height: 50,
right: 0,
top: 0,
width: 50,
width: 50
},
box5Transform: {
transform: [
{translateX: -50},
{translateY: 35},
{rotate: '50deg'},
{scale: 2},
],
transform: [{ translateX: -50 }, { translateY: 35 }, { rotate: '50deg' }, { scale: 2 }]
},
flipCardContainer: {
marginVertical: 40,
flex: 1,
alignSelf: 'center',
alignSelf: 'center'
},
flipCard: {
width: 200,
@@ -186,13 +185,13 @@ var styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'blue',
backfaceVisibility: 'hidden',
backfaceVisibility: 'hidden'
},
flipText: {
width: 90,
fontSize: 20,
color: 'white',
fontWeight: 'bold',
fontWeight: 'bold'
}
});
@@ -200,88 +199,61 @@ const examples = [
{
title: 'Perspective',
description: 'perspective: 850, rotateX: Animated.timing(0 -> 360)',
render(): ReactElement<any> { return <Flip />; }
render() {
return <Flip />;
}
},
{
title: 'Translate, Rotate, Scale',
description: "translateX: 100, translateY: 50, rotate: '30deg', scaleX: 2, scaleY: 2",
render() {
return (
<View style={styles.box1} />
);
return <View style={styles.box1} />;
}
},
{
title: 'Scale, Translate, Rotate, ',
description: "scaleX: 2, scaleY: 2, translateX: 100, translateY: 50, rotate: '30deg'",
render() {
return (
<View style={styles.box2} />
);
return <View style={styles.box2} />;
}
},
{
title: 'Rotate',
description: "rotate: '30deg'",
render() {
return (
<View style={styles.box3step1} />
);
return <View style={styles.box3step1} />;
}
},
{
title: 'Rotate, Scale',
description: "rotate: '30deg', scaleX: 2, scaleY: 2",
render() {
return (
<View style={styles.box3step2} />
);
return <View style={styles.box3step2} />;
}
},
{
title: 'Rotate, Scale, Translate ',
description: "rotate: '30deg', scaleX: 2, scaleY: 2, translateX: 100, translateY: 50",
render() {
return (
<View style={styles.box3step3} />
);
return <View style={styles.box3step3} />;
}
},
{
title: 'Translate, Scale, Rotate',
description: "translate: [200, 350], scale: 2.5, rotate: '-0.2rad'",
render() {
return (
<View style={styles.box4} />
);
return <View style={styles.box4} />;
}
},
{
title: 'Translate, Rotate, Scale',
description: "translate: [-50, 35], rotate: '50deg', scale: 2",
render() {
return (
<View style={[styles.box5, styles.box5Transform]} />
);
return <View style={[styles.box5, styles.box5Transform]} />;
}
}
];
examples.forEach((example) => {
storiesOf('component: View (transforms)', module)
.add(example.title, () => example.render())
})
examples.forEach(example => {
storiesOf('component: View (transforms)', module).add(example.title, () => example.render());
});

View File

@@ -16,22 +16,14 @@
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {
Animated,
AppRegistry,
StyleSheet,
Text,
TouchableOpacity,
View,
} = ReactNative;
import { any, func, object } from 'prop-types';
import GameBoard from './GameBoard';
import React from 'react';
import { Animated, AppRegistry, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
var GameBoard = require('./GameBoard');
var BOARD_PADDING = 3;
var CELL_MARGIN = 4;
var CELL_SIZE = 60;
const BOARD_PADDING = 3;
const CELL_MARGIN = 4;
const CELL_SIZE = 60;
class Cell extends React.Component {
render() {
@@ -40,13 +32,17 @@ class Cell extends React.Component {
}
class Board extends React.Component {
static propTypes = {
children: any
};
render() {
return (
<View style={styles.board}>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View>
<View style={styles.row}><Cell /><Cell /><Cell /><Cell /></View>
<View style={styles.row}><Cell /><Cell /><Cell /><Cell /></View>
<View style={styles.row}><Cell /><Cell /><Cell /><Cell /></View>
<View style={styles.row}><Cell /><Cell /><Cell /><Cell /></View>
{this.props.children}
</View>
);
@@ -56,6 +52,10 @@ class Board extends React.Component {
class Tile extends React.Component {
state: any;
static propTypes = {
tile: object
};
static _getPosition(index): number {
return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
}
@@ -63,58 +63,54 @@ class Tile extends React.Component {
constructor(props: {}) {
super(props);
var tile = this.props.tile;
const tile = this.props.tile;
this.state = {
opacity: new Animated.Value(0),
top: new Animated.Value(Tile._getPosition(tile.toRow())),
left: new Animated.Value(Tile._getPosition(tile.toColumn())),
left: new Animated.Value(Tile._getPosition(tile.toColumn()))
};
}
calculateOffset(): {top: number; left: number; opacity: number} {
var tile = this.props.tile;
calculateOffset(): { top: number, left: number, opacity: number } {
const tile = this.props.tile;
var offset = {
const offset = {
top: this.state.top,
left: this.state.left,
opacity: this.state.opacity,
opacity: this.state.opacity
};
if (tile.isNew()) {
Animated.timing(this.state.opacity, {
duration: 100,
toValue: 1,
toValue: 1
}).start();
} else {
Animated.parallel([
Animated.timing(offset.top, {
duration: 100,
toValue: Tile._getPosition(tile.toRow()),
toValue: Tile._getPosition(tile.toRow())
}),
Animated.timing(offset.left, {
duration: 100,
toValue: Tile._getPosition(tile.toColumn()),
}),
toValue: Tile._getPosition(tile.toColumn())
})
]).start();
}
return offset;
}
render() {
var tile = this.props.tile;
const tile = this.props.tile;
var tileStyles = [
styles.tile,
styles['tile' + tile.value],
this.calculateOffset(),
];
const tileStyles = [styles.tile, styles['tile' + tile.value], this.calculateOffset()];
var textStyles = [
const textStyles = [
styles.value,
tile.value > 4 && styles.whiteText,
tile.value > 100 && styles.threeDigits,
tile.value > 1000 && styles.fourDigits,
tile.value > 1000 && styles.fourDigits
];
return (
@@ -126,15 +122,19 @@ class Tile extends React.Component {
}
class GameEndOverlay extends React.Component {
static propTypes = {
board: object,
onRestart: func
};
render() {
var board = this.props.board;
const board = this.props.board;
if (!board.hasWon() && !board.hasLost()) {
return <View/>;
return <View />;
}
var message = board.hasWon() ?
'Good Job!' : 'Game Over';
const message = board.hasWon() ? 'Good Job!' : 'Game Over';
return (
<View style={styles.overlay}>
@@ -155,34 +155,34 @@ class Game2048 extends React.Component {
constructor(props: {}) {
super(props);
this.state = {
board: new GameBoard(),
board: new GameBoard()
};
this.startX = 0;
this.startY = 0;
}
restartGame() {
this.setState({board: new GameBoard()});
}
_handleRestart = () => {
this.setState({ board: new GameBoard() });
};
handleTouchStart(event: Object) {
_handleTouchStart = (event: Object) => {
if (this.state.board.hasWon()) {
return;
}
this.startX = event.nativeEvent.pageX;
this.startY = event.nativeEvent.pageY;
}
};
handleTouchEnd(event: Object) {
_handleTouchEnd = (event: Object) => {
if (this.state.board.hasWon()) {
return;
}
var deltaX = event.nativeEvent.pageX - this.startX;
var deltaY = event.nativeEvent.pageY - this.startY;
const deltaX = event.nativeEvent.pageX - this.startX;
const deltaY = event.nativeEvent.pageY - this.startY;
var direction = -1;
let direction = -1;
if (Math.abs(deltaX) > 3 * Math.abs(deltaY) && Math.abs(deltaX) > 30) {
direction = deltaX > 0 ? 2 : 0;
} else if (Math.abs(deltaY) > 3 * Math.abs(deltaX) && Math.abs(deltaY) > 30) {
@@ -190,39 +190,40 @@ class Game2048 extends React.Component {
}
if (direction !== -1) {
this.setState({board: this.state.board.move(direction)});
this.setState({ board: this.state.board.move(direction) });
}
}
};
render() {
var tiles = this.state.board.tiles
.filter((tile) => tile.value)
.map((tile) => <Tile ref={tile.id} key={tile.id} tile={tile} />);
const tiles = this.state.board.tiles
.filter(tile => tile.value)
.map(tile => <Tile key={tile.id} ref={tile.id} tile={tile} />);
return (
<View
onTouchEnd={this._handleTouchEnd}
onTouchStart={this._handleTouchStart}
style={styles.container}
onTouchStart={(event) => this.handleTouchStart(event)}
onTouchEnd={(event) => this.handleTouchEnd(event)}>
>
<Board>
{tiles}
</Board>
<GameEndOverlay board={this.state.board} onRestart={() => this.restartGame()} />
<GameEndOverlay board={this.state.board} onRestart={this._handleRestart} />
</View>
);
}
}
var styles = StyleSheet.create({
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
board: {
padding: BOARD_PADDING,
backgroundColor: '#bbaaaa',
borderRadius: 5,
borderRadius: 5
},
overlay: {
position: 'absolute',
@@ -234,31 +235,31 @@ var styles = StyleSheet.create({
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
overlayMessage: {
fontSize: 40,
marginBottom: 20,
marginBottom: 20
},
tryAgain: {
backgroundColor: '#887761',
padding: 20,
borderRadius: 5,
borderRadius: 5
},
tryAgainText: {
color: '#ffffff',
fontSize: 20,
fontWeight: '500',
fontWeight: '500'
},
cell: {
width: CELL_SIZE,
height: CELL_SIZE,
borderRadius: 5,
backgroundColor: '#ddccbb',
margin: CELL_MARGIN,
margin: CELL_MARGIN
},
row: {
flexDirection: 'row',
flexDirection: 'row'
},
tile: {
position: 'absolute',
@@ -268,56 +269,56 @@ var styles = StyleSheet.create({
borderRadius: 5,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
value: {
fontSize: 24,
color: '#776666',
fontFamily: 'Verdana',
fontWeight: '500',
fontWeight: '500'
},
tile2: {
backgroundColor: '#eeeeee',
backgroundColor: '#eeeeee'
},
tile4: {
backgroundColor: '#eeeecc',
backgroundColor: '#eeeecc'
},
tile8: {
backgroundColor: '#ffbb87',
backgroundColor: '#ffbb87'
},
tile16: {
backgroundColor: '#ff9966',
backgroundColor: '#ff9966'
},
tile32: {
backgroundColor: '#ff7755',
backgroundColor: '#ff7755'
},
tile64: {
backgroundColor: '#ff5533',
backgroundColor: '#ff5533'
},
tile128: {
backgroundColor: '#eecc77',
backgroundColor: '#eecc77'
},
tile256: {
backgroundColor: '#eecc66',
backgroundColor: '#eecc66'
},
tile512: {
backgroundColor: '#eecc55',
backgroundColor: '#eecc55'
},
tile1024: {
backgroundColor: '#eecc33',
backgroundColor: '#eecc33'
},
tile2048: {
backgroundColor: '#eecc22',
backgroundColor: '#eecc22'
},
whiteText: {
color: '#ffffff',
color: '#ffffff'
},
threeDigits: {
fontSize: 20,
fontSize: 20
},
fourDigits: {
fontSize: 18,
},
fontSize: 18
}
});
AppRegistry.registerComponent('Game2048', () => Game2048);

View File

@@ -0,0 +1,5 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import Game2048 from './Game2048';
storiesOf('demo: Game2048', module).add('the game', () => <Game2048 />);

View File

@@ -19,20 +19,20 @@
// NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js
// with no modification except to format it for CommonJS and fix lint/flow errors
var rotateLeft = function (matrix) {
var rows = matrix.length;
var columns = matrix[0].length;
var res = [];
for (var row = 0; row < rows; ++row) {
const rotateLeft = function(matrix) {
const rows = matrix.length;
const columns = matrix[0].length;
const res = [];
for (let row = 0; row < rows; ++row) {
res.push([]);
for (var column = 0; column < columns; ++column) {
for (let column = 0; column < columns; ++column) {
res[row][column] = matrix[column][columns - row - 1];
}
}
return res;
};
var Tile = function (value?: number, row?: number, column?: number) {
const Tile = function(value?: number, row?: number, column?: number) {
this.value = value || 0;
this.row = row || -1;
@@ -46,42 +46,45 @@ var Tile = function (value?: number, row?: number, column?: number) {
Tile.id = 0;
Tile.prototype.moveTo = function (row, column) {
Tile.prototype.moveTo = function(row, column) {
this.oldRow = this.row;
this.oldColumn = this.column;
this.row = row;
this.column = column;
};
Tile.prototype.isNew = function () {
Tile.prototype.isNew = function() {
return this.oldRow === -1 && !this.mergedInto;
};
Tile.prototype.hasMoved = function () {
return (this.fromRow() !== -1 && (this.fromRow() !== this.toRow() || this.fromColumn() !== this.toColumn())) ||
this.mergedInto;
Tile.prototype.hasMoved = function() {
return (
(this.fromRow() !== -1 &&
(this.fromRow() !== this.toRow() || this.fromColumn() !== this.toColumn())) ||
this.mergedInto
);
};
Tile.prototype.fromRow = function () {
Tile.prototype.fromRow = function() {
return this.mergedInto ? this.row : this.oldRow;
};
Tile.prototype.fromColumn = function () {
Tile.prototype.fromColumn = function() {
return this.mergedInto ? this.column : this.oldColumn;
};
Tile.prototype.toRow = function () {
Tile.prototype.toRow = function() {
return this.mergedInto ? this.mergedInto.row : this.row;
};
Tile.prototype.toColumn = function () {
Tile.prototype.toColumn = function() {
return this.mergedInto ? this.mergedInto.column : this.column;
};
var Board = function () {
const Board = function() {
this.tiles = [];
this.cells = [];
for (var i = 0; i < Board.size; ++i) {
for (let i = 0; i < Board.size; ++i) {
this.cells[i] = [this.addTile(), this.addTile(), this.addTile(), this.addTile()];
}
this.addRandomTile();
@@ -89,42 +92,44 @@ var Board = function () {
this.won = false;
};
Board.prototype.addTile = function () {
var res = new Tile();
Tile.apply(res, arguments);
Board.prototype.addTile = function(...args) {
const res = new Tile();
Tile.apply(res, args);
this.tiles.push(res);
return res;
};
Board.size = 4;
Board.prototype.moveLeft = function () {
var hasChanged = false;
for (var row = 0; row < Board.size; ++row) {
var currentRow = this.cells[row].filter(function (tile) { return tile.value !== 0; });
var resultRow = [];
for (var target = 0; target < Board.size; ++target) {
var targetTile = currentRow.length ? currentRow.shift() : this.addTile();
Board.prototype.moveLeft = function() {
let hasChanged = false;
for (let row = 0; row < Board.size; ++row) {
const currentRow = this.cells[row].filter(function(tile) {
return tile.value !== 0;
});
const resultRow = [];
for (let target = 0; target < Board.size; ++target) {
let targetTile = currentRow.length ? currentRow.shift() : this.addTile();
if (currentRow.length > 0 && currentRow[0].value === targetTile.value) {
var tile1 = targetTile;
const tile1 = targetTile;
targetTile = this.addTile(targetTile.value);
tile1.mergedInto = targetTile;
var tile2 = currentRow.shift();
const tile2 = currentRow.shift();
tile2.mergedInto = targetTile;
targetTile.value += tile2.value;
}
resultRow[target] = targetTile;
this.won = this.won || (targetTile.value === 2048);
hasChanged = hasChanged || (targetTile.value !== this.cells[row][target].value);
this.won = this.won || targetTile.value === 2048;
hasChanged = hasChanged || targetTile.value !== this.cells[row][target].value;
}
this.cells[row] = resultRow;
}
return hasChanged;
};
Board.prototype.setPositions = function () {
this.cells.forEach(function (row, rowIndex) {
row.forEach(function (tile, columnIndex) {
Board.prototype.setPositions = function() {
this.cells.forEach(function(row, rowIndex) {
row.forEach(function(tile, columnIndex) {
tile.oldRow = tile.row;
tile.oldColumn = tile.column;
tile.row = rowIndex;
@@ -136,29 +141,29 @@ Board.prototype.setPositions = function () {
Board.fourProbability = 0.1;
Board.prototype.addRandomTile = function () {
var emptyCells = [];
for (var r = 0; r < Board.size; ++r) {
for (var c = 0; c < Board.size; ++c) {
Board.prototype.addRandomTile = function() {
const emptyCells = [];
for (let r = 0; r < Board.size; ++r) {
for (let c = 0; c < Board.size; ++c) {
if (this.cells[r][c].value === 0) {
emptyCells.push({r: r, c: c});
emptyCells.push({ r: r, c: c });
}
}
}
var index = Math.floor(Math.random() * emptyCells.length);
var cell = emptyCells[index];
var newValue = Math.random() < Board.fourProbability ? 4 : 2;
const index = Math.floor(Math.random() * emptyCells.length);
const cell = emptyCells[index];
const newValue = Math.random() < Board.fourProbability ? 4 : 2;
this.cells[cell.r][cell.c] = this.addTile(newValue);
};
Board.prototype.move = function (direction) {
Board.prototype.move = function(direction) {
// 0 -> left, 1 -> up, 2 -> right, 3 -> down
this.clearOldTiles();
for (var i = 0; i < direction; ++i) {
for (let i = 0; i < direction; ++i) {
this.cells = rotateLeft(this.cells);
}
var hasChanged = this.moveLeft();
for (var i = direction; i < 4; ++i) {
const hasChanged = this.moveLeft();
for (let i = direction; i < 4; ++i) {
this.cells = rotateLeft(this.cells);
}
if (hasChanged) {
@@ -168,30 +173,34 @@ Board.prototype.move = function (direction) {
return this;
};
Board.prototype.clearOldTiles = function () {
this.tiles = this.tiles.filter(function (tile) { return tile.markForDeletion === false; });
this.tiles.forEach(function (tile) { tile.markForDeletion = true; });
Board.prototype.clearOldTiles = function() {
this.tiles = this.tiles.filter(function(tile) {
return tile.markForDeletion === false;
});
this.tiles.forEach(function(tile) {
tile.markForDeletion = true;
});
};
Board.prototype.hasWon = function () {
Board.prototype.hasWon = function() {
return this.won;
};
Board.deltaX = [-1, 0, 1, 0];
Board.deltaY = [0, -1, 0, 1];
Board.prototype.hasLost = function () {
var canMove = false;
for (var row = 0; row < Board.size; ++row) {
for (var column = 0; column < Board.size; ++column) {
canMove = canMove || (this.cells[row][column].value === 0);
for (var dir = 0; dir < 4; ++dir) {
var newRow = row + Board.deltaX[dir];
var newColumn = column + Board.deltaY[dir];
Board.prototype.hasLost = function() {
let canMove = false;
for (let row = 0; row < Board.size; ++row) {
for (let column = 0; column < Board.size; ++column) {
canMove = canMove || this.cells[row][column].value === 0;
for (let dir = 0; dir < 4; ++dir) {
const newRow = row + Board.deltaX[dir];
const newColumn = column + Board.deltaY[dir];
if (newRow < 0 || newRow >= Board.size || newColumn < 0 || newColumn >= Board.size) {
continue;
}
canMove = canMove || (this.cells[row][column].value === this.cells[newRow][newColumn].value);
canMove = canMove || this.cells[row][column].value === this.cells[newRow][newColumn].value;
}
}
}

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/jsx-no-bind */
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
@@ -14,29 +16,21 @@
* @providesModule TicTacToeApp
* @flow
*/
'use strict';
import createReactClass from 'create-react-class';
var React = require('react');
var ReactNative = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
TouchableHighlight,
View,
} = ReactNative;
import React from 'react';
import { AppRegistry, StyleSheet, Text, TouchableHighlight, View } from 'react-native';
class Board {
grid: Array<Array<number>>;
turn: number;
constructor() {
var size = 3;
var grid = Array(size);
for (var i = 0; i < size; i++) {
var row = Array(size);
for (var j = 0; j < size; j++) {
const size = 3;
const grid = Array(size);
for (let i = 0; i < size; i++) {
const row = Array(size);
for (let j = 0; j < size; j++) {
row[j] = 0;
}
grid[i] = row;
@@ -56,27 +50,39 @@ class Board {
}
winner(): ?number {
for (var i = 0; i < 3; i++) {
if (this.grid[i][0] !== 0 && this.grid[i][0] === this.grid[i][1] &&
this.grid[i][0] === this.grid[i][2]) {
for (let i = 0; i < 3; i++) {
if (
this.grid[i][0] !== 0 &&
this.grid[i][0] === this.grid[i][1] &&
this.grid[i][0] === this.grid[i][2]
) {
return this.grid[i][0];
}
}
for (var i = 0; i < 3; i++) {
if (this.grid[0][i] !== 0 && this.grid[0][i] === this.grid[1][i] &&
this.grid[0][i] === this.grid[2][i]) {
for (let i = 0; i < 3; i++) {
if (
this.grid[0][i] !== 0 &&
this.grid[0][i] === this.grid[1][i] &&
this.grid[0][i] === this.grid[2][i]
) {
return this.grid[0][i];
}
}
if (this.grid[0][0] !== 0 && this.grid[0][0] === this.grid[1][1] &&
this.grid[0][0] === this.grid[2][2]) {
if (
this.grid[0][0] !== 0 &&
this.grid[0][0] === this.grid[1][1] &&
this.grid[0][0] === this.grid[2][2]
) {
return this.grid[0][0];
}
if (this.grid[0][2] !== 0 && this.grid[0][2] === this.grid[1][1] &&
this.grid[0][2] === this.grid[2][0]) {
if (
this.grid[0][2] !== 0 &&
this.grid[0][2] === this.grid[1][1] &&
this.grid[0][2] === this.grid[2][0]
) {
return this.grid[0][2];
}
@@ -84,8 +90,8 @@ class Board {
}
tie(): boolean {
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (this.grid[i][j] === 0) {
return false;
}
@@ -95,7 +101,7 @@ class Board {
}
}
var Cell = createReactClass({
const Cell = createReactClass({
cellStyle() {
switch (this.props.player) {
case 1:
@@ -132,9 +138,10 @@ var Cell = createReactClass({
render() {
return (
<TouchableHighlight
activeOpacity={0.5}
onPress={this.props.onPress}
underlayColor="transparent"
activeOpacity={0.5}>
>
<View style={[styles.cell, this.cellStyle()]}>
<Text style={[styles.cellText, this.textStyle()]}>
{this.textContents()}
@@ -145,19 +152,19 @@ var Cell = createReactClass({
}
});
var GameEndOverlay = createReactClass({
const GameEndOverlay = createReactClass({
render() {
var board = this.props.board;
const board = this.props.board;
var tie = board.tie();
var winner = board.winner();
const tie = board.tie();
const winner = board.winner();
if (!winner && !tie) {
return <View />;
}
var message;
let message;
if (tie) {
message = 'It\'s a tie!';
message = "It's a tie!";
} else {
message = (winner === 1 ? 'X' : 'O') + ' wins!';
}
@@ -166,9 +173,10 @@ var GameEndOverlay = createReactClass({
<View style={styles.overlay}>
<Text style={styles.overlayMessage}>{message}</Text>
<TouchableHighlight
activeOpacity={0.5}
onPress={this.props.onRestart}
underlayColor="transparent"
activeOpacity={0.5}>
>
<View style={styles.newGame}>
<Text style={styles.newGameText}>New Game</Text>
</View>
@@ -178,7 +186,7 @@ var GameEndOverlay = createReactClass({
}
});
var TicTacToeApp = createReactClass({
const TicTacToeApp = createReactClass({
getInitialState() {
return { board: new Board(), player: 1 };
},
@@ -198,22 +206,22 @@ var TicTacToeApp = createReactClass({
this.setState({
board: this.state.board.mark(row, col, this.state.player),
player: this.nextPlayer(),
player: this.nextPlayer()
});
},
render() {
var rows = this.state.board.grid.map((cells, row) =>
const rows = this.state.board.grid.map((cells, row) => (
<View key={'row' + row} style={styles.row}>
{cells.map((player, col) =>
{cells.map((player, col) => (
<Cell
key={'cell' + col}
player={player}
onPress={this.handleCellPress.bind(this, row, col)}
player={player}
/>
)}
))}
</View>
);
));
return (
<View style={styles.container}>
@@ -221,16 +229,13 @@ var TicTacToeApp = createReactClass({
<View style={styles.board}>
{rows}
</View>
<GameEndOverlay
board={this.state.board}
onRestart={this.restartGame}
/>
<GameEndOverlay board={this.state.board} onRestart={this.restartGame} />
</View>
);
}
});
var styles = StyleSheet.create({
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
@@ -240,15 +245,15 @@ var styles = StyleSheet.create({
title: {
fontFamily: 'Chalkduster',
fontSize: 39,
marginBottom: 20,
marginBottom: 20
},
board: {
padding: 5,
backgroundColor: '#47525d',
borderRadius: 10,
borderRadius: 10
},
row: {
flexDirection: 'row',
flexDirection: 'row'
},
// CELL
@@ -261,13 +266,13 @@ var styles = StyleSheet.create({
margin: 5,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
cellX: {
backgroundColor: '#72d0eb',
backgroundColor: '#72d0eb'
},
cellO: {
backgroundColor: '#7ebd26',
backgroundColor: '#7ebd26'
},
// CELL TEXT
@@ -275,13 +280,13 @@ var styles = StyleSheet.create({
cellText: {
borderRadius: 5,
fontSize: 50,
fontFamily: 'AvenirNext-Bold',
fontFamily: 'AvenirNext-Bold'
},
cellTextX: {
color: '#19a9e5',
color: '#19a9e5'
},
cellTextO: {
color: '#b9dc2f',
color: '#b9dc2f'
},
// GAME OVER
@@ -296,7 +301,7 @@ var styles = StyleSheet.create({
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
alignItems: 'center'
},
overlayMessage: {
fontSize: 40,
@@ -304,18 +309,18 @@ var styles = StyleSheet.create({
marginLeft: 20,
marginRight: 20,
fontFamily: 'AvenirNext-DemiBold',
textAlign: 'center',
textAlign: 'center'
},
newGame: {
backgroundColor: '#887765',
padding: 20,
borderRadius: 5,
borderRadius: 5
},
newGameText: {
color: 'white',
fontSize: 20,
fontFamily: 'AvenirNext-DemiBold',
},
fontFamily: 'AvenirNext-DemiBold'
}
});
AppRegistry.registerComponent('TicTacToeApp', () => TicTacToeApp);

View File

@@ -0,0 +1,5 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import TicTacToe from './TicTacToe';
storiesOf('demo: TicTacToe', module).add('the game', () => <TicTacToe />);

View File

@@ -1,12 +0,0 @@
import { configure, addDecorator } from '@kadira/storybook'
import centered from './decorator-centered'
const context = require.context('../', true, /Example\.js$/)
addDecorator(centered)
function loadStories() {
context.keys().forEach(context)
}
configure(loadStories, module)

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