mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 09:24:09 +08:00
Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cda89c5ce | ||
|
|
695eba45af | ||
|
|
92a2cb274a | ||
|
|
b1ca04d11e | ||
|
|
22ab70ea6f | ||
|
|
49f36d8eb1 | ||
|
|
80ba119b83 | ||
|
|
c30b67f6db | ||
|
|
4580f93199 | ||
|
|
4c46126ffe | ||
|
|
f8d5c15405 | ||
|
|
dc54e03625 | ||
|
|
4d5819ae28 | ||
|
|
5c482ef3be | ||
|
|
f51592f96e | ||
|
|
6bffe1775f | ||
|
|
7e75d037f2 | ||
|
|
7536508fe3 | ||
|
|
945fff0015 | ||
|
|
5032ed6fe1 | ||
|
|
8c7cdbf4be | ||
|
|
e5d8857bcc | ||
|
|
cda8d05bb7 | ||
|
|
049edc4611 | ||
|
|
e76d5a4e6c | ||
|
|
f71dae7d93 | ||
|
|
94d31beaf4 | ||
|
|
f5f9389728 | ||
|
|
fdbd19a4af | ||
|
|
36eafbc2f5 | ||
|
|
bca3398c1c | ||
|
|
722d77e8e5 | ||
|
|
d65c92eea9 | ||
|
|
8dd39c681c | ||
|
|
0b1759363d | ||
|
|
abd1227a94 | ||
|
|
8352c7cbda | ||
|
|
89f5a13891 | ||
|
|
4005f9ddde | ||
|
|
f192a9ba26 | ||
|
|
e688a949fb | ||
|
|
77605cb27c | ||
|
|
4f71833aec | ||
|
|
fa14995a35 | ||
|
|
4beae0dd78 | ||
|
|
5598961d2c | ||
|
|
4613baf0e8 | ||
|
|
3b661d8d6d | ||
|
|
22d20706e3 | ||
|
|
0b2813b186 | ||
|
|
b248de552d | ||
|
|
2b826dc7f4 | ||
|
|
b46acd4f50 | ||
|
|
5a03cb25cb | ||
|
|
44e60d12e3 | ||
|
|
fc60f8d332 | ||
|
|
2a65ca6fc0 | ||
|
|
9db3bd7e67 | ||
|
|
1963e9109a | ||
|
|
14072c7471 | ||
|
|
0af6dc00fc | ||
|
|
c9d401f09a | ||
|
|
8aeeed0ef7 | ||
|
|
f5d0f73b4f | ||
|
|
ee7d367062 | ||
|
|
dbd607ce47 | ||
|
|
373cb38ca9 | ||
|
|
4576b42365 | ||
|
|
5a5707855b | ||
|
|
0c76cc5d80 | ||
|
|
d64df129b2 | ||
|
|
763c5444ce | ||
|
|
88d13f06f8 | ||
|
|
3dd94880e0 | ||
|
|
e1080d72d7 | ||
|
|
55849cdd0d | ||
|
|
0aef117733 | ||
|
|
977d8729f5 | ||
|
|
9222cbf4bd | ||
|
|
103560fc11 | ||
|
|
3a2daf386d | ||
|
|
6640b61b3e | ||
|
|
07d1124d60 | ||
|
|
c7b3a8e60b | ||
|
|
d32eec7239 | ||
|
|
f8f2898095 | ||
|
|
201bfd2e4d | ||
|
|
496839d19a | ||
|
|
6fe3f1f533 | ||
|
|
aa53d931d5 | ||
|
|
88b184d540 | ||
|
|
011affb110 | ||
|
|
87a4f56a89 | ||
|
|
2f94295739 | ||
|
|
fdf4a88251 | ||
|
|
acc7032d60 | ||
|
|
0fc5644959 | ||
|
|
be923ec453 | ||
|
|
248c2e1258 | ||
|
|
2e822c068d | ||
|
|
fb5406e4ec | ||
|
|
638479991e | ||
|
|
fb41be1bf7 | ||
|
|
2291544373 | ||
|
|
6416166bc3 | ||
|
|
f1e221e51e | ||
|
|
27edfd9d88 | ||
|
|
6cadc22ad5 | ||
|
|
5c8cdd7742 | ||
|
|
e3450ed26c | ||
|
|
d54a84701a | ||
|
|
8201906703 | ||
|
|
10f88670ed | ||
|
|
1be2c810d1 | ||
|
|
6f75fb3e0d | ||
|
|
424e6b5994 | ||
|
|
769931061a | ||
|
|
7aa760506a | ||
|
|
9401eb9b47 | ||
|
|
6a9212df40 | ||
|
|
f2772b89bf | ||
|
|
9e970b3c34 | ||
|
|
9677e9da0a | ||
|
|
2440e74e99 | ||
|
|
66b0387023 | ||
|
|
94f37740af | ||
|
|
e1991f8f6b | ||
|
|
21eeafabd5 | ||
|
|
249f157ed9 |
3
.babelrc
3
.babelrc
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"presets": [
|
||||
"react-native"
|
||||
],
|
||||
"plugins": [
|
||||
[ "transform-react-remove-prop-types", { "mode": "wrap" } ]
|
||||
]
|
||||
}
|
||||
|
||||
215
.eslintrc
215
.eslintrc
@@ -1,14 +1,213 @@
|
||||
{
|
||||
// babel parser to support ES features
|
||||
// babel parser to support ES6/7 features
|
||||
"parser": "babel-eslint",
|
||||
// based on https://github.com/feross/standard
|
||||
"extends": [ "standard", "standard-react" ],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 7,
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"jsx-a11y",
|
||||
"promise",
|
||||
"react"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"document": false,
|
||||
"navigator": false,
|
||||
"window": false
|
||||
},
|
||||
"rules": {
|
||||
// overrides of the standard style
|
||||
"space-before-function-paren": [ 2, { "anonymous": "always", "named": "never" } ],
|
||||
"wrap-iife": [ 2, "outside" ],
|
||||
// overrides of the standard-react style
|
||||
"accessor-pairs": 2,
|
||||
"array-bracket-spacing": ["error", "always"],
|
||||
"arrow-parens": [2, "always"],
|
||||
"arrow-spacing": [2, { "before": true, "after": true }],
|
||||
"block-spacing": [2, "always"],
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||
"camelcase": 0,
|
||||
"comma-dangle": [2, "never"],
|
||||
"comma-spacing": [2, { "before": false, "after": true }],
|
||||
"comma-style": [2, "last"],
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
"constructor-super": 2,
|
||||
"curly": [2, "all"],
|
||||
"default-case": [2, { commentPattern: '^no default$' }],
|
||||
"dot-location": [2, "property"],
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [2, "allow-null"],
|
||||
"generator-star-spacing": [2, { "before": true, "after": true }],
|
||||
"handle-callback-err": [2, "^(err|error)$" ],
|
||||
"indent": [2, 2, { "SwitchCase": 1 }],
|
||||
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||
"keyword-spacing": [2, { "before": true, "after": true }],
|
||||
"max-len": [2, 120, 4],
|
||||
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||
"new-parens": 2,
|
||||
"no-alert": 1,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-case-declarations": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-duplicate-imports": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eval": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"no-extra-semi": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-inner-declarations": [2, "functions"],
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [2, { "max": 1 }],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-new-require": 2,
|
||||
"no-new-symbol": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-path-concat": 2,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-return-assign": [2, "except-parens"],
|
||||
"no-script-url": 2,
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unmodified-loop-condition": 2,
|
||||
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
|
||||
"no-unreachable": 2,
|
||||
"no-unsafe-finally": 2,
|
||||
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
|
||||
"no-useless-call": 2,
|
||||
"no-useless-computed-key": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-useless-constructor": 2,
|
||||
"no-useless-escape": 2,
|
||||
"no-var": 2,
|
||||
"no-whitespace-before-property": 2,
|
||||
"no-with": 2,
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"operator-linebreak": [2, "after"],
|
||||
"padded-blocks": [2, "never"],
|
||||
"prefer-const": 2,
|
||||
"prefer-rest-params": 2,
|
||||
"prefer-template": 2,
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"radix": 2,
|
||||
"rest-spread-spacing": ["error"],
|
||||
"semi": [2, "always"],
|
||||
"semi-spacing": [2, { "before": false, "after": true }],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, { "anonymous": "always", "named": "never" }],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
|
||||
"template-curly-spacing": [2, "never"],
|
||||
"use-isnan": 2,
|
||||
"valid-typeof": 2,
|
||||
"wrap-iife": [2, "outside"],
|
||||
"yield-star-spacing": [2, "both"],
|
||||
"yoda": [2, "never"],
|
||||
|
||||
// promise
|
||||
"promise/param-names": 2,
|
||||
|
||||
// jsx accessibility
|
||||
"jsx-a11y/aria-props": 2,
|
||||
"jsx-a11y/aria-proptypes": 2,
|
||||
"jsx-a11y/aria-role": 2,
|
||||
"jsx-a11y/aria-unsupported-elements": 2,
|
||||
"jsx-a11y/heading-has-content": 2,
|
||||
"jsx-a11y/href-no-hash": 2,
|
||||
"jsx-a11y/html-has-lang": 2,
|
||||
"jsx-a11y/img-has-alt": 2,
|
||||
"jsx-a11y/img-redundant-alt": 2,
|
||||
"jsx-a11y/label-has-for": 2,
|
||||
"jsx-a11y/mouse-events-have-key-events": 2,
|
||||
"jsx-a11y/no-access-key": 2,
|
||||
"jsx-a11y/no-marquee": 2,
|
||||
"jsx-a11y/no-onchange": 0,
|
||||
"jsx-a11y/onclick-has-focus": 2,
|
||||
"jsx-a11y/onclick-has-role": 2,
|
||||
"jsx-a11y/role-has-required-aria-props": 2,
|
||||
"jsx-a11y/role-supports-aria-props": 2,
|
||||
"jsx-a11y/scope": 2,
|
||||
"jsx-a11y/tabindex-no-positive": 2,
|
||||
|
||||
// react
|
||||
"jsx-quotes": [2, "prefer-single"],
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 2,
|
||||
"react/jsx-handler-names": [2, {
|
||||
"eventHandlerPrefix": "_handle"
|
||||
}],
|
||||
"react/jsx-indent": [2, 2],
|
||||
"react/jsx-indent-props": [2, 2],
|
||||
"react/jsx-no-bind": 2,
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-pascal-case": 2,
|
||||
"react/jsx-sort-props": 2,
|
||||
"react/jsx-sort-prop-types": 2
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/no-did-mount-set-state": 0,
|
||||
"react/no-did-update-set-state": 2,
|
||||
"react/no-direct-mutation-state": 2,
|
||||
"react/no-multi-comp": 0,
|
||||
"react/no-string-refs": 2,
|
||||
"react/no-unknown-property": 2,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/prop-types": 2,
|
||||
"react/react-in-jsx-scope": 0,
|
||||
"react/self-closing-comp": 2,
|
||||
"react/sort-comp": 0,
|
||||
"react/sort-prop-types": 2,
|
||||
"react/wrap-multilines": 0
|
||||
}
|
||||
}
|
||||
|
||||
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
React Native for Web is an implementation of React Native. If you have feature
|
||||
requests, you should post them to https://productpains.com/product/react-native/.
|
||||
|
||||
GitHub issues should only be used for bugs or Web-specific features you believe
|
||||
React Native requires.
|
||||
|
||||
Make sure to add ALL the information needed to understand the bug so that
|
||||
someone can help. If the info is missing we'll add the 'needs more information'
|
||||
label and close the issue until there is enough information.
|
||||
-->
|
||||
|
||||
**What is the current behavior?**
|
||||
|
||||
Link to minimal test case: (template: [codepen](https://codepen.io/necolas/pen/PZzwBR?editors=0010))
|
||||
|
||||
**What is the expected behaviour?**
|
||||
|
||||
**Steps to reproduce**
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
**Environment (include versions)**
|
||||
|
||||
OS:
|
||||
Device:
|
||||
Browser:
|
||||
React Native for Web (version):
|
||||
React (version):
|
||||
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
Thanks for submitting a pull request! Make sure the PR does only one thing.
|
||||
Please provide enough information so that others can review your pull
|
||||
request. Make sure you have read the Contributing Guidelines -
|
||||
https://github.com/necolas/react-native-web/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
**This patch solves the following problem**
|
||||
|
||||
**Test plan**
|
||||
|
||||
**This pull request**
|
||||
|
||||
- [ ] includes documentation
|
||||
- [ ] includes tests
|
||||
- [ ] includes an interactive example
|
||||
- [ ] includes screenshots/videos
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/dist
|
||||
/dist-examples
|
||||
/node_modules
|
||||
|
||||
@@ -5,5 +5,4 @@ before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
|
||||
@@ -19,6 +19,12 @@ Fork, then clone the repo:
|
||||
git clone https://github.com/your-username/react-native-web.git
|
||||
```
|
||||
|
||||
Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
|
||||
|
||||
```
|
||||
yarn
|
||||
```
|
||||
|
||||
Run the examples:
|
||||
|
||||
```
|
||||
@@ -51,7 +57,7 @@ To continuously watch and run tests, run the following:
|
||||
npm run test:watch
|
||||
```
|
||||
|
||||
To perform linting, run the following:
|
||||
To perform only linting, run the following:
|
||||
|
||||
```
|
||||
npm run lint
|
||||
|
||||
145
README.md
145
README.md
@@ -2,51 +2,104 @@
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![npm version][npm-image]][npm-url]
|
||||

|
||||
|
||||
[React Native][react-native-url] components and APIs for the Web.
|
||||
|
||||
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
|
||||
[npm-image]: https://badge.fury.io/js/react-native-web.svg
|
||||
[npm-url]: https://npmjs.org/package/react-native-web
|
||||
[react-native-url]: https://facebook.github.io/react-native/
|
||||
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/necolas/react-native-web
|
||||
|
||||
## Overview
|
||||
|
||||
"React Native for Web" is a project to bring React Native's building blocks and
|
||||
touch handling to the Web.
|
||||
touch handling to the Web. [Read more](#why).
|
||||
|
||||
React Native provides a foundational layer to support interoperable,
|
||||
zero-configuration React component development. This is missing from React's
|
||||
web ecosystem where OSS components rely on inline styles (usually without
|
||||
vendor prefixes), or require build tool configuration. This project allows
|
||||
components built upon React Native to be run on the Web, and it manages all
|
||||
component styling out-of-the-box.
|
||||
|
||||
For example, the [`View`](docs/components/View.md) component makes it easy to build
|
||||
cross-browser layouts with flexbox, such as stacked and nested boxes with
|
||||
margin and padding. And the [`StyleSheet`](docs/guides/style.md) API converts
|
||||
styles defined in JavaScript into "Atomic CSS".
|
||||
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](http://codepen.io/necolas/pen/PZzwBR).
|
||||
|
||||
## Quick start
|
||||
|
||||
To install in your app:
|
||||
|
||||
```
|
||||
npm install --save react react-native-web
|
||||
npm install --save react@15.3 react-native-web
|
||||
```
|
||||
|
||||
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
|
||||
|
||||
You can also bootstrap a standard React Native project structure for web by
|
||||
using [react-native-web-starter](https://github.com/grabcode/react-native-web-starter).
|
||||
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).
|
||||
|
||||
## Examples
|
||||
## Documentation
|
||||
|
||||
Demos:
|
||||
Guides:
|
||||
|
||||
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
|
||||
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
|
||||
* [2048](http://codepen.io/necolas/full/wMVvxj/)
|
||||
* [Accessibility](docs/guides/accessibility.md)
|
||||
* [Client and server rendering](docs/guides/rendering.md)
|
||||
* [Direct manipulation](docs/guides/direct-manipulation.md)
|
||||
* [Internationalization](docs/guides/internationalization.md)
|
||||
* [Known issues](docs/guides/known-issues.md)
|
||||
* [React Native](docs/guides/react-native.md)
|
||||
* [Style](docs/guides/style.md)
|
||||
|
||||
Sample:
|
||||
Exported modules:
|
||||
|
||||
* Components
|
||||
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
|
||||
* [`Button`](docs/components/Button.md)
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`ProgressBar`](docs/components/ProgressBar.md)
|
||||
* [`ScrollView`](docs/components/ScrollView.md)
|
||||
* [`Switch`](docs/components/Switch.md)
|
||||
* [`Text`](docs/components/Text.md)
|
||||
* [`TextInput`](docs/components/TextInput.md)
|
||||
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
|
||||
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
|
||||
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
|
||||
* [`View`](docs/components/View.md)
|
||||
* APIs
|
||||
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
|
||||
* [`AppRegistry`](docs/apis/AppRegistry.md)
|
||||
* [`AppState`](docs/apis/AppState.md)
|
||||
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
|
||||
* [`Clipboard`](docs/apis/Clipboard.md)
|
||||
* [`Dimensions`](docs/apis/Dimensions.md)
|
||||
* [`I18nManager`](docs/apis/I18nManager.md)
|
||||
* [`NativeMethods`](docs/apis/NativeMethods.md)
|
||||
* [`NetInfo`](docs/apis/NetInfo.md)
|
||||
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
|
||||
* [`PixelRatio`](docs/apis/PixelRatio.md)
|
||||
* [`Platform`](docs/apis/Platform.md)
|
||||
* [`StyleSheet`](docs/apis/StyleSheet.md)
|
||||
* [`Vibration`](docs/apis/Vibration.md)
|
||||
|
||||
<span id="#why"></span>
|
||||
## Why?
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Example code
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
@@ -85,50 +138,14 @@ AppRegistry.registerComponent('MyApp', () => App)
|
||||
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## Related projects
|
||||
|
||||
Guides:
|
||||
|
||||
* [Accessibility](docs/guides/accessibility.md)
|
||||
* [Client and server rendering](docs/guides/rendering.md)
|
||||
* [Direct manipulation](docs/guides/direct-manipulation.md)
|
||||
* [Known issues](docs/guides/known-issues.md)
|
||||
* [React Native](docs/guides/react-native.md)
|
||||
* [Style](docs/guides/style.md)
|
||||
|
||||
Exported modules:
|
||||
|
||||
* Components
|
||||
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`ScrollView`](docs/components/ScrollView.md)
|
||||
* [`Text`](docs/components/Text.md)
|
||||
* [`TextInput`](docs/components/TextInput.md)
|
||||
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
|
||||
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
|
||||
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
|
||||
* [`View`](docs/components/View.md)
|
||||
* APIs
|
||||
* [`Animated`](http://facebook.github.io/react-native/releases/0.20/docs/animated.html) (mirrors React Native)
|
||||
* [`AppRegistry`](docs/apis/AppRegistry.md)
|
||||
* [`AppState`](docs/apis/AppState.md)
|
||||
* [`AsyncStorage`](docs/apis/AsyncStorage.md)
|
||||
* [`Dimensions`](docs/apis/Dimensions.md)
|
||||
* [`NativeMethods`](docs/apis/NativeMethods.md)
|
||||
* [`NetInfo`](docs/apis/NetInfo.md)
|
||||
* [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native)
|
||||
* [`PixelRatio`](docs/apis/PixelRatio.md)
|
||||
* [`Platform`](docs/apis/Platform.md)
|
||||
* [`StyleSheet`](docs/apis/StyleSheet.md)
|
||||
* [`Vibration`](docs/apis/Vibration.md)
|
||||
* [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-web](https://github.com/taobaofed/react-web)
|
||||
* [react-native-for-web](https://github.com/KodersLab/react-native-for-web)
|
||||
* [rhinos-app](https://github.com/rhinos-app/rhinos-app-dev)
|
||||
|
||||
## License
|
||||
|
||||
React Native for Web is [BSD licensed](LICENSE).
|
||||
|
||||
[npm-image]: https://badge.fury.io/js/react-native-web.svg
|
||||
[npm-url]: https://npmjs.org/package/react-native-web
|
||||
[react-native-url]: https://facebook.github.io/react-native/
|
||||
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/necolas/react-native-web
|
||||
|
||||
@@ -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`, and prerendered by invoking
|
||||
`AppRegistry.prerenderApplication` (see the [client and server rendering
|
||||
`AppRegistry.runApplication` (see the [client and server rendering
|
||||
guide](../guides/rendering.md) for more details).
|
||||
|
||||
To "stop" an application when a view should be destroyed, call
|
||||
@@ -13,14 +12,11 @@ into `runApplication`. These should always be used as a pair.
|
||||
|
||||
## Methods
|
||||
|
||||
(web) static **prerenderApplication**(appKey:string, appParameters: object)
|
||||
(web) static **getApplication**(appKey:string, appParameters: object)
|
||||
|
||||
Renders the given application to an HTML string. Use this for server-side
|
||||
rendering. Return object is of type `{ html: string; style: string;
|
||||
styleElement: ReactComponent }`. `html` is the prerendered HTML, `style` is the
|
||||
prerendered style sheet, and `styleElement` is a React Component. It's
|
||||
recommended that you use `styleElement` to render the style sheet in an app
|
||||
shell.
|
||||
Returns the given application element. Use this for server-side rendering.
|
||||
Return object is of type `{ element: ReactElement; stylesheet: ReactElement }`.
|
||||
It's recommended that you use `sheetsheet` to render the style sheet in an app
|
||||
|
||||
static **registerConfig**(config: Array<AppConfig>)
|
||||
|
||||
|
||||
16
docs/apis/Clipboard.md
Normal file
16
docs/apis/Clipboard.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Clipboard
|
||||
|
||||
Clipboard gives you an interface for setting to the clipboard. (Getting
|
||||
clipboard content is not supported on web.)
|
||||
|
||||
## Methods
|
||||
|
||||
static **getString**()
|
||||
|
||||
Returns a `Promise` of an empty string.
|
||||
|
||||
static **setString**(content: string): boolean
|
||||
|
||||
Copies a string to the clipboard. On web, some browsers may not support copying
|
||||
to the clipboard, therefore, this function returns a boolean to indicate if the
|
||||
copy was successful.
|
||||
25
docs/apis/I18nManager.md
Normal file
25
docs/apis/I18nManager.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# I18nManager
|
||||
|
||||
Control and set the layout and writing direction of the application.
|
||||
|
||||
## Properties
|
||||
|
||||
**isRTL**: bool = false
|
||||
|
||||
Whether the application is currently in RTL mode.
|
||||
|
||||
## Methods
|
||||
|
||||
static **allowRTL**(allowRTL: bool)
|
||||
|
||||
Allow the application to display in RTL mode.
|
||||
|
||||
static **forceRTL**(forceRTL: bool)
|
||||
|
||||
Force the application to display in RTL mode.
|
||||
|
||||
static **setPreferredLanguageRTL**(isRTL: bool)
|
||||
|
||||
Set the application's preferred writing direction to RTL. You will need to
|
||||
determine the user's preferred locale server-side (from HTTP headers) and
|
||||
decide whether it's an RTL language.
|
||||
@@ -29,7 +29,7 @@ static **removeEventListener**(eventName: ChangeEventName, handler: Function)
|
||||
|
||||
## Properties
|
||||
|
||||
**isConnected**
|
||||
**isConnected**: bool = true
|
||||
|
||||
Available on all user agents. Asynchronously fetch a boolean to determine
|
||||
internet connectivity.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
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 to
|
||||
[how style your application](docs/guides/style).
|
||||
outside of the render loop and are applied as inline styles. Read more about
|
||||
[how to style your application](../guides/style.md).
|
||||
|
||||
## Methods
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
|
||||
**animating**: bool = true
|
||||
|
||||
Whether to show the indicator (true, the default) or hide it (false).
|
||||
Whether to show the indicator or hide it.
|
||||
|
||||
**color**: string = #999999
|
||||
**color**: string = '#1976D2'
|
||||
|
||||
The foreground color of the spinner (default is gray).
|
||||
The foreground color of the spinner.
|
||||
|
||||
**hidesWhenStopped**: bool = true
|
||||
|
||||
Whether the indicator should hide when not animating (true by default).
|
||||
Whether the indicator should hide when not animating.
|
||||
|
||||
**size**: oneOf('small, 'large')
|
||||
**size**: oneOf('small, 'large') | number = 'small'
|
||||
|
||||
Size of the indicator. Small has a height of `20`, large has a height of `36`.
|
||||
|
||||
|
||||
39
docs/components/Button.md
Normal file
39
docs/components/Button.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Button
|
||||
|
||||
A basic button component. Supports a minimal level of customization. You can
|
||||
build your own custom button using `TouchableOpacity` or
|
||||
`TouchableNativeFeedback`.
|
||||
|
||||
## Props
|
||||
|
||||
**accessibilityLabel**: string
|
||||
|
||||
Defines the text available to assistive technologies upon interaction with the
|
||||
element. (This is implemented using `aria-label`.)
|
||||
|
||||
**color**: string
|
||||
|
||||
Background color of the button.
|
||||
|
||||
**disabled**: bool = false
|
||||
|
||||
If true, disable all interactions for this component
|
||||
|
||||
**onPress**: function
|
||||
|
||||
This function is called on press.
|
||||
|
||||
**title**: string
|
||||
|
||||
Text to display inside the button.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
<Button
|
||||
accessibilityLabel="Learn more about this purple button"
|
||||
color="#841584"
|
||||
onPress={onPressLearnMore}
|
||||
title="Learn More"
|
||||
/>
|
||||
```
|
||||
@@ -46,7 +46,7 @@ Invoked when load either succeeds or fails,
|
||||
|
||||
Invoked on load start.
|
||||
|
||||
**resizeMode**: oneOf('contain', 'cover', 'none', 'stretch') = 'stretch'
|
||||
**resizeMode**: oneOf('center', 'contain', 'cover', 'none', 'repeat', 'stretch') = 'cover'
|
||||
|
||||
Determines how to resize the image when the frame doesn't match the raw image
|
||||
dimensions.
|
||||
|
||||
23
docs/components/ProgressBar.md
Normal file
23
docs/components/ProgressBar.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# ProgressBar
|
||||
|
||||
Display an activity progress bar.
|
||||
|
||||
## Props
|
||||
|
||||
[...View props](./View.md)
|
||||
|
||||
**color**: string = '#1976D2'
|
||||
|
||||
Color of the progress bar.
|
||||
|
||||
**indeterminate**: bool = true
|
||||
|
||||
Whether the progress bar will show indeterminate progress.
|
||||
|
||||
**progress**: number
|
||||
|
||||
The progress value (between 0 and 1).
|
||||
|
||||
(web) **trackColor**: string = 'transparent'
|
||||
|
||||
Color of the track bar.
|
||||
@@ -38,6 +38,18 @@ which this `ScrollView` renders.
|
||||
Fires at most once per frame during scrolling. The frequency of the events can
|
||||
be contolled using the `scrollEventThrottle` prop.
|
||||
|
||||
Invoked on scroll with the following event:
|
||||
|
||||
```js
|
||||
{
|
||||
nativeEvent: {
|
||||
contentOffset: { x, y },
|
||||
contentSize: { height, width },
|
||||
layoutMeasurement: { height, width }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**refreshControl**: element
|
||||
|
||||
TODO
|
||||
@@ -51,8 +63,8 @@ When false, the content does not scroll.
|
||||
|
||||
**scrollEventThrottle**: number = 0
|
||||
|
||||
This controls how often the scroll event will be fired while scrolling (in
|
||||
events per seconds). A higher number yields better accuracy for code that is
|
||||
This controls how often the scroll event will be fired while scrolling (as a
|
||||
time interval in ms). A lower number yields better accuracy for code that is
|
||||
tracking the scroll position, but can lead to scroll performance problems. The
|
||||
default value is `0`, which means the scroll event will be sent only once each
|
||||
time the view is scrolled.
|
||||
@@ -104,7 +116,7 @@ export default class ScrollViewExample extends Component {
|
||||
contentContainerStyle={styles.container}
|
||||
horizontal
|
||||
onScroll={(e) => this.onScroll(e)}
|
||||
scrollEventThrottle={60}
|
||||
scrollEventThrottle={100}
|
||||
style={styles.root}
|
||||
/>
|
||||
)
|
||||
|
||||
76
docs/components/Switch.md
Normal file
76
docs/components/Switch.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Switch
|
||||
|
||||
This is a controlled component that requires an `onValueChange` callback that
|
||||
updates the value prop in order for the component to reflect user actions. If
|
||||
the `value` prop is not updated, the component will continue to render the
|
||||
supplied `value` prop instead of the expected result of any user actions.
|
||||
|
||||
## Props
|
||||
|
||||
[...View props](./View.md)
|
||||
|
||||
**disabled**: bool = false
|
||||
|
||||
If `true` the user won't be able to interact with the switch.
|
||||
|
||||
**onValueChange**: func
|
||||
|
||||
Invoked with the new value when the value changes.
|
||||
|
||||
**value**: bool = false
|
||||
|
||||
The value of the switch. If `true` the switch will be turned on.
|
||||
|
||||
(web) **activeThumbColor**: color = #009688
|
||||
|
||||
The color of the thumb grip when the switch is turned on.
|
||||
|
||||
(web) **activeTrackColor**: color = #A3D3CF
|
||||
|
||||
The color of the track when the switch is turned on.
|
||||
|
||||
(web) **thumbColor**: color = #FAFAFA
|
||||
|
||||
The color of the thumb grip when the switch is turned off.
|
||||
|
||||
(web) **trackColor**: color = #939393
|
||||
|
||||
The color of the track when the switch is turned off.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Component } from 'react'
|
||||
import { Switch, View } from 'react-native'
|
||||
|
||||
class ColorSwitchExample extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
colorTrueSwitchIsOn: true,
|
||||
colorFalseSwitchIsOn: false
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
activeThumbColor='#428BCA'
|
||||
activeTrackColor='#A0C4E3'
|
||||
onValueChange={(value) => this.setState({ colorFalseSwitchIsOn: value })}
|
||||
value={this.state.colorFalseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
activeThumbColor='#5CB85C'
|
||||
activeTrackColor='#ADDAAD'
|
||||
onValueChange={(value) => this.setState({ colorTrueSwitchIsOn: value })}
|
||||
thumbColor='#EBA9A7'
|
||||
trackColor='#D9534F'
|
||||
value={this.state.colorTrueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -68,14 +68,22 @@ Lets the user select the text.
|
||||
+ `fontWeight`
|
||||
+ `letterSpacing`
|
||||
+ `lineHeight`
|
||||
+ `textAlign`
|
||||
+ `textAlign`‡
|
||||
+ `textAlignVertical`
|
||||
+ `textDecorationLine`
|
||||
+ `textShadow`
|
||||
+ `textOverflow`
|
||||
+ `textRendering`
|
||||
+ `textShadowColor`
|
||||
+ `textShadowOffset`‡
|
||||
+ `textShadowRadius`
|
||||
+ `textTransform`
|
||||
+ `unicodeBidi`
|
||||
+ `whiteSpace`
|
||||
+ `wordWrap`
|
||||
+ `writingDirection`
|
||||
+ `writingDirection`‡
|
||||
|
||||
‡ This property can be suffixed with `$noI18n` to prevent automatic
|
||||
bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`.
|
||||
|
||||
**testID**: string
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ such as auto-complete, auto-focus, placeholder text, and event callbacks.
|
||||
Note: some props are exclusive to or excluded from `multiline`.
|
||||
|
||||
Unsupported React Native props:
|
||||
`autoCapitalize`,
|
||||
`autoCorrect`,
|
||||
`onEndEditing`,
|
||||
`onSubmitEditing`,
|
||||
`clearButtonMode` (ios),
|
||||
`enablesReturnKeyAutomatically` (ios),
|
||||
`placeholderTextColor`,
|
||||
`returnKeyType` (ios),
|
||||
`selectionState` (ios),
|
||||
`underlineColorAndroid` (android)
|
||||
@@ -20,15 +18,37 @@ Unsupported React Native props:
|
||||
|
||||
[...View props](./View.md)
|
||||
|
||||
(web) **autoComplete**: bool = false
|
||||
**autoCapitalize**: oneOf('characters', 'none', 'sentences', 'words') = 'sentences'
|
||||
|
||||
Indicates whether the value of the control can be automatically completed by the browser.
|
||||
Automatically capitalize certain characters (only available in Chrome and iOS Safari).
|
||||
|
||||
* `characters`: Automatically capitalize all characters.
|
||||
* `none`: Completely disables automatic capitalization
|
||||
* `sentences`: Automatically capitalize the first letter of sentences.
|
||||
* `words`: Automatically capitalize the first letter of words.
|
||||
|
||||
(web) **autoComplete**: string
|
||||
|
||||
Indicates whether the value of the control can be automatically completed by
|
||||
the browser. [Accepted values](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
|
||||
|
||||
**autoCorrect**: bool = true
|
||||
|
||||
Automatically correct spelling mistakes (only available in iOS Safari).
|
||||
|
||||
**autoFocus**: bool = false
|
||||
|
||||
If true, focuses the input on `componentDidMount`. Only the first form element
|
||||
If `true`, focuses the input on `componentDidMount`. Only the first form element
|
||||
in a document with `autofocus` is focused.
|
||||
|
||||
**blurOnSubmit**: bool
|
||||
|
||||
If `true`, the text field will blur when submitted. The default value is `true`
|
||||
for single-line fields and `false` for multiline fields. Note, for multiline
|
||||
fields setting `blurOnSubmit` to `true` means that pressing return will blur
|
||||
the field and trigger the `onSubmitEditing` event instead of inserting a
|
||||
newline into the field.
|
||||
|
||||
**clearTextOnFocus**: bool = false
|
||||
|
||||
If `true`, clears the text field automatically when focused.
|
||||
@@ -87,29 +107,25 @@ as an argument to the callback handler.
|
||||
|
||||
Callback that is called when the text input is focused.
|
||||
|
||||
(web) **onSelectionChange**: function
|
||||
**onKeyPress**: function
|
||||
|
||||
Callback that is called when the text input's selection changes. The following
|
||||
object is passed as an argument to the callback handler.
|
||||
Callback that is called when a key is pressed. Pressed key value is passed as
|
||||
an argument to the callback handler. Fires before `onChange` callbacks.
|
||||
|
||||
```js
|
||||
{
|
||||
selectionDirection,
|
||||
selectionEnd,
|
||||
selectionStart,
|
||||
nativeEvent
|
||||
}
|
||||
```
|
||||
**onSelectionChange**: function
|
||||
|
||||
Callback that is called when the text input's selection changes. This will be called with
|
||||
`{ nativeEvent: { selection: { start, end } } }`.
|
||||
|
||||
**onSubmitEditing**: function
|
||||
|
||||
Callback that is called when the keyboard's submit button is pressed.
|
||||
|
||||
**placeholder**: string
|
||||
|
||||
The string that will be rendered in an empty `TextInput` before text has been
|
||||
entered.
|
||||
|
||||
**placeholderTextColor**: string
|
||||
|
||||
The text color of the placeholder string.
|
||||
|
||||
**secureTextEntry**: bool = false
|
||||
|
||||
If true, the text input obscures the text entered so that sensitive text like
|
||||
@@ -117,6 +133,10 @@ passwords stay secure.
|
||||
|
||||
(Not available when `multiline` is `true`.)
|
||||
|
||||
**selection**: { start: number, end: ?number }
|
||||
|
||||
The start and end of the text input's selection. Set start and end to the same value to position the cursor.
|
||||
|
||||
**selectTextOnFocus**: bool = false
|
||||
|
||||
If `true`, all text will automatically be selected on focus.
|
||||
@@ -152,6 +172,10 @@ Clear the text from the underlying DOM input.
|
||||
|
||||
Focus the underlying DOM input.
|
||||
|
||||
**isFocused()**
|
||||
|
||||
Returns `true` if the input is currently focused; `false` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
|
||||
@@ -108,10 +108,26 @@ from `style`.
|
||||
+ `backgroundPosition`
|
||||
+ `backgroundRepeat`
|
||||
+ `backgroundSize`
|
||||
+ `borderColor`
|
||||
+ `borderRadius`
|
||||
+ `borderStyle`
|
||||
+ `borderWidth`
|
||||
+ `borderColor` (single value)
|
||||
+ `borderTopColor`
|
||||
+ `borderBottomColor`
|
||||
+ `borderRightColor`‡
|
||||
+ `borderLeftColor`‡
|
||||
+ `borderRadius` (single value)
|
||||
+ `borderTopLeftRadius`‡
|
||||
+ `borderTopRightRadius`‡
|
||||
+ `borderBottomLeftRadius`‡
|
||||
+ `borderBottomRightRadius`‡
|
||||
+ `borderStyle` (single value)
|
||||
+ `borderTopStyle`
|
||||
+ `borderRightStyle`‡
|
||||
+ `borderBottomStyle`
|
||||
+ `borderLeftStyle`‡
|
||||
+ `borderWidth` (single value)
|
||||
+ `borderBottomWidth`
|
||||
+ `borderLeftWidth`‡
|
||||
+ `borderRightWidth`‡
|
||||
+ `borderTopWidth`
|
||||
+ `bottom`
|
||||
+ `boxShadow`
|
||||
+ `boxSizing`
|
||||
@@ -124,12 +140,12 @@ from `style`.
|
||||
+ `flexWrap`
|
||||
+ `height`
|
||||
+ `justifyContent`
|
||||
+ `left`
|
||||
+ `left`‡
|
||||
+ `margin` (single value)
|
||||
+ `marginBottom`
|
||||
+ `marginHorizontal`
|
||||
+ `marginLeft`
|
||||
+ `marginRight`
|
||||
+ `marginLeft`‡
|
||||
+ `marginRight`‡
|
||||
+ `marginTop`
|
||||
+ `marginVertical`
|
||||
+ `maxHeight`
|
||||
@@ -144,20 +160,22 @@ from `style`.
|
||||
+ `padding` (single value)
|
||||
+ `paddingBottom`
|
||||
+ `paddingHorizontal`
|
||||
+ `paddingLeft`
|
||||
+ `paddingRight`
|
||||
+ `paddingLeft`‡
|
||||
+ `paddingRight`‡
|
||||
+ `paddingTop`
|
||||
+ `paddingVertical`
|
||||
+ `position`
|
||||
+ `right`
|
||||
+ `right`‡
|
||||
+ `top`
|
||||
+ `transform`
|
||||
+ `transformMatrix`
|
||||
+ `userSelect`
|
||||
+ `visibility`
|
||||
+ `width`
|
||||
+ `zIndex`
|
||||
|
||||
‡ This property can be suffixed with `$noI18n` to prevent automatic
|
||||
bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`.
|
||||
|
||||
Default:
|
||||
|
||||
```js
|
||||
|
||||
34
docs/guides/internationalization.md
Normal file
34
docs/guides/internationalization.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Internationalization
|
||||
|
||||
To support right-to-left languages, application layout can be automatically
|
||||
flipped from LTR to RTL. The `I18nManager` API can be used to help with more
|
||||
fine-grained control and testing of RTL layouts.
|
||||
|
||||
React Native for Web provides an experimental feature to support "true left"
|
||||
and "true right" styles. For example, `left` will be flipped to `right` in RTL
|
||||
mode, but `left$noI18n` will not. More information is available in the `Text`
|
||||
and `View` documentation.
|
||||
|
||||
## Working with icons and images
|
||||
|
||||
Icons and images that must match the LTR or RTL layout of the app need to be manually flipped.
|
||||
|
||||
Either use a transform style:
|
||||
|
||||
```js
|
||||
<Image
|
||||
source={...}
|
||||
style={{ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] }}
|
||||
/>
|
||||
```
|
||||
|
||||
Or replace the source asset:
|
||||
|
||||
```js
|
||||
import imageSourceLTR from './back.png';
|
||||
import imageSourceRTL from './forward.png';
|
||||
|
||||
<Image
|
||||
source={I18nManager.isRTL ? imageSourceRTL : imageSourceLTR}
|
||||
/>
|
||||
```
|
||||
@@ -16,6 +16,22 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
|
||||
The `react-native-web` package also includes a `core` module that exports only
|
||||
`ReactNative`, `Image`, `StyleSheet`, `Text`, `TextInput`, and `View`.
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
|
||||
module.exports = {
|
||||
// ...other configuration
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': 'react-native-web/core'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Client-side rendering
|
||||
|
||||
Rendering without using the `AppRegistry`:
|
||||
@@ -27,12 +43,7 @@ import ReactNative from 'react-native'
|
||||
// component that renders the app
|
||||
const AppHeaderContainer = (props) => { /* ... */ }
|
||||
|
||||
// DOM render
|
||||
ReactNative.render(<AppHeaderContainer />, document.getElementById('react-app-header'))
|
||||
|
||||
// Server render
|
||||
ReactNative.renderToString(<AppHeaderContainer />)
|
||||
ReactNative.renderToStaticMarkup(<AppHeaderContainer />)
|
||||
```
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
@@ -47,12 +58,27 @@ const AppContainer = (props) => { /* ... */ }
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => AppContainer)
|
||||
|
||||
// DOM render
|
||||
AppRegistry.runApplication('App', {
|
||||
initialProps: {},
|
||||
rootTag: document.getElementById('react-app')
|
||||
})
|
||||
```
|
||||
|
||||
## Server-side rendering
|
||||
|
||||
Rendering using the `AppRegistry`:
|
||||
|
||||
```js
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNative, { AppRegistry } from 'react-native'
|
||||
|
||||
// component that renders the app
|
||||
const AppContainer = (props) => { /* ... */ }
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => AppContainer)
|
||||
|
||||
// prerender the app
|
||||
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
|
||||
const { element, stylesheet } = AppRegistry.getApplication('App', { initialProps });
|
||||
const initialHTML = ReactDOMServer.renderToString(element);
|
||||
```
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Style
|
||||
|
||||
React Native for Web relies on JavaScript to define styles for your
|
||||
application. Along with a novel JS-to-CSS conversion strategy, this allows you
|
||||
to avoid issues arising from the [7 deadly sins of
|
||||
application. This allows you to avoid issues arising from the [7 deadly sins of
|
||||
CSS](https://speakerdeck.com/vjeux/react-css-in-js):
|
||||
|
||||
1. Global namespace
|
||||
@@ -33,9 +32,9 @@ const styles = StyleSheet.create({
|
||||
```
|
||||
|
||||
Using `StyleSheet.create` is optional but provides some key advantages: styles
|
||||
are immutable in development, styles are converted to CSS rather than applied
|
||||
as inline styles, and styles are only created once for the application and not
|
||||
on every render.
|
||||
are immutable in development, certain declarations are automatically converted
|
||||
to CSS rather than applied as inline styles, and styles are only created once
|
||||
for the application and not on every render.
|
||||
|
||||
The attribute names and values are a subset of CSS. See the `style`
|
||||
documentation of individual components.
|
||||
@@ -140,51 +139,8 @@ benefit of co-locating breakpoint-specific DOM and style changes.
|
||||
|
||||
## Pseudo-classes and pseudo-elements
|
||||
|
||||
Pseudo-classes like `:hover` and `:focus` can be implemented with the events
|
||||
(e.g. `onFocus`). Pseudo-elements are not supported; elements should be used
|
||||
instead.
|
||||
|
||||
## How it works
|
||||
|
||||
Every call to `StyleSheet.create` extracts the unique _declarations_ and
|
||||
converts them to a unique CSS rule. This is sometimes referred to as "atomic
|
||||
CSS". All the core components map their `style` property-value pairs to the
|
||||
corresponding `className`'s.
|
||||
|
||||
By doing this, the total size of the generated CSS is determined by the
|
||||
total number of unique declarations (rather than the total number of rules in
|
||||
the application), making it viable to inline the style sheet when pre-rendering
|
||||
on the server. Styles are updated if new module bundle are loaded asynchronously.
|
||||
|
||||
JavaScript definition:
|
||||
|
||||
```js
|
||||
const styles = StyleSheet.create({
|
||||
heading: {
|
||||
color: 'gray',
|
||||
fontSize: '2rem'
|
||||
},
|
||||
text: {
|
||||
color: 'gray',
|
||||
fontSize: '1.25rem'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
CSS output:
|
||||
|
||||
```css
|
||||
.__style1 { color: gray; }
|
||||
.__style2 { font-size: 2rem; }
|
||||
.__style3 { font-size: 1.25rem; }
|
||||
```
|
||||
|
||||
Rendered HTML:
|
||||
|
||||
```html
|
||||
<span className="__style1 __style2">Heading</span>
|
||||
<span className="__style1 __style3">Text</span>
|
||||
```
|
||||
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
|
||||
|
||||
@@ -194,27 +150,3 @@ You **do not** need to include a CSS reset or
|
||||
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`).
|
||||
|
||||
```css
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color:rgba(0,0,0,0)
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
12
examples/.storybook/config.js
Normal file
12
examples/.storybook/config.js
Normal 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)
|
||||
18
examples/.storybook/decorator-centered.js
Normal file
18
examples/.storybook/decorator-centered.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
export default function (renderStory) {
|
||||
return (
|
||||
<View style={[ StyleSheet.absoluteFill, styles.root ]}>
|
||||
{renderStory()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,7 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const EXAMPLES_DIRECTORY = __dirname
|
||||
|
||||
module.exports = {
|
||||
devServer: {
|
||||
contentBase: EXAMPLES_DIRECTORY
|
||||
},
|
||||
entry: {
|
||||
example: EXAMPLES_DIRECTORY
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
@@ -17,27 +9,28 @@ module.exports = {
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: { cacheDirectory: true }
|
||||
},
|
||||
{
|
||||
test: /\.(gif|jpe?g|png|svg)$/,
|
||||
loader: 'url-loader',
|
||||
query: { name: '[name].[ext]' }
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
}),
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
// https://github.com/animatedjs/animated/issues/40
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/es6-set/,
|
||||
path.join(__dirname, '../src/modules/polyfills/Set.js')
|
||||
path.join(__dirname, '../../src/modules/polyfills/Set.js')
|
||||
),
|
||||
new webpack.optimize.OccurenceOrderPlugin()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-native': path.join(__dirname, '../src')
|
||||
'react-native': path.join(__dirname, '../../src')
|
||||
}
|
||||
}
|
||||
}
|
||||
33
examples/apis/Clipboard/ClipboardExample.js
Normal file
33
examples/apis/Clipboard/ClipboardExample.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Clipboard, Text, TextInput, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
|
||||
class ClipboardExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={{ minWidth: 300 }}>
|
||||
<Text onPress={this._handleSet}>Copy to clipboard</Text>
|
||||
<TextInput
|
||||
multiline={true}
|
||||
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>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_handleGet() {
|
||||
Clipboard.getString().then((value) => { console.log(`Clipboard value: ${value}`) });
|
||||
}
|
||||
|
||||
_handleSet() {
|
||||
const success = Clipboard.setString('This text was copied to the clipboard by React Native');
|
||||
console.log(`Clipboard.setString success? ${success}`);
|
||||
}
|
||||
}
|
||||
|
||||
storiesOf('api: Clipboard', module)
|
||||
.add('setString', () => (
|
||||
<ClipboardExample />
|
||||
));
|
||||
79
examples/apis/I18nManager/I18nManagerExample.js
Normal file
79
examples/apis/I18nManager/I18nManagerExample.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { storiesOf } from '@kadira/storybook';
|
||||
import { I18nManager, StyleSheet, TouchableHighlight, Text, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
|
||||
class I18nManagerExample extends Component {
|
||||
componentWillUnmount() {
|
||||
I18nManager.setPreferredLanguageRTL(false)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text accessibilityRole='heading' style={styles.welcome}>
|
||||
LTR/RTL layout example!
|
||||
</Text>
|
||||
<Text style={styles.text}>
|
||||
This is sample text. The writing direction can be changed by pressing the button below.
|
||||
</Text>
|
||||
<Text style={[ styles.text, styles.ltrText ]}>
|
||||
This is text that will always display LTR.
|
||||
</Text>
|
||||
<Text style={[ styles.text, styles.rtlText ]}>
|
||||
This is text that will always display RTL.
|
||||
</Text>
|
||||
<TouchableHighlight
|
||||
onPress={this._handleToggle}
|
||||
style={styles.toggle}
|
||||
underlayColor='rgba(0,0,0,0.25)'
|
||||
>
|
||||
<Text>Toggle LTR/RTL</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_handleToggle = () => {
|
||||
this._isRTL = !this._isRTL
|
||||
I18nManager.setPreferredLanguageRTL(this._isRTL)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#F5FCFF',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
padding: 10
|
||||
},
|
||||
welcome: {
|
||||
fontSize: 28,
|
||||
marginVertical: 10
|
||||
},
|
||||
text: {
|
||||
color: '#333333',
|
||||
fontSize: 18,
|
||||
marginBottom: 5
|
||||
},
|
||||
ltrText: {
|
||||
textAlign$noI18n: 'left',
|
||||
writingDirection$noI18n: 'ltr'
|
||||
},
|
||||
rtlText: {
|
||||
textAlign$noI18n: 'right',
|
||||
writingDirection$noI18n: 'rtl'
|
||||
},
|
||||
toggle: {
|
||||
alignSelf: 'center',
|
||||
borderColor: 'black',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
marginTop: 10,
|
||||
padding: 10
|
||||
}
|
||||
})
|
||||
|
||||
storiesOf('api: I18nManager', module)
|
||||
.add('RTL layout', () => (
|
||||
<I18nManagerExample />
|
||||
))
|
||||
29
examples/apis/Linking/LinkingExample.js
Normal file
29
examples/apis/Linking/LinkingExample.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Linking, StyleSheet, Text, View } from 'react-native'
|
||||
import React, { Component } from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
class LinkingExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text onPress={() => { Linking.openURL('https://mathiasbynens.github.io/rel-noopener/malicious.html'); }} 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'>
|
||||
target="_blank" (Expect: "The previous tab is safe and intact")
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
marginVertical: 10
|
||||
}
|
||||
});
|
||||
|
||||
storiesOf('api: Linking', module)
|
||||
.add('Safe linking', () => (
|
||||
<LinkingExample />
|
||||
));
|
||||
117
examples/apis/PanResponder/PanResponderExample.js
Normal file
117
examples/apis/PanResponder/PanResponderExample.js
Normal file
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
var React = require('react');
|
||||
var ReactNative = require('react-native');
|
||||
var {
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
View
|
||||
} = ReactNative;
|
||||
|
||||
var CIRCLE_SIZE = 80;
|
||||
|
||||
var PanResponderExample = React.createClass({
|
||||
_panResponder: {},
|
||||
_previousLeft: 0,
|
||||
_previousTop: 0,
|
||||
_circleStyles: {},
|
||||
circle: (null : ?{ setNativeProps(props: Object): void }),
|
||||
|
||||
componentWillMount: function() {
|
||||
this._panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
|
||||
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderRelease: this._handlePanResponderEnd,
|
||||
onPanResponderTerminate: this._handlePanResponderEnd,
|
||||
});
|
||||
this._previousLeft = 20;
|
||||
this._previousTop = 84;
|
||||
this._circleStyles = {
|
||||
style: {
|
||||
left: this._previousLeft,
|
||||
top: this._previousTop,
|
||||
backgroundColor: 'green',
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View
|
||||
style={styles.container}>
|
||||
<View
|
||||
ref={(circle) => {
|
||||
this.circle = circle;
|
||||
}}
|
||||
style={styles.circle}
|
||||
{...this._panResponder.panHandlers}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_highlight: function() {
|
||||
this._circleStyles.style.backgroundColor = 'blue';
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
|
||||
_unHighlight: function() {
|
||||
this._circleStyles.style.backgroundColor = 'green';
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
|
||||
_updateNativeStyles: function() {
|
||||
this.circle && this.circle.setNativeProps(this._circleStyles);
|
||||
},
|
||||
|
||||
_handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
|
||||
// Should we become active when the user presses down on the circle?
|
||||
return true;
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
|
||||
// Should we become active when the user moves a touch over the circle?
|
||||
return false;
|
||||
},
|
||||
|
||||
_handlePanResponderGrant: function(e: Object, gestureState: Object) {
|
||||
this._highlight();
|
||||
},
|
||||
_handlePanResponderMove: function(e: Object, gestureState: Object) {
|
||||
this._circleStyles.style.left = this._previousLeft + gestureState.dx;
|
||||
this._circleStyles.style.top = this._previousTop + gestureState.dy;
|
||||
this._updateNativeStyles();
|
||||
},
|
||||
_handlePanResponderEnd: function(e: Object, gestureState: Object) {
|
||||
this._unHighlight();
|
||||
this._previousLeft += gestureState.dx;
|
||||
this._previousTop += gestureState.dy;
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
circle: {
|
||||
width: CIRCLE_SIZE,
|
||||
height: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 64,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
storiesOf('api: PanResponder', module)
|
||||
.add('example', () => <PanResponderExample />)
|
||||
@@ -0,0 +1,162 @@
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
const ToggleAnimatingActivityIndicator = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
animating: true,
|
||||
};
|
||||
},
|
||||
|
||||
setToggleTimeout() {
|
||||
this.setTimeout(() => {
|
||||
this.setState({animating: !this.state.animating});
|
||||
this.setToggleTimeout();
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.setToggleTimeout();
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
animating={this.state.animating}
|
||||
style={[styles.centering, {height: 80}]}
|
||||
size="large"
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Default',
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
style={[styles.centering]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom colors',
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<ActivityIndicator color="#0000ff" />
|
||||
<ActivityIndicator color="#aa00aa" />
|
||||
<ActivityIndicator color="#aa3300" />
|
||||
<ActivityIndicator color="#00aa00" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Large',
|
||||
render() {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
style={[styles.centering, styles.gray]}
|
||||
color="white"
|
||||
size="large"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Large, custom colors',
|
||||
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"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Start/stop',
|
||||
render() {
|
||||
return <ToggleAnimatingActivityIndicator />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom size',
|
||||
render() {
|
||||
return (
|
||||
<View style={[styles.horizontal, styles.centering]}>
|
||||
<ActivityIndicator size="40" />
|
||||
<ActivityIndicator
|
||||
style={{ marginLeft: 20, transform: [ {scale: 1.5} ] }}
|
||||
size="large"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centering: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 8,
|
||||
},
|
||||
gray: {
|
||||
backgroundColor: '#cccccc',
|
||||
},
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
padding: 8,
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: ActivityIndicator', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
@@ -1,271 +0,0 @@
|
||||
import GridView from './GridView'
|
||||
import Heading from './Heading'
|
||||
import React from 'react'
|
||||
import { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
|
||||
|
||||
export default class App extends React.Component {
|
||||
static propTypes = {
|
||||
style: View.propTypes.style
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
scrollEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const finalRootStyles = [
|
||||
rootStyles.common
|
||||
]
|
||||
|
||||
return (
|
||||
<ScrollView accessibilityRole='main'>
|
||||
<View style={finalRootStyles}>
|
||||
<Heading size='xlarge'>React Native for Web</Heading>
|
||||
<Text>React Native Web takes the core components from <Text
|
||||
accessibilityRole='link' href='https://facebook.github.io/react-native/'>React
|
||||
Native</Text> and brings them to the web. These components provide
|
||||
simple building blocks – touch handling, flexbox layout,
|
||||
scroll views – from which more complex components and apps can be
|
||||
constructed.</Text>
|
||||
|
||||
<Heading size='large'>Image</Heading>
|
||||
<Image
|
||||
onLayout={(e) => { console.log(e.nativeEvent.layout) }}
|
||||
accessibilityLabel='accessible image'
|
||||
children={<Text>Inner content</Text>}
|
||||
defaultSource={{
|
||||
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wkGESkdPWMDggAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAD5UlEQVR42u3UMQ0AAAgEMcC/x7eCCgaSVsIN10kK4IORADAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAkAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAALi04UQW9HF910gAAAABJRU5ErkJggg=='
|
||||
}}
|
||||
onError={(e) => { console.log('Image.onError', e) }}
|
||||
onLoad={(e) => { console.log('Image.onLoad', e) }}
|
||||
onLoadEnd={() => { console.log('Image.onLoadEnd') }}
|
||||
onLoadStart={() => { console.log('Image.onLoadStart') }}
|
||||
resizeMode={'contain'}
|
||||
source={{
|
||||
height: 400,
|
||||
uri: 'http://facebook.github.io/react/img/logo_og.png',
|
||||
width: 400
|
||||
}}
|
||||
style={{
|
||||
borderWidth: '5px'
|
||||
}}
|
||||
testID='Example.image'
|
||||
/>
|
||||
|
||||
<Heading size='large'>Text</Heading>
|
||||
<Text
|
||||
onPress={(e) => { console.log('Text.onPress', e) }}
|
||||
onLayout={(e) => { console.log(e.nativeEvent.layout) }}
|
||||
testID={'Example.text'}
|
||||
>
|
||||
PRESS ME.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
|
||||
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
|
||||
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
|
||||
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
|
||||
hendrerit consequat. Aliquam lorem quam, elementum eget ex nec,
|
||||
ultrices porttitor nibh. Nulla pellentesque urna leo, a aliquet elit
|
||||
rhoncus a. Aenean ultricies, nunc a interdum dictum, dui odio
|
||||
scelerisque mauris, a fringilla elit ligula vel sem. Sed vel aliquet
|
||||
ipsum, sed rhoncus velit. Vivamus commodo pretium libero id placerat.
|
||||
</Text>
|
||||
<Text numberOfLines={1}>
|
||||
TRUNCATED after 1 line.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
|
||||
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
|
||||
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
|
||||
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
|
||||
hendrerit consequat.
|
||||
</Text>
|
||||
|
||||
<Heading size='large'>TextInput</Heading>
|
||||
<TextInput
|
||||
defaultValue='Default textInput'
|
||||
keyboardType='default'
|
||||
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
|
||||
onChange={(e) => { console.log('TextInput.onChange', e) }}
|
||||
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
|
||||
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
|
||||
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
|
||||
/>
|
||||
<TextInput secureTextEntry style={styles.textInput} />
|
||||
<TextInput defaultValue='read only' editable={false} style={styles.textInput} />
|
||||
<TextInput
|
||||
style={[ styles.textInput, { flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' } ]}
|
||||
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
|
||||
/>
|
||||
<TextInput keyboardType='numeric' style={styles.textInput} />
|
||||
<TextInput keyboardType='phone-pad' style={styles.textInput} />
|
||||
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus style={styles.textInput} />
|
||||
<TextInput
|
||||
defaultValue='default value'
|
||||
maxNumberOfLines={10}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
style={styles.textInput}
|
||||
/>
|
||||
|
||||
<Heading size='large'>Touchable</Heading>
|
||||
<TouchableHighlight
|
||||
accessibilityLabel={'Touchable element'}
|
||||
activeHighlight='lightblue'
|
||||
activeOpacity={0.8}
|
||||
onLongPress={(e) => { console.log('Touchable.onLongPress', e) }}
|
||||
onPress={(e) => { console.log('Touchable.onPress', e) }}
|
||||
onPressIn={(e) => { console.log('Touchable.onPressIn', e) }}
|
||||
onPressOut={(e) => { console.log('Touchable.onPressOut', e) }}
|
||||
>
|
||||
<View style={styles.touchableArea}>
|
||||
<Text>Touchable area (press, long press)</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
|
||||
<Heading size='large'>View</Heading>
|
||||
<Heading>Default layout</Heading>
|
||||
<View>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{item}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading>Row layout</Heading>
|
||||
<View style={styles.row}>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{item}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading>pointerEvents</Heading>
|
||||
<GridView alley='10px'>
|
||||
{['box-none', 'box-only', 'none'].map((value, i) => {
|
||||
return (
|
||||
<View
|
||||
accessibilityRole='link'
|
||||
children={value}
|
||||
href='https://google.com'
|
||||
key={i}
|
||||
pointerEvents={value}
|
||||
style={styles.pointerEventsBox}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</GridView>
|
||||
|
||||
<Heading size='large'>ScrollView</Heading>
|
||||
<label>
|
||||
<input
|
||||
checked={this.state.scrollEnabled}
|
||||
onChange={() => this.setState({
|
||||
scrollEnabled: !this.state.scrollEnabled
|
||||
})}
|
||||
type='checkbox'
|
||||
/> Enable scroll
|
||||
</label>
|
||||
|
||||
<Heading>Default layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={styles.box}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
<Heading>Horizontal layout</Heading>
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={e => console.log('ScrollView.onScroll', e)}
|
||||
scrollEnabled={this.state.scrollEnabled}
|
||||
scrollEventThrottle={1} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const rootStyles = StyleSheet.create({
|
||||
common: {
|
||||
marginVertical: 0,
|
||||
marginHorizontal: 'auto'
|
||||
},
|
||||
mqSmall: {
|
||||
maxWidth: '400px'
|
||||
},
|
||||
mqLarge: {
|
||||
maxWidth: '600px'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
},
|
||||
textInput: {
|
||||
borderWidth: 1
|
||||
},
|
||||
box: {
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
horizontalBox: {
|
||||
width: '50px'
|
||||
},
|
||||
boxFull: {
|
||||
width: '100%'
|
||||
},
|
||||
pointerEventsBox: {
|
||||
alignItems: 'center',
|
||||
borderWidth: '1px',
|
||||
flexGrow: 1,
|
||||
height: '100px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
touchableArea: {
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
height: '200px',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px'
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
padding: '10px'
|
||||
}
|
||||
})
|
||||
80
examples/components/Button/ButtonExample.js
Normal file
80
examples/components/Button/ButtonExample.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
import { Button, StyleSheet, View } from 'react-native';
|
||||
|
||||
const onButtonPress = action('Button has been pressed!');
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Simple Button',
|
||||
description: 'The title and onPress handler are required. It is ' +
|
||||
'recommended to set accessibilityLabel to help make your app usable by ' +
|
||||
'everyone.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Press Me"
|
||||
accessibilityLabel="See an informative alert"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Adjusted color',
|
||||
description: 'Adjusts the color in a way that looks standard on each ' +
|
||||
'platform. On iOS, the color prop controls the color of the text. On ' +
|
||||
'Android, the color adjusts the background color of the button.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
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',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="This looks great!"
|
||||
accessibilityLabel="This sounds great!"
|
||||
/>
|
||||
<Button
|
||||
onPress={onButtonPress}
|
||||
title="Ok!"
|
||||
color="#841584"
|
||||
accessibilityLabel="Ok, Great!"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Disabled Button',
|
||||
description: 'All interactions for the component are disabled.',
|
||||
render: function() {
|
||||
return (
|
||||
<Button
|
||||
disabled
|
||||
onPress={onButtonPress}
|
||||
title="I Am Disabled"
|
||||
accessibilityLabel="See an informative alert"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: Button', module)
|
||||
.add(example.title, () => example.render());
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
||||
export default class GridView extends Component {
|
||||
static propTypes = {
|
||||
alley: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.element,
|
||||
PropTypes.arrayOf(PropTypes.element)
|
||||
]),
|
||||
gutter: PropTypes.string,
|
||||
style: PropTypes.object
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
alley: '0px',
|
||||
gutter: '0px'
|
||||
}
|
||||
|
||||
render() {
|
||||
const { alley, children, gutter, style, ...other } = this.props
|
||||
|
||||
const rootStyle = [
|
||||
style,
|
||||
styles.root
|
||||
]
|
||||
|
||||
const contentContainerStyle = [
|
||||
styles.contentContainer,
|
||||
{ marginHorizontal: `calc(-0.5 * ${alley})` },
|
||||
{ paddingHorizontal: `${gutter}` }
|
||||
]
|
||||
|
||||
const newChildren = React.Children.map(children, (child) => {
|
||||
return child && React.cloneElement(child, {
|
||||
style: [
|
||||
child.props.style,
|
||||
styles.column,
|
||||
{ marginHorizontal: `calc(0.5 * ${alley})` }
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<View className='GridView' {...other} style={rootStyle}>
|
||||
<View style={contentContainerStyle}>
|
||||
{newChildren}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
contentContainer: {
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1
|
||||
},
|
||||
// distribute all space (rather than extra space)
|
||||
column: {
|
||||
flexBasis: '0%'
|
||||
}
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
|
||||
const Heading = ({ children, size = 'normal' }) => (
|
||||
<Text
|
||||
accessibilityRole='heading'
|
||||
children={children}
|
||||
style={[ styles.root, sizeStyles[size] ]}
|
||||
/>
|
||||
)
|
||||
|
||||
const sizeStyles = StyleSheet.create({
|
||||
xlarge: {
|
||||
fontSize: '2rem',
|
||||
marginBottom: '1em'
|
||||
},
|
||||
large: {
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: '1em',
|
||||
marginTop: '1em'
|
||||
},
|
||||
normal: {
|
||||
fontSize: '1.25rem',
|
||||
marginBottom: '0.5em',
|
||||
marginTop: '0.5em'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
fontFamily: '"Helvetica Neue", arial, sans-serif'
|
||||
}
|
||||
})
|
||||
|
||||
export default Heading
|
||||
657
examples/components/Image/ImageExample.js
Normal file
657
examples/components/Image/ImageExample.js
Normal file
@@ -0,0 +1,657 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action, addDecorator } from '@kadira/storybook';
|
||||
import { ActivityIndicator, Image, Platform, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
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==';
|
||||
|
||||
//var ImageCapInsetsExample = require('./ImageCapInsetsExample');
|
||||
//const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now();
|
||||
//var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
|
||||
|
||||
/*
|
||||
var NetworkImageCallbackExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
events: [],
|
||||
startLoadPrefetched: false,
|
||||
mountTime: new Date(),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({mountTime: new Date()});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var { 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.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)`)}
|
||||
/>
|
||||
: null}
|
||||
<Text style={{marginTop: 20}}>
|
||||
{this.state.events.join('\n')}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_loadEventFired(event) {
|
||||
this.setState((state) => {
|
||||
return state.events = [...state.events, event];
|
||||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
var NetworkImageExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
error: false,
|
||||
loading: false,
|
||||
progress: 0
|
||||
};
|
||||
},
|
||||
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>;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
var ImageSizeExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
Image.getSize(this.props.source.uri, (width, height) => {
|
||||
this.setState({width, height});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Image
|
||||
style={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
backgroundColor: 'transparent',
|
||||
marginRight: 10,
|
||||
}}
|
||||
source={this.props.source} />
|
||||
<Text>
|
||||
Actual dimensions:{'\n'}
|
||||
Width: {this.state.width}, Height: {this.state.height}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
*/
|
||||
/*
|
||||
var MultipleSourcesExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
width: 30,
|
||||
height: 30,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
<Text
|
||||
style={styles.touchableText}
|
||||
onPress={this.decreaseImageSize} >
|
||||
Decrease image size
|
||||
</Text>
|
||||
<Text
|
||||
style={styles.touchableText}
|
||||
onPress={this.increaseImageSize} >
|
||||
Increase image size
|
||||
</Text>
|
||||
</View>
|
||||
<Text>Container image size: {this.state.width}x{this.state.height} </Text>
|
||||
<View
|
||||
style={[styles.imageContainer, {height: this.state.height, width: this.state.width}]} >
|
||||
<Image
|
||||
style={{flex: 1}}
|
||||
source={[
|
||||
{uri: 'http://facebook.github.io/react/img/logo_small.png', width: 38, height: 38},
|
||||
{uri: 'http://facebook.github.io/react/img/logo_small_2x.png', width: 76, height: 76},
|
||||
{uri: 'http://facebook.github.io/react/img/logo_og.png', width: 400, height: 400}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
increaseImageSize: function() {
|
||||
if (this.state.width >= 100) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
width: this.state.width + 10,
|
||||
height: this.state.height + 10,
|
||||
});
|
||||
},
|
||||
decreaseImageSize: function() {
|
||||
if (this.state.width <= 10) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
width: this.state.width - 10,
|
||||
height: this.state.height - 10,
|
||||
});
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
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.',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
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.',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image source={require('./uie_thumb_normal@2x.png')} style={styles.icon} />
|
||||
<Image source={require('./uie_thumb_selected@2x.png')} style={styles.icon} />
|
||||
{/*<Image source={require('./uie_comment_normal.png')} style={styles.icon} />*/}
|
||||
{/*<Image source={require('./uie_comment_highlighted.png')} style={styles.icon} />*/}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: 'Image Loading Events',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}}
|
||||
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/>
|
||||
);
|
||||
},
|
||||
},
|
||||
*/
|
||||
{
|
||||
title: 'Error Handler',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png'}} />
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Image Download Progress',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'defaultSource',
|
||||
description: 'Show a placeholder image when a network image is loading',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
defaultSource={require('./bunny.png')}
|
||||
source={{uri: 'http://facebook.github.io/origami/public/images/birds.jpg'}}
|
||||
style={styles.base}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Border Color',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[
|
||||
styles.base,
|
||||
styles.background,
|
||||
{borderWidth: 3, borderColor: '#f099f0'}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Border Width',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[
|
||||
styles.base,
|
||||
styles.background,
|
||||
{borderWidth: 5, borderColor: '#f099f0'}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Border Radius',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
style={[styles.base, {borderRadius: 5}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 19}]}
|
||||
source={fullImage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Background Color',
|
||||
render: function() {
|
||||
return (
|
||||
<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}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {backgroundColor: 'red'}]}
|
||||
source={smallImage}
|
||||
/>
|
||||
<Image
|
||||
style={[styles.base, styles.leftMargin, {backgroundColor: 'black'}]}
|
||||
source={smallImage}
|
||||
/>
|
||||
</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}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Nesting',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
style={{width: 60, height: 60, backgroundColor: 'transparent'}}
|
||||
source={fullImage}>
|
||||
<Text style={styles.nestedText}>
|
||||
React
|
||||
</Text>
|
||||
</Image>
|
||||
);
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: 'Tint Color',
|
||||
description: 'The `tintColor` style prop changes all the non-alpha ' +
|
||||
'pixels to the tint color.',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, {borderRadius: 5, tintColor: '#5ac8fa' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('./uie_thumb_normal@2x.png')}
|
||||
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.sectionText}>
|
||||
It also works with downloaded images:
|
||||
</Text>
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, {borderRadius: 5, tintColor: '#5ac8fa' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
*/
|
||||
{
|
||||
title: 'Resize Mode',
|
||||
description: 'The `resizeMode` style prop controls how the image is ' +
|
||||
'rendered within the frame.',
|
||||
render: function() {
|
||||
return (
|
||||
<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>
|
||||
<View style={styles.leftMargin}>
|
||||
<Text style={[styles.resizeModeText]}>
|
||||
Cover
|
||||
</Text>
|
||||
<Image
|
||||
style={styles.resizeMode}
|
||||
resizeMode={Image.resizeMode.cover}
|
||||
source={image}
|
||||
/>
|
||||
</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
|
||||
style={styles.gif}
|
||||
source={{uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Base64 image',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
style={styles.base64}
|
||||
source={{uri: base64Icon, scale: 3}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: 'Cap Insets',
|
||||
description:
|
||||
'When the image is resized, the corners of the size specified ' +
|
||||
'by capInsets will stay a fixed size, but the center content and ' +
|
||||
'borders of the image will be stretched. This is useful for creating ' +
|
||||
'resizable rounded buttons, shadows, and other resizable assets.',
|
||||
render: function() {
|
||||
return <ImageCapInsetsExample />;
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
title: 'Image Size',
|
||||
render: function() {
|
||||
return <ImageSizeExample source={fullImage} />;
|
||||
},
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
title: 'MultipleSourcesExample',
|
||||
description:
|
||||
'The `source` prop allows passing in an array of uris, so that native to choose which image ' +
|
||||
'to diplay based on the size of the of the target image',
|
||||
render: function() {
|
||||
return <MultipleSourcesExample />;
|
||||
},
|
||||
platform: 'android',
|
||||
},
|
||||
*/
|
||||
];
|
||||
|
||||
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'};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
base: {
|
||||
width: 38,
|
||||
height: 38,
|
||||
},
|
||||
progress: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
width: 100
|
||||
},
|
||||
leftMargin: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
background: {
|
||||
backgroundColor: '#222222'
|
||||
},
|
||||
sectionText: {
|
||||
marginVertical: 6,
|
||||
},
|
||||
nestedText: {
|
||||
marginLeft: 12,
|
||||
marginTop: 20,
|
||||
backgroundColor: 'transparent',
|
||||
color: 'white'
|
||||
},
|
||||
resizeMode: {
|
||||
width: 90,
|
||||
height: 60,
|
||||
borderWidth: 0.5,
|
||||
borderColor: 'black'
|
||||
},
|
||||
resizeModeText: {
|
||||
fontSize: 11,
|
||||
marginBottom: 3,
|
||||
},
|
||||
icon: {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
horizontal: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
gif: {
|
||||
flex: 1,
|
||||
height: 200,
|
||||
},
|
||||
base64: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
touchableText: {
|
||||
fontWeight: '500',
|
||||
color: 'blue',
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: Image', module)
|
||||
.addDecorator((renderStory) => <View style={{ width: '100%' }}>{renderStory()}</View>)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
BIN
examples/components/Image/bunny.png
Normal file
BIN
examples/components/Image/bunny.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
examples/components/Image/uie_thumb_normal@2x.png
Normal file
BIN
examples/components/Image/uie_thumb_normal@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 850 B |
BIN
examples/components/Image/uie_thumb_selected@2x.png
Normal file
BIN
examples/components/Image/uie_thumb_selected@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
80
examples/components/ListView/ListViewExample.js
Normal file
80
examples/components/ListView/ListViewExample.js
Normal file
@@ -0,0 +1,80 @@
|
||||
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 dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
||||
|
||||
storiesOf('component: ListView', module)
|
||||
.add('vertical', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(100))}
|
||||
initialListSize={100}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
.add('incremental rendering - large pageSize', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(5000))}
|
||||
initialListSize={100}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
pageSize={50}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
.add('incremental rendering - small pageSize', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ListView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
dataSource={dataSource.cloneWithRows(generateData(5000))}
|
||||
initialListSize={5}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onScroll={(e) => { console.log('ScrollView.onScroll', e); } }
|
||||
pageSize={1}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
renderRow={(row) => (
|
||||
<View><Text>{row}</Text></View>
|
||||
)}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px',
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: '1px'
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
backgroundColor: '#eee',
|
||||
padding: '10px'
|
||||
}
|
||||
});
|
||||
96
examples/components/ProgressBar/ProgressBarExample.js
Normal file
96
examples/components/ProgressBar/ProgressBarExample.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { ProgressBar, StyleSheet, View } from 'react-native'
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import TimerMixin from 'react-timer-mixin';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
var ProgressBarExample = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
progress: 0,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.updateProgress();
|
||||
},
|
||||
|
||||
updateProgress() {
|
||||
var progress = this.state.progress + 0.01;
|
||||
this.setState({ progress });
|
||||
this.requestAnimationFrame(() => this.updateProgress());
|
||||
},
|
||||
|
||||
getProgress(offset) {
|
||||
var 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)} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const examples = [{
|
||||
title: 'progress',
|
||||
render() {
|
||||
return (
|
||||
<ProgressBarExample />
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'indeterminate',
|
||||
render() {
|
||||
return (
|
||||
<ProgressBar indeterminate style={styles.progressView} trackColor='#D1E3F6' />
|
||||
);
|
||||
}
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
minWidth: 200,
|
||||
marginTop: -20,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
progressView: {
|
||||
marginTop: 20,
|
||||
minWidth: 200
|
||||
}
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: ProgressBar', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
59
examples/components/ScrollView/ScrollViewExample.js
Normal file
59
examples/components/ScrollView/ScrollViewExample.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { action, storiesOf } from '@kadira/storybook';
|
||||
import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'
|
||||
|
||||
const onScroll = action('ScrollView.onScroll');
|
||||
|
||||
storiesOf('component: ScrollView', module)
|
||||
.add('vertical', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1000} // 1 event per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={styles.box}>
|
||||
<TouchableHighlight onPress={() => {}}><Text>{i}</Text></TouchableHighlight>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
))
|
||||
.add('horizontal', () => (
|
||||
<View style={styles.scrollViewContainer}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollViewContentContainerStyle}
|
||||
horizontal
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16} // ~60 events per second
|
||||
style={styles.scrollViewStyle}
|
||||
>
|
||||
{Array.from({ length: 50 }).map((item, i) => (
|
||||
<View key={i} style={[ styles.box, styles.horizontalBox ]}>
|
||||
<Text>{i}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
))
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContainer: {
|
||||
height: '200px',
|
||||
width: 300
|
||||
},
|
||||
scrollViewStyle: {
|
||||
borderWidth: 1
|
||||
},
|
||||
scrollViewContentContainerStyle: {
|
||||
backgroundColor: '#eee',
|
||||
padding: 10
|
||||
}
|
||||
})
|
||||
190
examples/components/Switch/SwitchExample.js
Normal file
190
examples/components/Switch/SwitchExample.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Platform, Switch, Text, View } from 'react-native'
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
var BasicSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
trueSwitchIsOn: true,
|
||||
falseSwitchIsOn: false,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.falseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
|
||||
value={this.state.trueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DisabledSwitchExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
disabled={true}
|
||||
style={{marginBottom: 10}}
|
||||
value={true} />
|
||||
<Switch
|
||||
disabled={true}
|
||||
value={false} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var ColorSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
colorTrueSwitchIsOn: true,
|
||||
colorFalseSwitchIsOn: false,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
activeThumbColor="#428bca"
|
||||
activeTrackColor="#A0C4E3"
|
||||
onValueChange={(value) => this.setState({colorFalseSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.colorFalseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
activeThumbColor="#5CB85C"
|
||||
activeTrackColor="#ADDAAD"
|
||||
onValueChange={(value) => this.setState({colorTrueSwitchIsOn: value})}
|
||||
thumbColor="#EBA9A7"
|
||||
trackColor="#D9534F"
|
||||
value={this.state.colorTrueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var EventSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
eventSwitchIsOn: false,
|
||||
eventSwitchRegressionIsOn: true,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
|
||||
<View>
|
||||
<Switch
|
||||
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} />
|
||||
<Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Switch
|
||||
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} />
|
||||
<Text>{this.state.eventSwitchRegressionIsOn ? 'On' : 'Off'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SizeSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
trueSwitchIsOn: true,
|
||||
falseSwitchIsOn: false,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
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 }}
|
||||
value={this.state.trueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var examples = [
|
||||
{
|
||||
title: 'set to true or false',
|
||||
render(): ReactElement<any> { return <BasicSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'disabled',
|
||||
render(): ReactElement<any> { return <DisabledSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'change events',
|
||||
render(): ReactElement<any> { return <EventSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'custom colors',
|
||||
render(): ReactElement<any> { return <ColorSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'custom size',
|
||||
render(): ReactElement<any> { return <SizeSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'controlled component',
|
||||
render(): ReactElement<any> { return <Switch />; }
|
||||
}
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: Switch', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
472
examples/components/Text/TextExample.js
Normal file
472
examples/components/Text/TextExample.js
Normal file
@@ -0,0 +1,472 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
var Entity = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<Text style={{fontWeight: '500', color: '#527fe4'}}>
|
||||
{this.props.children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var AttributeToggler = React.createClass({
|
||||
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() {
|
||||
var 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
|
||||
style={{backgroundColor: '#ffaaaa', marginTop: 5}}
|
||||
onPress={this.toggleWeight}>
|
||||
Toggle Weight
|
||||
</Text>
|
||||
<Text
|
||||
style={{backgroundColor: '#aaaaff', marginTop: 5}}
|
||||
onPress={this.increaseSize}>
|
||||
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 style={{ writingDirection$noI18n: 'rtl' }}>
|
||||
أحب اللغة العربية 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 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(): ReactElement<any> {
|
||||
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 suppressHighlighting={false} style={{backgroundColor: 'white', textDecorationLine: 'underline', color: 'blue'}} onPress={() => null}>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>
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
backgroundColorText: {
|
||||
margin: 5,
|
||||
marginBottom: 0,
|
||||
backgroundColor: 'rgba(100, 100, 100, 0.3)'
|
||||
},
|
||||
});
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: Text', module)
|
||||
.addDecorator((renderStory) => <View style={{ width: 320 }}>{renderStory()}</View>)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
867
examples/components/TextInput/TextInputExample.js
Normal file
867
examples/components/TextInput/TextInputExample.js
Normal file
@@ -0,0 +1,867 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { StyleSheet, Text, TextInput, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
class WithLabel extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.labelContainer}>
|
||||
<View style={styles.label}>
|
||||
<Text>{this.props.label}</Text>
|
||||
</View>
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextEventsExample extends React.Component {
|
||||
state = {
|
||||
curText: '<No Event>',
|
||||
prevText: '<No Event>',
|
||||
prev2Text: '<No Event>',
|
||||
prev3Text: '<No Event>',
|
||||
};
|
||||
|
||||
updateText = (text) => {
|
||||
this.setState((state) => {
|
||||
return {
|
||||
curText: text,
|
||||
prevText: state.curText,
|
||||
prev2Text: state.prevText,
|
||||
prev3Text: state.prev2Text,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<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) => {
|
||||
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
|
||||
}}
|
||||
style={[ styles.default, { maxWidth: 200 } ]}
|
||||
/>
|
||||
<Text style={styles.eventLabel}>
|
||||
{this.state.curText}{'\n'}
|
||||
(prev: {this.state.prevText}){'\n'}
|
||||
(prev2: {this.state.prev2Text}){'\n'}
|
||||
(prev3: {this.state.prev3Text})
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AutoExpandingTextInput extends React.Component {
|
||||
state: any;
|
||||
|
||||
constructor(props) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<TextInput
|
||||
{...this.props}
|
||||
multiline={true}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text});
|
||||
}}
|
||||
onContentSizeChange={(event) => {
|
||||
this.setState({height: event.nativeEvent.contentSize.height});
|
||||
}}
|
||||
style={[styles.default, {height: Math.max(35, this.state.height)}]}
|
||||
value={this.state.text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RewriteExample extends React.Component {
|
||||
state: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {text: ''};
|
||||
}
|
||||
render() {
|
||||
var limit = 20;
|
||||
var remainder = limit - this.state.text.length;
|
||||
var remainderColor = remainder > 5 ? 'blue' : 'red';
|
||||
return (
|
||||
<View style={styles.rewriteContainer}>
|
||||
<TextInput
|
||||
multiline={false}
|
||||
maxLength={limit}
|
||||
onChangeText={(text) => {
|
||||
text = text.replace(/ /g, '_');
|
||||
this.setState({text});
|
||||
}}
|
||||
style={styles.default}
|
||||
value={this.state.text}
|
||||
/>
|
||||
<Text style={[styles.remainder, {color: remainderColor}]}>
|
||||
{remainder}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RewriteExampleInvalidCharacters extends React.Component {
|
||||
state: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {text: ''};
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.rewriteContainer}>
|
||||
<TextInput
|
||||
multiline={false}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text: text.replace(/\s/g, '')});
|
||||
}}
|
||||
style={styles.default}
|
||||
value={this.state.text}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenizedTextExample extends React.Component {
|
||||
state: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {text: 'Hello #World'};
|
||||
}
|
||||
render() {
|
||||
|
||||
//define delimiter
|
||||
let delimiter = /\s+/;
|
||||
|
||||
//split string
|
||||
let _text = this.state.text;
|
||||
let token, index, parts = [];
|
||||
while (_text) {
|
||||
delimiter.lastIndex = 0;
|
||||
token = delimiter.exec(_text);
|
||||
if (token === null) {
|
||||
break;
|
||||
}
|
||||
index = token.index;
|
||||
if (token[0].length === 0) {
|
||||
index = 1;
|
||||
}
|
||||
parts.push(_text.substr(0, index));
|
||||
parts.push(token[0]);
|
||||
index = index + token[0].length;
|
||||
_text = _text.slice(index);
|
||||
}
|
||||
parts.push(_text);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
value={parts.join('')}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BlurOnSubmitExample extends React.Component {
|
||||
focusNextField = (nextField) => {
|
||||
this.refs[nextField].focus();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
ref="1"
|
||||
style={styles.default}
|
||||
placeholder="blurOnSubmit = false"
|
||||
returnKeyType="next"
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('2')}
|
||||
/>
|
||||
<TextInput
|
||||
ref="2"
|
||||
style={styles.default}
|
||||
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"
|
||||
returnKeyType="next"
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('4')}
|
||||
/>
|
||||
<TextInput
|
||||
ref="4"
|
||||
style={styles.default}
|
||||
keyboardType="numeric"
|
||||
placeholder="blurOnSubmit = false"
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('5')}
|
||||
/>
|
||||
<TextInput
|
||||
ref="5"
|
||||
style={styles.default}
|
||||
keyboardType="numeric"
|
||||
placeholder="blurOnSubmit = true"
|
||||
returnKeyType="done"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type SelectionExampleState = {
|
||||
selection: {
|
||||
start: number;
|
||||
end?: number;
|
||||
};
|
||||
value: string;
|
||||
};
|
||||
|
||||
class SelectionExample extends React.Component {
|
||||
state: SelectionExampleState;
|
||||
|
||||
_textInput: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selection: {start: 0, end: 0},
|
||||
value: props.value
|
||||
};
|
||||
}
|
||||
|
||||
onSelectionChange({nativeEvent: {selection}}) {
|
||||
this.setState({selection});
|
||||
}
|
||||
|
||||
getRandomPosition() {
|
||||
var length = this.state.value.length;
|
||||
return Math.round(Math.random() * length);
|
||||
}
|
||||
|
||||
select(start, end) {
|
||||
this._textInput.focus();
|
||||
this.setState({selection: {start, end}});
|
||||
}
|
||||
|
||||
selectRandom() {
|
||||
var positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
|
||||
this.select(...positions);
|
||||
}
|
||||
|
||||
placeAt(position) {
|
||||
this.select(position, position);
|
||||
}
|
||||
|
||||
placeAtRandom() {
|
||||
this.placeAt(this.getRandomPosition());
|
||||
}
|
||||
|
||||
render() {
|
||||
var length = this.state.value.length;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
multiline={this.props.multiline}
|
||||
onChangeText={(value) => this.setState({value})}
|
||||
onSelectionChange={this.onSelectionChange.bind(this)}
|
||||
ref={textInput => (this._textInput = textInput)}
|
||||
selection={this.state.selection}
|
||||
style={this.props.style}
|
||||
value={this.state.value}
|
||||
/>
|
||||
<View>
|
||||
<Text>
|
||||
selection = {JSON.stringify(this.state.selection)}
|
||||
</Text>
|
||||
<Text onPress={this.placeAt.bind(this, 0)}>
|
||||
Place at Start (0, 0)
|
||||
</Text>
|
||||
<Text onPress={this.placeAt.bind(this, length)}>
|
||||
Place at End ({length}, {length})
|
||||
</Text>
|
||||
<Text onPress={this.placeAtRandom.bind(this)}>
|
||||
Place at Random
|
||||
</Text>
|
||||
<Text onPress={this.select.bind(this, 0, length)}>
|
||||
Select All
|
||||
</Text>
|
||||
<Text onPress={this.selectRandom.bind(this)}>
|
||||
Select Random
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
page: {
|
||||
paddingBottom: 300,
|
||||
},
|
||||
default: {
|
||||
height: 26,
|
||||
borderWidth: 0.5,
|
||||
borderColor: '#0f0f0f',
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
padding: 4,
|
||||
},
|
||||
multiline: {
|
||||
borderWidth: 0.5,
|
||||
borderColor: '#0f0f0f',
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
height: 50,
|
||||
padding: 4,
|
||||
marginBottom: 4,
|
||||
},
|
||||
multilineWithFontStyles: {
|
||||
color: 'blue',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 18,
|
||||
fontFamily: 'Cochin',
|
||||
height: 60,
|
||||
},
|
||||
multilineChild: {
|
||||
width: 50,
|
||||
height: 40,
|
||||
position: 'absolute',
|
||||
right: 5,
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
eventLabel: {
|
||||
margin: 3,
|
||||
fontSize: 12,
|
||||
},
|
||||
labelContainer: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 2,
|
||||
flex: 1,
|
||||
},
|
||||
label: {
|
||||
width: 115,
|
||||
alignItems: 'flex-end',
|
||||
marginRight: 10,
|
||||
paddingTop: 2,
|
||||
},
|
||||
rewriteContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
remainder: {
|
||||
textAlign: 'right',
|
||||
width: 24,
|
||||
},
|
||||
hashtag: {
|
||||
color: 'blue',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Auto-focus',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
autoFocus={true}
|
||||
style={styles.default}
|
||||
accessibilityLabel="I am the accessibility label for text input"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Live Re-Write (<sp> -> '_') + maxLength",
|
||||
render: function() {
|
||||
return <RewriteExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Live Re-Write (no spaces allowed)',
|
||||
render: function() {
|
||||
return <RewriteExampleInvalidCharacters />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Auto-capitalize',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="none">
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="sentences">
|
||||
<TextInput
|
||||
autoCapitalize="sentences"
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="words">
|
||||
<TextInput
|
||||
autoCapitalize="words"
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="characters">
|
||||
<TextInput
|
||||
autoCapitalize="characters"
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Auto-correct',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="true">
|
||||
<TextInput autoCorrect={true} style={styles.default} />
|
||||
</WithLabel>
|
||||
<WithLabel label="false">
|
||||
<TextInput autoCorrect={false} style={styles.default} />
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Keyboard types',
|
||||
render: function() {
|
||||
var keyboardTypes = [
|
||||
'default',
|
||||
//'ascii-capable',
|
||||
//'numbers-and-punctuation',
|
||||
'url',
|
||||
'number-pad',
|
||||
'phone-pad',
|
||||
//'name-phone-pad',
|
||||
'email-address',
|
||||
//'decimal-pad',
|
||||
//'twitter',
|
||||
'web-search',
|
||||
'numeric',
|
||||
];
|
||||
var examples = keyboardTypes.map((type) => {
|
||||
return (
|
||||
<WithLabel key={type} label={type}>
|
||||
<TextInput
|
||||
keyboardType={type}
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
);
|
||||
});
|
||||
return <View>{examples}</View>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Keyboard appearance',
|
||||
render: function() {
|
||||
var keyboardAppearance = [
|
||||
'default',
|
||||
'light',
|
||||
'dark',
|
||||
];
|
||||
var examples = keyboardAppearance.map((type) => {
|
||||
return (
|
||||
<WithLabel key={type} label={type}>
|
||||
<TextInput
|
||||
keyboardAppearance={type}
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
);
|
||||
});
|
||||
return <View>{examples}</View>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Return key types',
|
||||
render: function() {
|
||||
var returnKeyTypes = [
|
||||
'default',
|
||||
'go',
|
||||
'google',
|
||||
'join',
|
||||
'next',
|
||||
'route',
|
||||
'search',
|
||||
'send',
|
||||
'yahoo',
|
||||
'done',
|
||||
'emergency-call',
|
||||
];
|
||||
var examples = returnKeyTypes.map((type) => {
|
||||
return (
|
||||
<WithLabel key={type} label={type}>
|
||||
<TextInput
|
||||
returnKeyType={type}
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
);
|
||||
});
|
||||
return <View>{examples}</View>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Enable return key automatically',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="true">
|
||||
<TextInput enablesReturnKeyAutomatically={true} style={styles.default} />
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Secure text entry',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="true">
|
||||
<TextInput secureTextEntry={true} style={styles.default} defaultValue="abc" />
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Event handling',
|
||||
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"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Colored highlight/cursor for text input',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
style={styles.default}
|
||||
selectionColor={"green"}
|
||||
defaultValue="Highlight me"
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.default}
|
||||
selectionColor={"rgba(86, 76, 205, 1)"}
|
||||
defaultValue="Highlight me"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Clear button mode',
|
||||
render: function () {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="never">
|
||||
<TextInput
|
||||
style={styles.default}
|
||||
clearButtonMode="never"
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="while editing">
|
||||
<TextInput
|
||||
style={styles.default}
|
||||
clearButtonMode="while-editing"
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="unless editing">
|
||||
<TextInput
|
||||
style={styles.default}
|
||||
clearButtonMode="unless-editing"
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="always">
|
||||
<TextInput
|
||||
style={styles.default}
|
||||
clearButtonMode="always"
|
||||
/>
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Clear and select',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="clearTextOnFocus">
|
||||
<TextInput
|
||||
placeholder="text is cleared on focus"
|
||||
defaultValue="text is cleared on focus"
|
||||
style={styles.default}
|
||||
clearTextOnFocus={true}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="selectTextOnFocus">
|
||||
<TextInput
|
||||
placeholder="text is selected on focus"
|
||||
defaultValue="text is selected on focus"
|
||||
style={styles.default}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Blur on submit',
|
||||
render: function(): React.Element<any> { return <BlurOnSubmitExample />; },
|
||||
},
|
||||
{
|
||||
title: 'Multiline blur on submit',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
style={styles.multiline}
|
||||
placeholder="blurOnSubmit = true"
|
||||
returnKeyType="next"
|
||||
blurOnSubmit={true}
|
||||
multiline={true}
|
||||
onSubmitEditing={event => alert(event.nativeEvent.text)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Multiline',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<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"
|
||||
keyboardType="url"
|
||||
style={[styles.multiline, styles.multilineWithFontStyles]}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline text input with max length"
|
||||
maxLength={5}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="uneditable multiline text input"
|
||||
editable={false}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
defaultValue="uneditable multiline text input with phone number detection: 88888888."
|
||||
editable={false}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
dataDetectorTypes="phoneNumber"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Number of lines',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
multiline={true}
|
||||
numberOfLines={4}
|
||||
style={[ styles.multiline, { height: 'auto' } ]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Auto-expanding',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<AutoExpandingTextInput
|
||||
placeholder="height increases with content"
|
||||
enablesReturnKeyAutomatically={true}
|
||||
returnKeyType="default"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Attributed text',
|
||||
render: function() {
|
||||
return <TokenizedTextExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Text selection & cursor placement',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<SelectionExample
|
||||
style={styles.default}
|
||||
value="text selection can be changed"
|
||||
/>
|
||||
<SelectionExample
|
||||
multiline
|
||||
style={styles.multiline}
|
||||
value={"multiline text selection\ncan also be changed"}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'TextInput maxLength',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="maxLength: 5">
|
||||
<TextInput
|
||||
maxLength={5}
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="maxLength: 5 with placeholder">
|
||||
<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}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="maxLength: 5 with very long default value already set">
|
||||
<TextInput
|
||||
maxLength={5}
|
||||
defaultValue="9402512345"
|
||||
style={styles.default}
|
||||
/>
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: TextInput', module)
|
||||
.add(example.title, () => example.render())
|
||||
});
|
||||
450
examples/components/Touchable/TouchableExample.js
Normal file
450
examples/components/Touchable/TouchableExample.js
Normal file
@@ -0,0 +1,450 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import {
|
||||
Image,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
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
|
||||
style={styles.wrapper}
|
||||
onPress={() => console.log('stock THW image - highlight')}>
|
||||
<Image
|
||||
source={heartImage}
|
||||
style={styles.image}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
activeOpacity={1}
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
<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(): ReactElement<any> {
|
||||
return <TextOnPressBox />;
|
||||
},
|
||||
}, {
|
||||
title: 'Touchable feedback events',
|
||||
description: '<Touchable*> components accept onPress, onPressIn, ' +
|
||||
'onPressOut, and onLongPress as props.',
|
||||
render: function(): ReactElement<any> {
|
||||
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(): ReactElement<any> {
|
||||
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(): ReactElement<any> {
|
||||
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(): ReactElement<any> {
|
||||
return <TouchableHitSlop />;
|
||||
},
|
||||
}, {
|
||||
title: 'Disabled Touchable*',
|
||||
description: '<Touchable*> components accept disabled prop which prevents ' +
|
||||
'any interaction with component',
|
||||
render: function(): ReactElement<any> {
|
||||
return <TouchableDisabled />;
|
||||
},
|
||||
}];
|
||||
|
||||
var TextOnPressBox = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
timesPressed: 0,
|
||||
};
|
||||
},
|
||||
textOnPress: function() {
|
||||
this.setState({
|
||||
timesPressed: this.state.timesPressed + 1,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var 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
|
||||
style={styles.textBlock}
|
||||
onPress={this.textOnPress}>
|
||||
Text has built-in onPress handling
|
||||
</Text>
|
||||
<View style={styles.logBox}>
|
||||
<Text>
|
||||
{textLog}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TouchableFeedbackEvents = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
eventLog: [],
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View testID="touchable_feedback_events">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_feedback_events_button"
|
||||
accessibilityLabel="touchable feedback events"
|
||||
accessibilityTraits="button"
|
||||
accessibilityComponentType="button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
onPressIn={() => this._appendEvent('pressIn')}
|
||||
onPressOut={() => this._appendEvent('pressOut')}
|
||||
onLongPress={() => this._appendEvent('longPress')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View testID="touchable_feedback_events_console" style={styles.eventLogBox}>
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
_appendEvent: function(eventName) {
|
||||
var limit = 6;
|
||||
var eventLog = this.state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
this.setState({eventLog});
|
||||
},
|
||||
});
|
||||
|
||||
var TouchableDelayEvents = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
eventLog: [],
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View testID="touchable_delay_events">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_delay_events_button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
delayPressIn={400}
|
||||
onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
|
||||
delayPressOut={1000}
|
||||
onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
|
||||
delayLongPress={800}
|
||||
onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
|
||||
<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>
|
||||
);
|
||||
},
|
||||
_appendEvent: function(eventName) {
|
||||
var limit = 6;
|
||||
var eventLog = this.state.eventLog.slice(0, limit - 1);
|
||||
eventLog.unshift(eventName);
|
||||
this.setState({eventLog});
|
||||
},
|
||||
});
|
||||
|
||||
var ForceTouchExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
force: 0,
|
||||
};
|
||||
},
|
||||
_renderConsoleText: function() {
|
||||
return View.forceTouchAvailable ?
|
||||
'Force: ' + this.state.force.toFixed(3) :
|
||||
'3D Touch is not available on this device';
|
||||
},
|
||||
render: function() {
|
||||
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
|
||||
style={styles.wrapper}
|
||||
testID="touchable_3dtouch_button"
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderMove={(event) => this.setState({force: event.nativeEvent.force})}
|
||||
onResponderRelease={(event) => this.setState({force: 0})}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var TouchableHitSlop = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
timesPressed: 0,
|
||||
};
|
||||
},
|
||||
onPress: function() {
|
||||
this.setState({
|
||||
timesPressed: this.state.timesPressed + 1,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var 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
|
||||
onPress={this.onPress}
|
||||
style={styles.hitSlopWrapper}
|
||||
hitSlop={{top: 30, bottom: 30, left: 60, right: 60}}
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TouchableDisabled = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableOpacity disabled={true} style={[styles.row, styles.block]}>
|
||||
<Text style={styles.disabledButton}>Disabled TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity disabled={false} style={[styles.row, styles.block]}>
|
||||
<Text style={styles.button}>Enabled TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableHighlight
|
||||
activeOpacity={1}
|
||||
disabled={true}
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
<Text style={styles.disabledButton}>
|
||||
Disabled TouchableHighlight
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
|
||||
<TouchableHighlight
|
||||
activeOpacity={1}
|
||||
animationVelocity={0}
|
||||
underlayColor="rgb(210, 230, 255)"
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom THW text - highlight')}>
|
||||
<Text style={styles.button}>
|
||||
Enabled TouchableHighlight
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
|
||||
{Platform.OS === 'android' &&
|
||||
<TouchableNativeFeedback
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom TNF has been clicked')}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
<View>
|
||||
<Text style={[styles.button, styles.nativeFeedbackButton]}>
|
||||
Enabled TouchableNativeFeedback
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
}
|
||||
|
||||
{Platform.OS === 'android' &&
|
||||
<TouchableNativeFeedback
|
||||
disabled={true}
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom TNF has been clicked')}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
<View>
|
||||
<Text style={[styles.disabledButton, styles.nativeFeedbackButton]}>
|
||||
Disabled TouchableNativeFeedback
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
|
||||
|
||||
var 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())
|
||||
})
|
||||
250
examples/components/View/ViewExample.js
Normal file
250
examples/components/View/ViewExample.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
box: {
|
||||
backgroundColor: '#527FE4',
|
||||
borderColor: '#000033',
|
||||
borderWidth: 1,
|
||||
},
|
||||
zIndex: {
|
||||
justifyContent: 'space-around',
|
||||
width: 100,
|
||||
height: 50,
|
||||
marginTop: -10,
|
||||
},
|
||||
});
|
||||
|
||||
var ViewBorderStyleExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
showBorder: true
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={this._handlePress}>
|
||||
<View>
|
||||
<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}}>
|
||||
Dotted border style
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
},
|
||||
|
||||
_handlePress() {
|
||||
this.setState({showBorder: !this.state.showBorder});
|
||||
}
|
||||
});
|
||||
|
||||
var ZIndexExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
flipped: false
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const indices = this.state.flipped ? [-1, 0, 1, 2] : [2, 1, 0, -1];
|
||||
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>ZIndex {indices[0]}</Text>
|
||||
</View>
|
||||
<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]}
|
||||
]}>
|
||||
<Text>ZIndex {indices[2]}</Text>
|
||||
</View>
|
||||
<View style={[
|
||||
styles.zIndex,
|
||||
{marginLeft: 150, backgroundColor: '#64B5F6', zIndex: indices[3]}
|
||||
]}>
|
||||
<Text>ZIndex {indices[3]}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
},
|
||||
|
||||
_handlePress() {
|
||||
this.setState({flipped: !this.state.flipped});
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Background Color',
|
||||
render: function() {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
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>
|
||||
<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}}>
|
||||
5px margin and padding,
|
||||
</Text>
|
||||
<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}}>
|
||||
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}} />
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Overflow',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View
|
||||
style={{
|
||||
width: 95,
|
||||
height: 10,
|
||||
marginRight: 10,
|
||||
marginBottom: 5,
|
||||
overflow: 'hidden',
|
||||
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}}>
|
||||
<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>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'ZIndex',
|
||||
render: function() {
|
||||
return <ZIndexExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: View', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
286
examples/components/View/ViewTransformsExample.js
Normal file
286
examples/components/View/ViewTransformsExample.js
Normal file
@@ -0,0 +1,286 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import { Animated, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
var Flip = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
theta: new Animated.Value(45),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this._animate();
|
||||
},
|
||||
|
||||
_animate() {
|
||||
this.state.theta.setValue(0);
|
||||
Animated.timing(this.state.theta, {
|
||||
toValue: 360,
|
||||
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']
|
||||
})},
|
||||
]}]}>
|
||||
<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']
|
||||
})},
|
||||
]}]}>
|
||||
<Text style={styles.flipText}>
|
||||
On the flip side...
|
||||
</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
box1: {
|
||||
left: 0,
|
||||
backgroundColor: 'green',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{translateX: 100},
|
||||
{translateY: 50},
|
||||
{rotate: '30deg'},
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box2: {
|
||||
left: 0,
|
||||
backgroundColor: 'purple',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
{translateX: 100},
|
||||
{translateY: 50},
|
||||
{rotate: '30deg'},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box3step1: {
|
||||
left: 0,
|
||||
backgroundColor: 'lightpink',
|
||||
height: 50,
|
||||
top: 0,
|
||||
transform: [
|
||||
{rotate: '30deg'},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box3step2: {
|
||||
left: 0,
|
||||
backgroundColor: 'hotpink',
|
||||
height: 50,
|
||||
opacity: 0.5,
|
||||
top: 0,
|
||||
transform: [
|
||||
{rotate: '30deg'},
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
],
|
||||
width: 50,
|
||||
},
|
||||
box3step3: {
|
||||
left: 0,
|
||||
backgroundColor: 'deeppink',
|
||||
height: 50,
|
||||
opacity: 0.5,
|
||||
top: 0,
|
||||
transform: [
|
||||
{rotate: '30deg'},
|
||||
{scaleX: 2},
|
||||
{scaleY: 2},
|
||||
{translateX: 10},
|
||||
{translateY: 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,
|
||||
},
|
||||
box5: {
|
||||
backgroundColor: 'maroon',
|
||||
height: 50,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: 50,
|
||||
},
|
||||
box5Transform: {
|
||||
transform: [
|
||||
{translateX: -50},
|
||||
{translateY: 35},
|
||||
{rotate: '50deg'},
|
||||
{scale: 2},
|
||||
],
|
||||
},
|
||||
flipCardContainer: {
|
||||
marginVertical: 40,
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
flipCard: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'blue',
|
||||
backfaceVisibility: 'hidden',
|
||||
},
|
||||
flipText: {
|
||||
width: 90,
|
||||
fontSize: 20,
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
});
|
||||
|
||||
const examples = [
|
||||
{
|
||||
title: 'Perspective',
|
||||
description: 'perspective: 850, rotateX: Animated.timing(0 -> 360)',
|
||||
render(): ReactElement<any> { return <Flip />; }
|
||||
},
|
||||
{
|
||||
title: 'Translate, Rotate, Scale',
|
||||
description: "translateX: 100, translateY: 50, rotate: '30deg', scaleX: 2, scaleY: 2",
|
||||
render() {
|
||||
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} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate',
|
||||
description: "rotate: '30deg'",
|
||||
render() {
|
||||
return (
|
||||
|
||||
<View style={styles.box3step1} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate, Scale',
|
||||
description: "rotate: '30deg', scaleX: 2, scaleY: 2",
|
||||
render() {
|
||||
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} />
|
||||
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Translate, Scale, Rotate',
|
||||
description: "translate: [200, 350], scale: 2.5, rotate: '-0.2rad'",
|
||||
render() {
|
||||
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]} />
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('component: View (transforms)', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
@@ -23,7 +23,7 @@ var {
|
||||
AppRegistry,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableBounce,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = ReactNative;
|
||||
|
||||
@@ -139,9 +139,9 @@ class GameEndOverlay extends React.Component {
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<Text style={styles.overlayMessage}>{message}</Text>
|
||||
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<TouchableOpacity onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||
</TouchableBounce>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
8
examples/demos/Game2048/Game2048Example.js
Normal file
8
examples/demos/Game2048/Game2048Example.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import Game2048 from './Game2048'
|
||||
|
||||
storiesOf('demo: Game2048', module)
|
||||
.add('the game', () => (
|
||||
<Game2048 />
|
||||
))
|
||||
8
examples/demos/TicTacToe/TicTacToeExample.js
Normal file
8
examples/demos/TicTacToe/TicTacToeExample.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
import TicTacToe from './TicTacToe'
|
||||
|
||||
storiesOf('demo: TicTacToe', module)
|
||||
.add('the game', () => (
|
||||
<TicTacToe />
|
||||
))
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>React Native for Web</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<div id="react-root"></div>
|
||||
<script src="/bundle.js"></script>
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactNative, { AppRegistry } from 'react-native'
|
||||
import App from './components/App'
|
||||
import Game2048 from './2048/Game2048'
|
||||
import TicTacToeApp from './TicTacToe/TicTacToe'
|
||||
|
||||
const rootTag = document.getElementById('react-root')
|
||||
AppRegistry.registerComponent('App', () => App)
|
||||
AppRegistry.runApplication('App', { rootTag })
|
||||
// ReactNative.render(<App />, rootTag)
|
||||
@@ -1,62 +0,0 @@
|
||||
const webpack = require('webpack')
|
||||
|
||||
const testEntry = 'tests.webpack.js'
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
|
||||
browserNoActivityTimeout: 60000,
|
||||
client: {
|
||||
captureConsole: true,
|
||||
mocha: { ui: 'tdd' },
|
||||
useIframe: true
|
||||
},
|
||||
files: [
|
||||
testEntry
|
||||
],
|
||||
frameworks: [ 'mocha' ],
|
||||
plugins: [
|
||||
'karma-chrome-launcher',
|
||||
'karma-firefox-launcher',
|
||||
'karma-mocha',
|
||||
'karma-mocha-reporter',
|
||||
'karma-sourcemap-loader',
|
||||
'karma-webpack'
|
||||
],
|
||||
preprocessors: {
|
||||
[testEntry]: [ 'webpack', 'sourcemap' ]
|
||||
},
|
||||
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'mocha' ],
|
||||
singleRun: true,
|
||||
webpack: {
|
||||
devtool: 'inline-source-map',
|
||||
// required by 'enzyme'
|
||||
externals: {
|
||||
'cheerio': 'window',
|
||||
'react/addons': true,
|
||||
'react/lib/ExecutionEnvironment': true,
|
||||
'react/lib/ReactContext': true
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel',
|
||||
query: { cacheDirectory: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify('test')
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
webpackServer: {
|
||||
noInfo: true
|
||||
}
|
||||
})
|
||||
}
|
||||
80
package.json
80
package.json
@@ -1,61 +1,63 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.37",
|
||||
"version": "0.0.60",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"src",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
|
||||
"build:examples": "build-storybook -o dist-examples -c ./examples/.storybook",
|
||||
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
|
||||
"examples": "webpack-dev-server --config examples/webpack.config.js --inline --hot --colors --quiet",
|
||||
"deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -",
|
||||
"examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track",
|
||||
"lint": "eslint src",
|
||||
"prepublish": "npm run build && npm run build:umd",
|
||||
"test": "karma start karma.config.js",
|
||||
"test:watch": "npm run test -- --no-single-run"
|
||||
"test": "npm run lint && npm run test:jest",
|
||||
"test:jest": "jest",
|
||||
"test:watch": "npm run test:jest -- --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"animated": "^0.1.3",
|
||||
"babel-runtime": "^6.9.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"inline-style-prefixer": "^2.0.0",
|
||||
"lodash": "^4.13.1",
|
||||
"react-dom": "^15.1.0",
|
||||
"react-textarea-autosize": "^4.0.2",
|
||||
"array-find-index": "^1.0.2",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"debounce": "^1.0.0",
|
||||
"deep-assign": "^2.0.0",
|
||||
"fbjs": "^0.8.4",
|
||||
"inline-style-prefixer": "^2.0.1",
|
||||
"react-dom": "~15.3.2",
|
||||
"react-textarea-autosize": "^4.0.4",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.10.1",
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-eslint": "^6.1.0",
|
||||
"babel-loader": "^6.2.4",
|
||||
"@kadira/storybook": "^2.5.1",
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.2.11",
|
||||
"babel-preset-react-native": "^1.9.0",
|
||||
"del-cli": "^0.2.0",
|
||||
"enzyme": "^2.3.0",
|
||||
"eslint": "^2.12.0",
|
||||
"eslint-config-standard": "^5.3.1",
|
||||
"eslint-config-standard-react": "^2.4.0",
|
||||
"eslint-plugin-promise": "^1.3.2",
|
||||
"eslint-plugin-react": "^5.1.1",
|
||||
"eslint-plugin-standard": "^1.3.2",
|
||||
"karma": "^0.13.22",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-mocha": "^1.1.1",
|
||||
"karma-mocha-reporter": "^2.0.4",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.5.3",
|
||||
"enzyme": "^2.4.1",
|
||||
"eslint": "^3.4.0",
|
||||
"eslint-plugin-jsx-a11y": "^2.2.0",
|
||||
"eslint-plugin-promise": "^2.0.1",
|
||||
"eslint-plugin-react": "^6.1.2",
|
||||
"file-loader": "^0.9.0",
|
||||
"jest": "^16.0.2",
|
||||
"node-libs-browser": "^0.5.3",
|
||||
"react": "^15.2.0",
|
||||
"react-addons-test-utils": "^15.2.0",
|
||||
"webpack": "^1.13.1",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
"react": "~15.3.2",
|
||||
"react-addons-test-utils": "~15.3.2",
|
||||
"react-test-renderer": "~15.3.2",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-bundle-analyzer": "^1.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.1.0"
|
||||
"react": "~15.3.2"
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "BSD-3-Clause",
|
||||
@@ -71,5 +73,9 @@
|
||||
"react-component",
|
||||
"react-native",
|
||||
"web"
|
||||
]
|
||||
],
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"timers": "fake"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import Animated from 'animated'
|
||||
import StyleSheet from '../StyleSheet'
|
||||
import Image from '../../components/Image'
|
||||
import Text from '../../components/Text'
|
||||
import View from '../../components/View'
|
||||
import Animated from 'animated';
|
||||
import Image from '../../components/Image';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import Text from '../../components/Text';
|
||||
import View from '../../components/View';
|
||||
|
||||
Animated.inject.FlattenStyle(StyleSheet.flatten)
|
||||
Animated.inject.FlattenStyle(StyleSheet.flatten);
|
||||
|
||||
module.exports = {
|
||||
...Animated,
|
||||
Image: Animated.createAnimatedComponent(Image),
|
||||
Text: Animated.createAnimatedComponent(Text),
|
||||
View: Animated.createAnimatedComponent(View)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../StyleSheet'
|
||||
import View from '../../components/View'
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import View from '../../components/View';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
class ReactNativeApp extends Component {
|
||||
static propTypes = {
|
||||
@@ -10,13 +10,13 @@ class ReactNativeApp extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { initialProps, rootComponent: RootComponent, rootTag } = this.props
|
||||
const { initialProps, rootComponent: RootComponent, rootTag } = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.appContainer}>
|
||||
<RootComponent {...initialProps} rootTag={rootTag} />
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@ const styles = StyleSheet.create({
|
||||
right: 0,
|
||||
bottom: 0
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
module.exports = ReactNativeApp
|
||||
module.exports = ReactNativeApp;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import assert from 'assert'
|
||||
import { prerenderApplication } from '../renderApplication'
|
||||
import React from 'react'
|
||||
import { getApplication } from '../renderApplication';
|
||||
import React from 'react';
|
||||
|
||||
const component = () => <div />
|
||||
const component = () => <div />;
|
||||
|
||||
suite('apis/AppRegistry/renderApplication', () => {
|
||||
test('prerenderApplication', () => {
|
||||
const { html, styleElement } = prerenderApplication(component, {})
|
||||
describe('apis/AppRegistry/renderApplication', () => {
|
||||
test('getApplication', () => {
|
||||
const { element, stylesheet } = getApplication(component, {});
|
||||
|
||||
assert.ok(html.indexOf('<div ') > -1)
|
||||
assert.equal(styleElement.type, 'style')
|
||||
})
|
||||
})
|
||||
expect(element).toBeTruthy();
|
||||
expect(stylesheet.type).toEqual('style');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { Component } from 'react'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import ReactDOM from 'react-dom'
|
||||
import renderApplication, { prerenderApplication } from './renderApplication'
|
||||
import { Component } from 'react';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import { unmountComponentAtNode } from 'react/lib/ReactMount';
|
||||
import renderApplication, { getApplication } from './renderApplication';
|
||||
|
||||
const runnables = {}
|
||||
const runnables = {};
|
||||
|
||||
type ComponentProvider = () => Component<any, any, any>
|
||||
|
||||
@@ -26,67 +26,67 @@ type AppConfig = {
|
||||
*/
|
||||
class AppRegistry {
|
||||
static getAppKeys(): Array<string> {
|
||||
return Object.keys(runnables)
|
||||
return Object.keys(runnables);
|
||||
}
|
||||
|
||||
static prerenderApplication(appKey: string, appParameters?: Object): string {
|
||||
static getApplication(appKey: string, appParameters?: Object): string {
|
||||
invariant(
|
||||
runnables[appKey] && runnables[appKey].prerender,
|
||||
runnables[appKey] && runnables[appKey].getApplication,
|
||||
`Application ${appKey} has not been registered. ` +
|
||||
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
|
||||
)
|
||||
);
|
||||
|
||||
return runnables[appKey].prerender(appParameters)
|
||||
return runnables[appKey].getApplication(appParameters);
|
||||
}
|
||||
|
||||
static registerComponent(appKey: string, getComponentFunc: ComponentProvider): string {
|
||||
runnables[appKey] = {
|
||||
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag),
|
||||
prerender: ({ initialProps } = {}) => prerenderApplication(getComponentFunc(), initialProps)
|
||||
}
|
||||
return appKey
|
||||
getApplication: ({ initialProps } = {}) => getApplication(getComponentFunc(), initialProps),
|
||||
run: ({ initialProps, rootTag }) => renderApplication(getComponentFunc(), initialProps, rootTag)
|
||||
};
|
||||
return appKey;
|
||||
}
|
||||
|
||||
static registerConfig(config: Array<AppConfig>) {
|
||||
config.forEach(({ appKey, component, run }) => {
|
||||
if (run) {
|
||||
AppRegistry.registerRunnable(appKey, run)
|
||||
AppRegistry.registerRunnable(appKey, run);
|
||||
} else {
|
||||
invariant(component, 'No component provider passed in')
|
||||
AppRegistry.registerComponent(appKey, component)
|
||||
invariant(component, 'No component provider passed in');
|
||||
AppRegistry.registerComponent(appKey, component);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: fix style sheet creation when using this method
|
||||
static registerRunnable(appKey: string, run: Function): string {
|
||||
runnables[appKey] = { run }
|
||||
return appKey
|
||||
runnables[appKey] = { run };
|
||||
return appKey;
|
||||
}
|
||||
|
||||
static runApplication(appKey: string, appParameters?: Object): void {
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
const params = { ...appParameters }
|
||||
params.rootTag = `#${params.rootTag.id}`
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
const params = { ...appParameters };
|
||||
params.rootTag = `#${params.rootTag.id}`;
|
||||
|
||||
console.log(
|
||||
`Running application "${appKey}" with appParams: ${JSON.stringify(params)}. ` +
|
||||
`development-level warnings are ${isDevelopment ? 'ON' : 'OFF'}, ` +
|
||||
`performance optimizations are ${isDevelopment ? 'OFF' : 'ON'}`
|
||||
)
|
||||
);
|
||||
|
||||
invariant(
|
||||
runnables[appKey] && runnables[appKey].run,
|
||||
`Application "${appKey}" has not been registered. ` +
|
||||
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
|
||||
)
|
||||
);
|
||||
|
||||
runnables[appKey].run(appParameters)
|
||||
runnables[appKey].run(appParameters);
|
||||
}
|
||||
|
||||
static unmountApplicationComponentAtRootTag(rootTag) {
|
||||
ReactDOM.unmountComponentAtNode(rootTag)
|
||||
unmountComponentAtNode(rootTag);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppRegistry
|
||||
module.exports = AppRegistry;
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import React, { Component } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNativeApp from './ReactNativeApp'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import { render } from 'react/lib/ReactMount';
|
||||
import ReactNativeApp from './ReactNativeApp';
|
||||
import StyleSheet from '../../apis/StyleSheet';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
|
||||
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
|
||||
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag);
|
||||
|
||||
const component = (
|
||||
<ReactNativeApp
|
||||
@@ -22,18 +21,17 @@ export default function renderApplication(RootComponent: Component, initialProps
|
||||
rootComponent={RootComponent}
|
||||
rootTag={rootTag}
|
||||
/>
|
||||
)
|
||||
ReactDOM.render(component, rootTag)
|
||||
);
|
||||
render(component, rootTag);
|
||||
}
|
||||
|
||||
export function prerenderApplication(RootComponent: Component, initialProps: Object): string {
|
||||
const component = (
|
||||
export function getApplication(RootComponent: Component, initialProps: Object): Object {
|
||||
const element = (
|
||||
<ReactNativeApp
|
||||
initialProps={initialProps}
|
||||
rootComponent={RootComponent}
|
||||
/>
|
||||
)
|
||||
const html = ReactDOMServer.renderToString(component)
|
||||
const styleElement = StyleSheet.render()
|
||||
return { html, styleElement }
|
||||
);
|
||||
const stylesheet = StyleSheet.render();
|
||||
return { element, stylesheet };
|
||||
}
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import AppState from '..'
|
||||
import assert from 'assert'
|
||||
import AppState from '..';
|
||||
|
||||
suite('apis/AppState', () => {
|
||||
const handler = () => {}
|
||||
describe('apis/AppState', () => {
|
||||
const handler = () => {};
|
||||
|
||||
teardown(() => {
|
||||
try { AppState.removeEventListener('change', handler) } catch (e) {}
|
||||
})
|
||||
afterEach(() => {
|
||||
try { AppState.removeEventListener('change', handler); } catch (e) {}
|
||||
});
|
||||
|
||||
suite('addEventListener', () => {
|
||||
describe('addEventListener', () => {
|
||||
test('throws if the provided "eventType" is not supported', () => {
|
||||
assert.throws(() => AppState.addEventListener('foo', handler))
|
||||
assert.doesNotThrow(() => AppState.addEventListener('change', handler))
|
||||
})
|
||||
})
|
||||
expect(() => AppState.addEventListener('foo', handler)).toThrow();
|
||||
expect(() => AppState.addEventListener('change', handler)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
suite('removeEventListener', () => {
|
||||
describe('removeEventListener', () => {
|
||||
test('throws if the handler is not registered', () => {
|
||||
assert.throws(() => AppState.removeEventListener('change', handler))
|
||||
})
|
||||
expect(() => AppState.removeEventListener('change', handler)).toThrow();
|
||||
});
|
||||
|
||||
test('throws if the provided "eventType" is not supported', () => {
|
||||
AppState.addEventListener('change', handler)
|
||||
assert.throws(() => AppState.removeEventListener('foo', handler))
|
||||
assert.doesNotThrow(() => AppState.removeEventListener('change', handler))
|
||||
})
|
||||
})
|
||||
})
|
||||
AppState.addEventListener('change', handler);
|
||||
expect(() => AppState.removeEventListener('foo', handler)).toThrow();
|
||||
expect(() => AppState.removeEventListener('change', handler)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import findIndex from 'lodash/findIndex'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import findIndex from 'array-find-index';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const EVENT_TYPES = [ 'change' ]
|
||||
const VISIBILITY_CHANGE_EVENT = 'visibilitychange'
|
||||
const EVENT_TYPES = [ 'change' ];
|
||||
const VISIBILITY_CHANGE_EVENT = 'visibilitychange';
|
||||
|
||||
const AppStates = {
|
||||
BACKGROUND: 'background',
|
||||
ACTIVE: 'active'
|
||||
}
|
||||
};
|
||||
|
||||
const listeners = []
|
||||
const listeners = [];
|
||||
|
||||
class AppState {
|
||||
static isSupported = ExecutionEnvironment.canUseDOM && document.visibilityState
|
||||
|
||||
static get currentState() {
|
||||
if (!AppState.isSupported) {
|
||||
return AppState.ACTIVE
|
||||
return AppState.ACTIVE;
|
||||
}
|
||||
|
||||
switch (document.visibilityState) {
|
||||
case 'hidden':
|
||||
case 'prerender':
|
||||
case 'unloaded':
|
||||
return AppStates.BACKGROUND
|
||||
return AppStates.BACKGROUND;
|
||||
default:
|
||||
return AppStates.ACTIVE
|
||||
return AppStates.ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
static addEventListener(type: string, handler: Function) {
|
||||
if (AppState.isSupported) {
|
||||
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
const callback = () => handler(AppState.currentState)
|
||||
listeners.push([ handler, callback ])
|
||||
document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false)
|
||||
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
|
||||
const callback = () => handler(AppState.currentState);
|
||||
listeners.push([ handler, callback ]);
|
||||
document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false);
|
||||
}
|
||||
}
|
||||
|
||||
static removeEventListener(type: string, handler: Function) {
|
||||
if (AppState.isSupported) {
|
||||
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type)
|
||||
const listenerIndex = findIndex(listeners, (pair) => pair[0] === handler)
|
||||
invariant(listenerIndex !== -1, 'Trying to remove AppState listener for unregistered handler')
|
||||
const callback = listeners[listenerIndex][1]
|
||||
document.removeEventListener(VISIBILITY_CHANGE_EVENT, callback, false)
|
||||
listeners.splice(listenerIndex, 1)
|
||||
invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type);
|
||||
const listenerIndex = findIndex(listeners, (pair) => pair[0] === handler);
|
||||
invariant(listenerIndex !== -1, 'Trying to remove AppState listener for unregistered handler');
|
||||
const callback = listeners[listenerIndex][1];
|
||||
document.removeEventListener(VISIBILITY_CHANGE_EVENT, callback, false);
|
||||
listeners.splice(listenerIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppState
|
||||
module.exports = AppState;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
exports[`apis/AsyncStorage mergeLocalStorageItem should have same behavior as react-native 1`] = `
|
||||
Object {
|
||||
"age": 31,
|
||||
"name": "Chris",
|
||||
"traits": Object {
|
||||
"eyes": "blue",
|
||||
"hair": "brown",
|
||||
"shoe_size": 10,
|
||||
},
|
||||
}
|
||||
`;
|
||||
@@ -1,5 +1,74 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
import AsyncStorage from '..';
|
||||
|
||||
suite('apis/AsyncStorage', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
const waterfall = (fns, cb) => {
|
||||
const _waterfall = (...args) => {
|
||||
const fn = (fns || []).shift();
|
||||
if (typeof fn === 'function') {
|
||||
fn(...args, (err, ...nextArgs) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
} else {
|
||||
return _waterfall(...nextArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(null, ...args);
|
||||
}
|
||||
};
|
||||
_waterfall();
|
||||
};
|
||||
|
||||
const obj = {};
|
||||
const mockLocalStorage = {
|
||||
getItem(key) {
|
||||
return obj[key];
|
||||
},
|
||||
setItem(key, value) {
|
||||
obj[key] = value;
|
||||
}
|
||||
};
|
||||
const originalLocalStorage = window.localStorage;
|
||||
|
||||
describe('apis/AsyncStorage', () => {
|
||||
describe('mergeLocalStorageItem', () => {
|
||||
test('should have same behavior as react-native', (done) => {
|
||||
window.localStorage = mockLocalStorage;
|
||||
// https://facebook.github.io/react-native/docs/asyncstorage.html
|
||||
const UID123_object = {
|
||||
name: 'Chris',
|
||||
age: 30,
|
||||
traits: { hair: 'brown', eyes: 'brown' }
|
||||
};
|
||||
const UID123_delta = {
|
||||
age: 31,
|
||||
traits: { eyes: 'blue', shoe_size: 10 }
|
||||
};
|
||||
|
||||
waterfall([
|
||||
(cb) => {
|
||||
AsyncStorage.setItem('UID123', JSON.stringify(UID123_object))
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
},
|
||||
(cb) => {
|
||||
AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta))
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
},
|
||||
(cb) => {
|
||||
AsyncStorage.getItem('UID123')
|
||||
.then((result) => {
|
||||
cb(null, JSON.parse(result));
|
||||
})
|
||||
.catch(cb);
|
||||
}
|
||||
], (err, result) => {
|
||||
expect(err).toEqual(null);
|
||||
expect(result).toMatchSnapshot();
|
||||
window.localStorage = originalLocalStorage;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
import merge from 'deep-assign';
|
||||
|
||||
const mergeLocalStorageItem = (key, value) => {
|
||||
const oldValue = window.localStorage.getItem(key)
|
||||
const oldObject = JSON.parse(oldValue)
|
||||
const newObject = JSON.parse(value)
|
||||
const nextValue = JSON.stringify({ ...oldObject, ...newObject })
|
||||
window.localStorage.setItem(key, nextValue)
|
||||
}
|
||||
const oldValue = window.localStorage.getItem(key);
|
||||
const oldObject = JSON.parse(oldValue);
|
||||
const newObject = JSON.parse(value);
|
||||
const nextValue = JSON.stringify(merge({}, oldObject, newObject));
|
||||
window.localStorage.setItem(key, nextValue);
|
||||
};
|
||||
|
||||
class AsyncStorage {
|
||||
/**
|
||||
@@ -19,12 +20,12 @@ class AsyncStorage {
|
||||
static clear() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.clear()
|
||||
resolve(null)
|
||||
window.localStorage.clear();
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,17 +34,17 @@ class AsyncStorage {
|
||||
static getAllKeys() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const numberOfKeys = window.localStorage.length
|
||||
const keys = []
|
||||
const numberOfKeys = window.localStorage.length;
|
||||
const keys = [];
|
||||
for (let i = 0; i < numberOfKeys; i += 1) {
|
||||
const key = window.localStorage.key(i)
|
||||
keys.push(key)
|
||||
const key = window.localStorage.key(i);
|
||||
keys.push(key);
|
||||
}
|
||||
resolve(keys)
|
||||
resolve(keys);
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,12 +53,12 @@ class AsyncStorage {
|
||||
static getItem(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const value = window.localStorage.getItem(key)
|
||||
resolve(value)
|
||||
const value = window.localStorage.getItem(key);
|
||||
resolve(value);
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,12 +67,12 @@ class AsyncStorage {
|
||||
static mergeItem(key: string, value: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
mergeLocalStorageItem(key, value)
|
||||
resolve(null)
|
||||
mergeLocalStorageItem(key, value);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,12 +82,12 @@ class AsyncStorage {
|
||||
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
|
||||
*/
|
||||
static multiGet(keys: Array<string>) {
|
||||
const promises = keys.map((key) => AsyncStorage.getItem(key))
|
||||
const promises = keys.map((key) => AsyncStorage.getItem(key));
|
||||
|
||||
return Promise.all(promises).then(
|
||||
(result) => Promise.resolve(result.map((value, i) => [ keys[i], value ])),
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,24 +97,24 @@ class AsyncStorage {
|
||||
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
static multiMerge(keyValuePairs: Array<Array<string>>) {
|
||||
const promises = keyValuePairs.map((item) => AsyncStorage.mergeItem(item[0], item[1]))
|
||||
const promises = keyValuePairs.map((item) => AsyncStorage.mergeItem(item[0], item[1]));
|
||||
|
||||
return Promise.all(promises).then(
|
||||
() => Promise.resolve(null),
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the keys in the `keys` array.
|
||||
*/
|
||||
static multiRemove(keys: Array<string>) {
|
||||
const promises = keys.map((key) => AsyncStorage.removeItem(key))
|
||||
const promises = keys.map((key) => AsyncStorage.removeItem(key));
|
||||
|
||||
return Promise.all(promises).then(
|
||||
() => Promise.resolve(null),
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,12 +122,12 @@ class AsyncStorage {
|
||||
* multiSet([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
static multiSet(keyValuePairs: Array<Array<string>>) {
|
||||
const promises = keyValuePairs.map((item) => AsyncStorage.setItem(item[0], item[1]))
|
||||
const promises = keyValuePairs.map((item) => AsyncStorage.setItem(item[0], item[1]));
|
||||
|
||||
return Promise.all(promises).then(
|
||||
() => Promise.resolve(null),
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,12 +136,12 @@ class AsyncStorage {
|
||||
static removeItem(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.removeItem(key)
|
||||
resolve(null)
|
||||
window.localStorage.removeItem(key);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,13 +150,13 @@ class AsyncStorage {
|
||||
static setItem(key: string, value: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
window.localStorage.setItem(key, value)
|
||||
resolve(null)
|
||||
window.localStorage.setItem(key, value);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AsyncStorage
|
||||
module.exports = AsyncStorage;
|
||||
|
||||
21
src/apis/Clipboard/index.js
Normal file
21
src/apis/Clipboard/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
class Clipboard {
|
||||
static getString() {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
|
||||
static setString(text) {
|
||||
let success = false;
|
||||
const textField = document.createElement('textarea');
|
||||
textField.innerText = text;
|
||||
document.body.appendChild(textField);
|
||||
textField.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
success = true;
|
||||
} catch (e) {}
|
||||
document.body.removeChild(textField);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Clipboard;
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
suite('apis/Dimensions', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
describe('apis/Dimensions', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {});
|
||||
});
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import debounce from 'lodash/debounce'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import debounce from 'debounce';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
|
||||
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} };
|
||||
|
||||
const dimensions = {}
|
||||
const dimensions = {};
|
||||
|
||||
class Dimensions {
|
||||
static get(dimension: string): Object {
|
||||
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
|
||||
return dimensions[dimension]
|
||||
invariant(dimensions[dimension], `No dimension set for key ${dimension}`);
|
||||
return dimensions[dimension];
|
||||
}
|
||||
|
||||
static set(): void {
|
||||
@@ -26,18 +26,18 @@ class Dimensions {
|
||||
height: win.innerHeight,
|
||||
scale: win.devicePixelRatio || 1,
|
||||
width: win.innerWidth
|
||||
}
|
||||
};
|
||||
|
||||
dimensions.screen = {
|
||||
fontScale: 1,
|
||||
height: win.screen.height,
|
||||
scale: win.devicePixelRatio || 1,
|
||||
width: win.screen.width
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Dimensions.set()
|
||||
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50))
|
||||
Dimensions.set();
|
||||
ExecutionEnvironment.canUseDOM && window.addEventListener('resize', debounce(Dimensions.set, 50));
|
||||
|
||||
module.exports = Dimensions
|
||||
module.exports = Dimensions;
|
||||
|
||||
45
src/apis/I18nManager/__tests__/index-test.js
Normal file
45
src/apis/I18nManager/__tests__/index-test.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import I18nManager from '..';
|
||||
|
||||
describe('apis/I18nManager', () => {
|
||||
describe('when RTL not enabled', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.setPreferredLanguageRTL(false);
|
||||
});
|
||||
|
||||
test('is "false" by default', () => {
|
||||
expect(I18nManager.isRTL).toEqual(false);
|
||||
expect(document.documentElement.getAttribute('dir')).toEqual('ltr');
|
||||
});
|
||||
|
||||
test('is "true" when forced', () => {
|
||||
I18nManager.forceRTL(true);
|
||||
expect(I18nManager.isRTL).toEqual(true);
|
||||
expect(document.documentElement.getAttribute('dir')).toEqual('rtl');
|
||||
I18nManager.forceRTL(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when RTL is enabled', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.setPreferredLanguageRTL(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
I18nManager.setPreferredLanguageRTL(false);
|
||||
});
|
||||
|
||||
test('is "true" by default', () => {
|
||||
expect(I18nManager.isRTL).toEqual(true);
|
||||
expect(document.documentElement.getAttribute('dir')).toEqual('rtl');
|
||||
});
|
||||
|
||||
test('is "false" when not allowed', () => {
|
||||
I18nManager.allowRTL(false);
|
||||
expect(I18nManager.isRTL).toEqual(false);
|
||||
expect(document.documentElement.getAttribute('dir')).toEqual('ltr');
|
||||
I18nManager.allowRTL(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
45
src/apis/I18nManager/index.js
Normal file
45
src/apis/I18nManager/index.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
|
||||
type I18nManagerStatus = {
|
||||
allowRTL: (allowRTL: boolean) => {},
|
||||
forceRTL: (forceRTL: boolean) => {},
|
||||
setRTL: (setRTL: boolean) => {},
|
||||
isRTL: boolean
|
||||
}
|
||||
|
||||
let isPreferredLanguageRTL = false;
|
||||
let isRTLAllowed = true;
|
||||
let isRTLForced = false;
|
||||
|
||||
const isRTL = () => {
|
||||
if (isRTLForced) {
|
||||
return true;
|
||||
}
|
||||
return isRTLAllowed && isPreferredLanguageRTL;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr');
|
||||
}
|
||||
};
|
||||
|
||||
const I18nManager: I18nManagerStatus = {
|
||||
allowRTL(bool) {
|
||||
isRTLAllowed = bool;
|
||||
onChange();
|
||||
},
|
||||
forceRTL(bool) {
|
||||
isRTLForced = bool;
|
||||
onChange();
|
||||
},
|
||||
setPreferredLanguageRTL(bool) {
|
||||
isPreferredLanguageRTL = bool;
|
||||
onChange();
|
||||
},
|
||||
get isRTL() {
|
||||
return isRTL();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = I18nManager;
|
||||
@@ -5,14 +5,13 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import keyMirror from 'fbjs/lib/keyMirror'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const InteractionManager = {
|
||||
Events: keyMirror({
|
||||
interactionStart: true,
|
||||
interactionComplete: true
|
||||
}),
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete'
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed.
|
||||
@@ -21,15 +20,15 @@ const InteractionManager = {
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'Must specify a function to schedule.'
|
||||
)
|
||||
callback()
|
||||
);
|
||||
callback();
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has started.
|
||||
*/
|
||||
createInteractionHandle() {
|
||||
return 1
|
||||
return 1;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -39,10 +38,10 @@ const InteractionManager = {
|
||||
invariant(
|
||||
!!handle,
|
||||
'Must provide a handle to clear.'
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
addListener: () => {}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = InteractionManager
|
||||
module.exports = InteractionManager;
|
||||
|
||||
36
src/apis/Linking/index.js
Normal file
36
src/apis/Linking/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const Linking = {
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
canOpenUrl() { return true; },
|
||||
getInitialUrl() { return ''; },
|
||||
openURL(url) {
|
||||
iframeOpen(url);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabs opened using JavaScript may redirect the parent tab using
|
||||
* `window.opener.location`, ignoring cross-origin restrictions and enabling
|
||||
* phishing attacks.
|
||||
*
|
||||
* Safari requires that we open the url by injecting a hidden iframe that calls
|
||||
* window.open(), then removes the iframe from the DOM.
|
||||
*
|
||||
* https://mathiasbynens.github.io/rel-noopener/
|
||||
*/
|
||||
const iframeOpen = (url) => {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
const script = iframeDoc.createElement('script');
|
||||
script.text = `
|
||||
window.parent = null; window.top = null; window.frameElement = null;
|
||||
var child = window.open("${url}"); child.opener = null;
|
||||
`;
|
||||
iframeDoc.body.appendChild(script);
|
||||
document.body.removeChild(iframe);
|
||||
};
|
||||
|
||||
module.exports = Linking;
|
||||
@@ -1,5 +1,32 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
suite('apis/NetInfo', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
import NetInfo from '..';
|
||||
|
||||
describe('apis/NetInfo', () => {
|
||||
describe('isConnected', () => {
|
||||
const handler = () => {};
|
||||
|
||||
afterEach(() => {
|
||||
try { NetInfo.isConnected.removeEventListener('change', handler); } catch (e) {}
|
||||
});
|
||||
|
||||
describe('addEventListener', () => {
|
||||
test('throws if the provided "eventType" is not supported', () => {
|
||||
expect(() => NetInfo.isConnected.addEventListener('foo', handler)).toThrow();
|
||||
expect(() => NetInfo.isConnected.addEventListener('change', handler)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeEventListener', () => {
|
||||
test('throws if the handler is not registered', () => {
|
||||
expect(() => NetInfo.isConnected.removeEventListener('change', handler)).toThrow;
|
||||
});
|
||||
|
||||
test('throws if the provided "eventType" is not supported', () => {
|
||||
NetInfo.isConnected.addEventListener('change', handler);
|
||||
expect(() => NetInfo.isConnected.removeEventListener('foo', handler)).toThrow;
|
||||
expect(() => NetInfo.isConnected.removeEventListener('change', handler)).not.toThrow;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,16 +6,19 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
import invariant from 'fbjs/lib/invariant'
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import findIndex from 'array-find-index';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
const connection = ExecutionEnvironment.canUseDOM && (
|
||||
window.navigator.connection ||
|
||||
window.navigator.mozConnection ||
|
||||
window.navigator.webkitConnection
|
||||
)
|
||||
);
|
||||
|
||||
const eventTypes = [ 'change' ]
|
||||
const eventTypes = [ 'change' ];
|
||||
|
||||
const connectionListeners = [];
|
||||
|
||||
/**
|
||||
* Navigator online: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine
|
||||
@@ -23,63 +26,74 @@ const eventTypes = [ 'change' ]
|
||||
*/
|
||||
const NetInfo = {
|
||||
addEventListener(type: string, handler: Function): { remove: () => void } {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
|
||||
if (!connection) {
|
||||
console.error('Network Connection API is not supported. Not listening for connection type changes.')
|
||||
console.error('Network Connection API is not supported. Not listening for connection type changes.');
|
||||
return {
|
||||
remove: () => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
connection.addEventListener(type, handler)
|
||||
connection.addEventListener(type, handler);
|
||||
return {
|
||||
remove: () => NetInfo.removeEventListener(type, handler)
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
removeEventListener(type: string, handler: Function): void {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
if (!connection) { return }
|
||||
connection.removeEventListener(type, handler)
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
|
||||
if (!connection) { return; }
|
||||
connection.removeEventListener(type, handler);
|
||||
},
|
||||
|
||||
fetch(): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(connection.type)
|
||||
resolve(connection.type);
|
||||
} catch (err) {
|
||||
resolve('unknown')
|
||||
resolve('unknown');
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
isConnected: {
|
||||
addEventListener(type: string, handler: Function): { remove: () => void } {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
window.addEventListener('online', handler.bind(null, true), false)
|
||||
window.addEventListener('offline', handler.bind(null, false), false)
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
|
||||
const onlineCallback = () => handler(true);
|
||||
const offlineCallback = () => handler(false);
|
||||
connectionListeners.push([ handler, onlineCallback, offlineCallback ]);
|
||||
|
||||
window.addEventListener('online', onlineCallback, false);
|
||||
window.addEventListener('offline', offlineCallback, false);
|
||||
|
||||
return {
|
||||
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
removeEventListener(type: string, handler: Function): void {
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
|
||||
window.removeEventListener('online', handler.bind(null, true), false)
|
||||
window.removeEventListener('offline', handler.bind(null, false), false)
|
||||
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
|
||||
|
||||
const listenerIndex = findIndex(connectionListeners, (pair) => pair[0] === handler);
|
||||
invariant(listenerIndex !== -1, 'Trying to remove NetInfo connection listener for unregistered handler');
|
||||
const [ , onlineCallback, offlineCallback ] = connectionListeners[listenerIndex];
|
||||
|
||||
window.removeEventListener('online', onlineCallback, false);
|
||||
window.removeEventListener('offline', offlineCallback, false);
|
||||
|
||||
connectionListeners.splice(listenerIndex, 1);
|
||||
},
|
||||
|
||||
fetch(): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(window.navigator.onLine)
|
||||
resolve(window.navigator.onLine);
|
||||
} catch (err) {
|
||||
resolve(true)
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = NetInfo
|
||||
module.exports = NetInfo;
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = {
|
||||
/**
|
||||
* This code is optimized and not intended to look beautiful. This allows
|
||||
* computing of touch centroids that have moved after `touchesChangedAfter`
|
||||
* timeStamp. You can compute the current centroid involving all touches
|
||||
* moves after `touchesChangedAfter`, or you can compute the previous
|
||||
* centroid of all touches that were moved after `touchesChangedAfter`.
|
||||
*
|
||||
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
|
||||
* data.
|
||||
* @param {number} touchesChangedAfter timeStamp after which moved touches
|
||||
* are considered "actively moving" - not just "active".
|
||||
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
|
||||
* @param {boolean} ofCurrent Compute current centroid for actively moving
|
||||
* touches vs. previous centroid of now actively moving touches.
|
||||
* @return {number} value of centroid in specified dimension.
|
||||
*/
|
||||
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var total = 0;
|
||||
var count = 0;
|
||||
|
||||
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
|
||||
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
|
||||
|
||||
if (oneTouchData !== null) {
|
||||
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
|
||||
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
|
||||
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
|
||||
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
|
||||
oneTouchData.previousPageY;
|
||||
count = 1;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < touchBank.length; i++) {
|
||||
var touchTrack = touchBank[i];
|
||||
if (touchTrack !== null &&
|
||||
touchTrack !== undefined &&
|
||||
touchTrack.touchActive &&
|
||||
touchTrack.currentTimeStamp >= touchesChangedAfter) {
|
||||
var toAdd; // Yuck, program temporarily in invalid state.
|
||||
if (ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.currentPageX;
|
||||
} else if (ofCurrent && !isXAxis) {
|
||||
toAdd = touchTrack.currentPageY;
|
||||
} else if (!ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.previousPageX;
|
||||
} else {
|
||||
toAdd = touchTrack.previousPageY;
|
||||
}
|
||||
total += toAdd;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
|
||||
},
|
||||
|
||||
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidX: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidY: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
noCentroid: -1,
|
||||
};
|
||||
|
||||
module.exports = TouchHistoryMath;
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
import normalizeNativeEvent from '../../modules/normalizeNativeEvent';
|
||||
var TouchHistoryMath = require('./TouchHistoryMath');
|
||||
var TouchHistoryMath = require('react/lib/TouchHistoryMath');
|
||||
|
||||
var currentCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
@@ -288,11 +287,11 @@ var PanResponder = {
|
||||
var panHandlers = {
|
||||
onStartShouldSetResponder: function(e) {
|
||||
return config.onStartShouldSetPanResponder === undefined ? false :
|
||||
config.onStartShouldSetPanResponder(normalizeEvent(e), gestureState);
|
||||
config.onStartShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onMoveShouldSetResponder: function(e) {
|
||||
return config.onMoveShouldSetPanResponder === undefined ? false :
|
||||
config.onMoveShouldSetPanResponder(normalizeEvent(e), gestureState);
|
||||
config.onMoveShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onStartShouldSetResponderCapture: function(e) {
|
||||
// TODO: Actually, we should reinitialize the state any time
|
||||
@@ -302,12 +301,12 @@ var PanResponder = {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
}
|
||||
else if (e.nativeEvent.type === 'mousedown') {
|
||||
else if (e.nativeEvent.originalEvent && e.nativeEvent.originalEvent.type === 'mousedown') {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
||||
return config.onStartShouldSetPanResponderCapture !== undefined ?
|
||||
config.onStartShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false;
|
||||
config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onMoveShouldSetResponderCapture: function(e) {
|
||||
@@ -320,7 +319,7 @@ var PanResponder = {
|
||||
}
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
return config.onMoveShouldSetPanResponderCapture ?
|
||||
config.onMoveShouldSetPanResponderCapture(normalizeEvent(e), gestureState) : false;
|
||||
config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onResponderGrant: function(e) {
|
||||
@@ -328,25 +327,25 @@ var PanResponder = {
|
||||
gestureState.y0 = currentCentroidY(e.touchHistory);
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
config.onPanResponderGrant && config.onPanResponderGrant(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
|
||||
// TODO: t7467124 investigate if this can be removed
|
||||
return config.onShouldBlockNativeResponder === undefined ? true :
|
||||
config.onShouldBlockNativeResponder();
|
||||
},
|
||||
|
||||
onResponderReject: function(e) {
|
||||
config.onPanResponderReject && config.onPanResponderReject(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderRelease: function(e) {
|
||||
config.onPanResponderRelease && config.onPanResponderRelease(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderStart: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderStart && config.onPanResponderStart(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderMove: function(e) {
|
||||
@@ -359,13 +358,13 @@ var PanResponder = {
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
config.onPanResponderMove && config.onPanResponderMove(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderEnd: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderEnd && config.onPanResponderEnd(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminate: function(e) {
|
||||
@@ -376,17 +375,11 @@ var PanResponder = {
|
||||
|
||||
onResponderTerminationRequest: function(e) {
|
||||
return config.onPanResponderTerminationRequest === undefined ? true :
|
||||
config.onPanResponderTerminationRequest(normalizeEvent(e), gestureState);
|
||||
config.onPanResponderTerminationRequest(e, gestureState);
|
||||
},
|
||||
};
|
||||
return {panHandlers: panHandlers};
|
||||
},
|
||||
};
|
||||
|
||||
function normalizeEvent(e) {
|
||||
const normalizedEvent = Object.create(e);
|
||||
normalizedEvent.nativeEvent = normalizeNativeEvent(e.nativeEvent, e.type);
|
||||
return normalizedEvent;
|
||||
}
|
||||
|
||||
module.exports = PanResponder;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
suite('apis/PixelRatio', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
describe('apis/PixelRatio', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Dimensions from '../Dimensions'
|
||||
import Dimensions from '../Dimensions';
|
||||
|
||||
/**
|
||||
* PixelRatio gives access to the device pixel density.
|
||||
@@ -16,14 +16,14 @@ class PixelRatio {
|
||||
* Returns the device pixel density.
|
||||
*/
|
||||
static get(): number {
|
||||
return Dimensions.get('window').scale
|
||||
return Dimensions.get('window').scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* No equivalent for Web
|
||||
*/
|
||||
static getFontScale(): number {
|
||||
return Dimensions.get('window').fontScale || PixelRatio.get()
|
||||
return Dimensions.get('window').fontScale || PixelRatio.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ class PixelRatio {
|
||||
* Guaranteed to return an integer number.
|
||||
*/
|
||||
static getPixelSizeForLayoutSize(layoutSize: number): number {
|
||||
return Math.round(layoutSize * PixelRatio.get())
|
||||
return Math.round(layoutSize * PixelRatio.get());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,9 +41,9 @@ class PixelRatio {
|
||||
* exactly (8.33 * 3) = 25 pixels.
|
||||
*/
|
||||
static roundToNearestPixel(layoutSize: number): number {
|
||||
const ratio = PixelRatio.get()
|
||||
return Math.round(layoutSize * ratio) / ratio
|
||||
const ratio = PixelRatio.get();
|
||||
return Math.round(layoutSize * ratio) / ratio;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PixelRatio
|
||||
module.exports = PixelRatio;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const Platform = {
|
||||
OS: 'web',
|
||||
select: (obj: Object) => obj.web
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Platform
|
||||
module.exports = Platform;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
@@ -8,6 +9,8 @@
|
||||
|
||||
import { PropTypes } from 'react'
|
||||
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
|
||||
import ReactPropTypeLocations from 'react/lib/ReactPropTypeLocations'
|
||||
import ReactPropTypesSecret from 'react/lib/ReactPropTypesSecret'
|
||||
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
|
||||
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
|
||||
import warning from 'fbjs/lib/warning'
|
||||
@@ -16,13 +19,21 @@ class StyleSheetValidation {
|
||||
static validateStyleProp(prop, style, caller) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (allStylePropTypes[prop] === undefined) {
|
||||
const message1 = `"${prop}" is not a valid style property.`
|
||||
const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
|
||||
styleError(message1, style, caller, message2)
|
||||
var message1 = '"' + prop + '" is not a valid style property.';
|
||||
var message2 = '\nValid style props: ' +
|
||||
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ');
|
||||
styleError(message1, style, caller, message2);
|
||||
} else {
|
||||
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
|
||||
var error = allStylePropTypes[prop](
|
||||
style,
|
||||
prop,
|
||||
caller,
|
||||
ReactPropTypeLocations.prop,
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
);
|
||||
if (error) {
|
||||
styleError(error.message, style, caller)
|
||||
styleError(error.message, style, caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,28 +41,28 @@ class StyleSheetValidation {
|
||||
|
||||
static validateStyle(name, styles) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
for (const prop in styles[name]) {
|
||||
StyleSheetValidation.validateStyleProp(prop, styles[name], 'StyleSheet ' + name)
|
||||
for (var prop in styles[name]) {
|
||||
StyleSheetValidation.validateStyleProp(prop, styles[name], 'StyleSheet ' + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static addValidStylePropTypes(stylePropTypes) {
|
||||
for (const key in stylePropTypes) {
|
||||
allStylePropTypes[key] = stylePropTypes[key]
|
||||
for (var key in stylePropTypes) {
|
||||
allStylePropTypes[key] = stylePropTypes[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styleError = (message1, style, caller, message2) => {
|
||||
var styleError = function(message1, style, caller?, message2?) {
|
||||
warning(
|
||||
false,
|
||||
message1 + '\n' + (caller || '<<unknown>>') + ': ' +
|
||||
JSON.stringify(style, null, ' ') + (message2 || '')
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const allStylePropTypes = {}
|
||||
var allStylePropTypes = {};
|
||||
|
||||
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
|
||||
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
|
||||
@@ -61,10 +72,10 @@ StyleSheetValidation.addValidStylePropTypes({
|
||||
clear: PropTypes.string,
|
||||
cursor: PropTypes.string,
|
||||
display: PropTypes.string,
|
||||
direction: PropTypes.string, /* @private */
|
||||
float: PropTypes.oneOf([ 'left', 'none', 'right' ]),
|
||||
font: PropTypes.string, /* @private */
|
||||
listStyle: PropTypes.string
|
||||
listStyle: PropTypes.string,
|
||||
WebkitOverflowScrolling: PropTypes.string /* @private */
|
||||
})
|
||||
|
||||
module.exports = StyleSheetValidation
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
exports[`apis/StyleSheet/expandStyle shortform -> longform 1`] = `
|
||||
Object {
|
||||
"borderBottomColor": "white",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomWidth": "1px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopWidth": "0px",
|
||||
"boxSizing": "border-box",
|
||||
"marginBottom": "25px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "10px",
|
||||
"marginTop": "50px",
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,107 @@
|
||||
exports[`apis/StyleSheet/i18nStyle LTR mode does not auto-flip 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": 20,
|
||||
"borderBottomRightRadius": "2rem",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": 5,
|
||||
"borderRightColor": "blue",
|
||||
"borderRightStyle": "dotted",
|
||||
"borderRightWidth": 6,
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": "1rem",
|
||||
"left": 1,
|
||||
"marginLeft": 7,
|
||||
"marginRight": 8,
|
||||
"paddingLeft": 9,
|
||||
"paddingRight": 10,
|
||||
"right": 2,
|
||||
"textAlign": "left",
|
||||
"textShadowOffset": Object {
|
||||
"height": 10,
|
||||
"width": "1rem",
|
||||
},
|
||||
"writingDirection": "ltr",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/i18nStyle LTR mode normalizes properties 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": 20,
|
||||
"borderBottomRightRadius": "2rem",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": 5,
|
||||
"borderRightColor": "blue",
|
||||
"borderRightStyle": "dotted",
|
||||
"borderRightWidth": 6,
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": "1rem",
|
||||
"left": 1,
|
||||
"marginLeft": 7,
|
||||
"marginRight": 8,
|
||||
"paddingLeft": 9,
|
||||
"paddingRight": 10,
|
||||
"right": 2,
|
||||
"textAlign": "left",
|
||||
"textShadowOffset": Object {
|
||||
"height": 10,
|
||||
"width": "1rem",
|
||||
},
|
||||
"writingDirection": "ltr",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/i18nStyle RTL mode does auto-flip 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": "2rem",
|
||||
"borderBottomRightRadius": 20,
|
||||
"borderLeftColor": "blue",
|
||||
"borderLeftStyle": "dotted",
|
||||
"borderLeftWidth": 6,
|
||||
"borderRightColor": "red",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightWidth": 5,
|
||||
"borderTopLeftRadius": "1rem",
|
||||
"borderTopRightRadius": 10,
|
||||
"left": 2,
|
||||
"marginLeft": 8,
|
||||
"marginRight": 7,
|
||||
"paddingLeft": 10,
|
||||
"paddingRight": 9,
|
||||
"right": 1,
|
||||
"textAlign": "right",
|
||||
"textShadowOffset": Object {
|
||||
"height": 10,
|
||||
"width": "-1rem",
|
||||
},
|
||||
"writingDirection": "rtl",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`apis/StyleSheet/i18nStyle RTL mode normalizes properties 1`] = `
|
||||
Object {
|
||||
"borderBottomLeftRadius": 20,
|
||||
"borderBottomRightRadius": "2rem",
|
||||
"borderLeftColor": "red",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftWidth": 5,
|
||||
"borderRightColor": "blue",
|
||||
"borderRightStyle": "dotted",
|
||||
"borderRightWidth": 6,
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": "1rem",
|
||||
"left": 1,
|
||||
"marginLeft": 7,
|
||||
"marginRight": 8,
|
||||
"paddingLeft": 9,
|
||||
"paddingRight": 10,
|
||||
"right": 2,
|
||||
"textAlign": "left",
|
||||
"textShadowOffset": Object {
|
||||
"height": 10,
|
||||
"width": "-1rem",
|
||||
},
|
||||
"writingDirection": "ltr",
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,10 @@
|
||||
exports[`apis/StyleSheet resolve 1`] = `
|
||||
Object {
|
||||
"className": "test __style_df __style_pebn",
|
||||
"style": Object {
|
||||
"display": null,
|
||||
"opacity": 1,
|
||||
"pointerEvents": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
@@ -1,13 +1,12 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import assert from 'assert'
|
||||
import createReactStyleObject from '../createReactStyleObject'
|
||||
import createReactStyleObject from '../createReactStyleObject';
|
||||
|
||||
suite('apis/StyleSheet/createReactStyleObject', () => {
|
||||
describe('apis/StyleSheet/createReactStyleObject', () => {
|
||||
test('converts ReactNative style to ReactDOM style', () => {
|
||||
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 }
|
||||
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 }
|
||||
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 };
|
||||
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 };
|
||||
|
||||
assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle)
|
||||
})
|
||||
})
|
||||
expect(createReactStyleObject(reactNativeStyle)).toEqual(expectedStyle);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import assert from 'assert'
|
||||
import expandStyle from '../expandStyle'
|
||||
import expandStyle from '../expandStyle';
|
||||
|
||||
suite('apis/StyleSheet/expandStyle', () => {
|
||||
describe('apis/StyleSheet/expandStyle', () => {
|
||||
test('shortform -> longform', () => {
|
||||
const initial = {
|
||||
const style = {
|
||||
borderStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
borderBottomColor: 'white',
|
||||
@@ -14,53 +13,36 @@ suite('apis/StyleSheet/expandStyle', () => {
|
||||
marginTop: 50,
|
||||
marginVertical: 25,
|
||||
margin: 10
|
||||
}
|
||||
};
|
||||
|
||||
const expected = {
|
||||
borderBottomStyle: 'solid',
|
||||
borderLeftStyle: 'solid',
|
||||
borderRightStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
borderBottomColor: 'white',
|
||||
borderTopStyle: 'solid',
|
||||
borderTopWidth: '0px',
|
||||
borderLeftWidth: '0px',
|
||||
borderRightWidth: '0px',
|
||||
borderBottomWidth: '1px',
|
||||
marginTop: '50px',
|
||||
marginBottom: '25px',
|
||||
marginLeft: '10px',
|
||||
marginRight: '10px'
|
||||
}
|
||||
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
expect(expandStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('textAlignVertical', () => {
|
||||
const initial = {
|
||||
textAlignVertical: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
const expected = {
|
||||
verticalAlign: 'middle'
|
||||
}
|
||||
};
|
||||
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
expect(expandStyle(initial)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('flex', () => {
|
||||
const value = 10
|
||||
const value = 10;
|
||||
|
||||
const initial = {
|
||||
flex: value
|
||||
}
|
||||
};
|
||||
|
||||
const expected = {
|
||||
flexGrow: value,
|
||||
flexShrink: 1,
|
||||
flexBasis: 'auto'
|
||||
}
|
||||
};
|
||||
|
||||
assert.deepEqual(expandStyle(initial), expected)
|
||||
})
|
||||
})
|
||||
expect(expandStyle(initial)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
68
src/apis/StyleSheet/__tests__/i18nStyle-test.js
Normal file
68
src/apis/StyleSheet/__tests__/i18nStyle-test.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import I18nManager from '../../I18nManager';
|
||||
import i18nStyle from '../i18nStyle';
|
||||
|
||||
const style = {
|
||||
borderLeftColor: 'red',
|
||||
borderRightColor: 'blue',
|
||||
borderTopLeftRadius: 10,
|
||||
borderTopRightRadius: '1rem',
|
||||
borderBottomLeftRadius: 20,
|
||||
borderBottomRightRadius: '2rem',
|
||||
borderLeftStyle: 'solid',
|
||||
borderRightStyle: 'dotted',
|
||||
borderLeftWidth: 5,
|
||||
borderRightWidth: 6,
|
||||
left: 1,
|
||||
marginLeft: 7,
|
||||
marginRight: 8,
|
||||
paddingLeft: 9,
|
||||
paddingRight: 10,
|
||||
right: 2,
|
||||
textAlign: 'left',
|
||||
textShadowOffset: { width: '1rem', height: 10 },
|
||||
writingDirection: 'ltr'
|
||||
};
|
||||
|
||||
const styleNoI18n = Object.keys(style).reduce((acc, prop) => {
|
||||
const newProp = `${prop}$noI18n`;
|
||||
acc[newProp] = style[prop];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
describe('apis/StyleSheet/i18nStyle', () => {
|
||||
describe('LTR mode', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.allowRTL(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
I18nManager.allowRTL(true);
|
||||
});
|
||||
|
||||
test('does not auto-flip', () => {
|
||||
expect(i18nStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
test('normalizes properties', () => {
|
||||
expect(i18nStyle(styleNoI18n)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL mode', () => {
|
||||
beforeEach(() => {
|
||||
I18nManager.forceRTL(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
I18nManager.forceRTL(false);
|
||||
});
|
||||
|
||||
test('does auto-flip', () => {
|
||||
expect(i18nStyle(style)).toMatchSnapshot();
|
||||
});
|
||||
test('normalizes properties', () => {
|
||||
expect(i18nStyle(styleNoI18n)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,71 +1,64 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import assert from 'assert'
|
||||
import { defaultStyles } from '../predefs'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import StyleSheet from '..'
|
||||
import { getDefaultStyleSheet } from '../css';
|
||||
import StyleSheet from '..';
|
||||
|
||||
suite('apis/StyleSheet', () => {
|
||||
setup(() => {
|
||||
StyleSheet._reset()
|
||||
})
|
||||
const isPlainObject = (x) => {
|
||||
const toString = Object.prototype.toString;
|
||||
let proto;
|
||||
/* eslint-disable */
|
||||
return (
|
||||
toString.call(x) === '[object Object]' &&
|
||||
(proto = Object.getPrototypeOf(x), proto === null || proto === Object.getPrototypeOf({}))
|
||||
);
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
describe('apis/StyleSheet', () => {
|
||||
beforeEach(() => {
|
||||
StyleSheet._reset();
|
||||
});
|
||||
|
||||
test('absoluteFill', () => {
|
||||
assert(Number.isInteger(StyleSheet.absoluteFill) === true)
|
||||
})
|
||||
expect(Number.isInteger(StyleSheet.absoluteFill) === true).toBeTruthy();
|
||||
});
|
||||
|
||||
test('absoluteFillObject', () => {
|
||||
assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true)
|
||||
})
|
||||
expect(isPlainObject(StyleSheet.absoluteFillObject) === true).toBeTruthy();
|
||||
});
|
||||
|
||||
suite('create', () => {
|
||||
describe('create', () => {
|
||||
test('replaces styles with numbers', () => {
|
||||
const style = StyleSheet.create({ root: { opacity: 1 } })
|
||||
assert(Number.isInteger(style.root) === true)
|
||||
})
|
||||
const style = StyleSheet.create({ root: { opacity: 1 } });
|
||||
expect(Number.isInteger(style.root) === true).toBeTruthy();
|
||||
});
|
||||
|
||||
test('renders a style sheet in the browser', () => {
|
||||
StyleSheet.create({ root: { color: 'red' } })
|
||||
assert.equal(
|
||||
document.getElementById('__react-native-style').textContent,
|
||||
defaultStyles
|
||||
)
|
||||
})
|
||||
})
|
||||
StyleSheet.create({ root: { color: 'red' } });
|
||||
expect(document.getElementById('react-native-style__').textContent).toEqual(getDefaultStyleSheet());
|
||||
});
|
||||
});
|
||||
|
||||
test('flatten', () => {
|
||||
assert(typeof StyleSheet.flatten === 'function')
|
||||
})
|
||||
expect(typeof StyleSheet.flatten === 'function').toBeTruthy();
|
||||
});
|
||||
|
||||
test('hairlineWidth', () => {
|
||||
assert(Number.isInteger(StyleSheet.hairlineWidth) === true)
|
||||
})
|
||||
expect(Number.isInteger(StyleSheet.hairlineWidth) === true).toBeTruthy();
|
||||
});
|
||||
|
||||
test('render', () => {
|
||||
assert.equal(
|
||||
StyleSheet.render().props.dangerouslySetInnerHTML.__html,
|
||||
defaultStyles
|
||||
)
|
||||
})
|
||||
expect(StyleSheet.render().props.dangerouslySetInnerHTML.__html).toEqual(getDefaultStyleSheet());
|
||||
});
|
||||
|
||||
test('resolve', () => {
|
||||
assert.deepEqual(
|
||||
StyleSheet.resolve({
|
||||
className: 'test',
|
||||
style: {
|
||||
display: 'flex',
|
||||
opacity: 1,
|
||||
pointerEvents: 'box-none'
|
||||
}
|
||||
}),
|
||||
{
|
||||
className: 'test __style_df __style_pebn',
|
||||
style: {
|
||||
display: 'flex',
|
||||
opacity: 1,
|
||||
pointerEvents: 'box-none'
|
||||
}
|
||||
expect(StyleSheet.resolve({
|
||||
className: 'test',
|
||||
style: {
|
||||
display: 'flex',
|
||||
opacity: 1,
|
||||
pointerEvents: 'box-none'
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import assert from 'assert'
|
||||
import normalizeValue from '../normalizeValue'
|
||||
import normalizeValue from '../normalizeValue';
|
||||
|
||||
suite('apis/StyleSheet/normalizeValue', () => {
|
||||
describe('apis/StyleSheet/normalizeValue', () => {
|
||||
test('normalizes property values requiring units', () => {
|
||||
assert.deepEqual(normalizeValue('margin', 0), '0px')
|
||||
})
|
||||
expect(normalizeValue('margin', 0)).toEqual('0px');
|
||||
});
|
||||
test('ignores unitless property values', () => {
|
||||
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
|
||||
assert.deepEqual(normalizeValue('scale', 2), 2)
|
||||
})
|
||||
})
|
||||
expect(normalizeValue('flexGrow', 1)).toEqual(1);
|
||||
expect(normalizeValue('scale', 2)).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
20
src/apis/StyleSheet/__tests__/processTextShadow-test.js
Normal file
20
src/apis/StyleSheet/__tests__/processTextShadow-test.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import processTextShadow from '../processTextShadow';
|
||||
|
||||
describe('apis/StyleSheet/processTextShadow', () => {
|
||||
test('textShadowOffset', () => {
|
||||
const style = {
|
||||
textShadowColor: 'red',
|
||||
textShadowOffset: { width: 2, height: 2 },
|
||||
textShadowRadius: 5
|
||||
};
|
||||
|
||||
expect(processTextShadow(style)).toEqual({
|
||||
textShadow: '2px 2px 5px red',
|
||||
textShadowColor: null,
|
||||
textShadowOffset: null,
|
||||
textShadowRadius: null
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-env mocha */
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import assert from 'assert'
|
||||
import processTransform from '../processTransform'
|
||||
import processTransform from '../processTransform';
|
||||
|
||||
suite('apis/StyleSheet/processTransform', () => {
|
||||
describe('apis/StyleSheet/processTransform', () => {
|
||||
test('transform', () => {
|
||||
const style = {
|
||||
transform: [
|
||||
@@ -11,22 +10,19 @@ suite('apis/StyleSheet/processTransform', () => {
|
||||
{ translateX: 20 },
|
||||
{ rotate: '20deg' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
processTransform(style),
|
||||
{ transform: 'scaleX(20) translateX(20px) rotate(20deg)' }
|
||||
)
|
||||
})
|
||||
expect(processTransform(style)).toEqual({ transform: 'scaleX(20) translateX(20px) rotate(20deg)' });
|
||||
});
|
||||
|
||||
test('transformMatrix', () => {
|
||||
const style = {
|
||||
transformMatrix: [ 1, 2, 3, 4, 5, 6 ]
|
||||
}
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
processTransform(style),
|
||||
{ transform: 'matrix3d(1,2,3,4,5,6)' }
|
||||
)
|
||||
})
|
||||
})
|
||||
expect(processTransform(style)).toEqual({
|
||||
transform: 'matrix3d(1,2,3,4,5,6)',
|
||||
transformMatrix: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
13
src/apis/StyleSheet/__tests__/processVendorPrefixes-test.js
Normal file
13
src/apis/StyleSheet/__tests__/processVendorPrefixes-test.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import processVendorPrefixes from '../processVendorPrefixes';
|
||||
|
||||
describe('apis/StyleSheet/processVendorPrefixes', () => {
|
||||
test('handles array values', () => {
|
||||
const style = {
|
||||
display: [ '-webkit-flex', 'flex' ]
|
||||
};
|
||||
|
||||
expect(processVendorPrefixes(style)).toEqual({ display: 'flex' });
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,20 @@
|
||||
import expandStyle from './expandStyle'
|
||||
import flattenStyle from '../../modules/flattenStyle'
|
||||
import prefixAll from 'inline-style-prefixer/static'
|
||||
import processTransform from './processTransform'
|
||||
import expandStyle from './expandStyle';
|
||||
import flattenStyle from '../../modules/flattenStyle';
|
||||
import i18nStyle from './i18nStyle';
|
||||
import processTextShadow from './processTextShadow';
|
||||
import processTransform from './processTransform';
|
||||
import processVendorPrefixes from './processVendorPrefixes';
|
||||
|
||||
const addVendorPrefixes = (style) => {
|
||||
let prefixedStyles = prefixAll(style)
|
||||
// React@15 removed undocumented support for fallback values in
|
||||
// inline-styles. Revert array values to the standard CSS value
|
||||
for (const prop in prefixedStyles) {
|
||||
const value = prefixedStyles[prop]
|
||||
if (Array.isArray(value)) {
|
||||
prefixedStyles[prop] = value[value.length - 1]
|
||||
}
|
||||
}
|
||||
return prefixedStyles
|
||||
}
|
||||
const processors = [
|
||||
processTextShadow,
|
||||
processTransform,
|
||||
processVendorPrefixes
|
||||
];
|
||||
|
||||
const _createReactDOMStyleObject = (reactNativeStyle) => processTransform(expandStyle(flattenStyle(reactNativeStyle)))
|
||||
const createReactDOMStyleObject = (reactNativeStyle) => addVendorPrefixes(_createReactDOMStyleObject(reactNativeStyle))
|
||||
const applyProcessors = (style) => processors.reduce((style, processor) => processor(style), style);
|
||||
|
||||
module.exports = createReactDOMStyleObject
|
||||
const createReactDOMStyleObject = (reactNativeStyle) => applyProcessors(
|
||||
expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))
|
||||
);
|
||||
|
||||
module.exports = createReactDOMStyleObject;
|
||||
|
||||
42
src/apis/StyleSheet/css.js
Normal file
42
src/apis/StyleSheet/css.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const DISPLAY_FLEX_CLASSNAME = '__style_df';
|
||||
const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea';
|
||||
const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn';
|
||||
const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo';
|
||||
const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const CSS_RESET =
|
||||
// reset unwanted styles
|
||||
'/* React Native */\n' +
|
||||
'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
|
||||
'body{margin:0}\n' +
|
||||
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
|
||||
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
|
||||
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
|
||||
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}';
|
||||
|
||||
const CSS_HELPERS =
|
||||
// vendor prefix 'display:flex' until React supports fallback values for inline styles
|
||||
`.${DISPLAY_FLEX_CLASSNAME} {display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}\n` +
|
||||
// implement React Native's pointer event values
|
||||
`.${POINTER_EVENTS_AUTO_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME}, .${POINTER_EVENTS_BOX_NONE_CLASSNAME} * {pointer-events:auto}\n` +
|
||||
`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const styleAsClassName = {
|
||||
display: {
|
||||
'flex': DISPLAY_FLEX_CLASSNAME
|
||||
},
|
||||
pointerEvents: {
|
||||
'auto': POINTER_EVENTS_AUTO_CLASSNAME,
|
||||
'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME,
|
||||
'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME,
|
||||
'none': POINTER_EVENTS_NONE_CLASSNAME
|
||||
}
|
||||
};
|
||||
|
||||
export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`;
|
||||
|
||||
export const getStyleAsHelperClassName = (prop, value) => {
|
||||
return styleAsClassName[prop] && styleAsClassName[prop][value];
|
||||
};
|
||||
@@ -9,9 +9,9 @@
|
||||
* longfrom equivalents.
|
||||
*/
|
||||
|
||||
import normalizeValue from './normalizeValue'
|
||||
import normalizeValue from './normalizeValue';
|
||||
|
||||
const emptyObject = {}
|
||||
const emptyObject = {};
|
||||
const styleShortFormProperties = {
|
||||
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
|
||||
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
|
||||
@@ -26,48 +26,48 @@ const styleShortFormProperties = {
|
||||
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
|
||||
textDecorationLine: [ 'textDecoration' ],
|
||||
writingDirection: [ 'direction' ]
|
||||
}
|
||||
};
|
||||
|
||||
const alphaSort = (arr) => arr.sort((a, b) => {
|
||||
if (a < b) { return -1 }
|
||||
if (a > b) { return 1 }
|
||||
return 0
|
||||
})
|
||||
if (a < b) { return -1; }
|
||||
if (a > b) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
const createStyleReducer = (originalStyle) => {
|
||||
const originalStyleProps = Object.keys(originalStyle)
|
||||
const originalStyleProps = Object.keys(originalStyle);
|
||||
|
||||
return (style, prop) => {
|
||||
const value = normalizeValue(prop, originalStyle[prop])
|
||||
const longFormProperties = styleShortFormProperties[prop]
|
||||
const value = normalizeValue(prop, originalStyle[prop]);
|
||||
const longFormProperties = styleShortFormProperties[prop];
|
||||
|
||||
// React Native treats `flex:1` like `flex:1 1 auto`
|
||||
if (prop === 'flex') {
|
||||
style.flexGrow = value
|
||||
style.flexShrink = 1
|
||||
style.flexBasis = 'auto'
|
||||
style.flexGrow = value;
|
||||
style.flexShrink = 1;
|
||||
style.flexBasis = 'auto';
|
||||
// React Native accepts 'center' as a value
|
||||
} else if (prop === 'textAlignVertical') {
|
||||
style.verticalAlign = (value === 'center' ? 'middle' : value)
|
||||
style.verticalAlign = (value === 'center' ? 'middle' : value);
|
||||
} else if (longFormProperties) {
|
||||
longFormProperties.forEach((longForm, i) => {
|
||||
// the value of any longform property in the original styles takes
|
||||
// precedence over the shortform's value
|
||||
if (originalStyleProps.indexOf(longForm) === -1) {
|
||||
style[longForm] = value
|
||||
style[longForm] = value;
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
style[prop] = value
|
||||
style[prop] = value;
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
return style;
|
||||
};
|
||||
};
|
||||
|
||||
const expandStyle = (style = emptyObject) => {
|
||||
const sortedStyleProps = alphaSort(Object.keys(style))
|
||||
const styleReducer = createStyleReducer(style)
|
||||
return sortedStyleProps.reduce(styleReducer, {})
|
||||
}
|
||||
const sortedStyleProps = alphaSort(Object.keys(style));
|
||||
const styleReducer = createStyleReducer(style);
|
||||
return sortedStyleProps.reduce(styleReducer, {});
|
||||
};
|
||||
|
||||
module.exports = expandStyle
|
||||
module.exports = expandStyle;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user