Compare commits

..

129 Commits

Author SHA1 Message Date
Nicolas Gallagher
7cda89c5ce 0.0.60 2016-12-16 12:15:23 +00:00
Nicolas Gallagher
695eba45af [add] Clipboard API
Close #125
Fix #122
2016-12-16 11:59:22 +00:00
Nicolas Gallagher
92a2cb274a [fix] remove TextInput default flex value 2016-12-16 11:39:12 +00:00
Nicolas Gallagher
b1ca04d11e Rename I18nManager example 2016-12-16 11:35:11 +00:00
Nicolas Gallagher
22ab70ea6f 0.0.59 2016-12-14 17:42:15 +00:00
Gethin Webster
49f36d8eb1 Update to ListView functionality
Re-build ListView from the core react-native component, to get better
feature parity

Ensure lists with small initialListSize render correctly

Changes as requested via PR
2016-12-14 09:39:05 -08:00
Nicolas Gallagher
80ba119b83 Update install command
Close #285
Close #286
2016-12-14 17:05:15 +00:00
Nicolas Gallagher
c30b67f6db 0.0.57 2016-12-12 15:10:39 +00:00
Nicolas Gallagher
4580f93199 Use fbjs requestAnimationFrame in Image 2016-12-12 14:26:07 +00:00
Nicolas Gallagher
4c46126ffe [change] ScrollView event normalization 2016-12-12 14:21:33 +00:00
Calvin Chan
f8d5c15405 [add] Button component 2016-12-12 13:02:16 +00:00
Nicolas Gallagher
dc54e03625 [add] Linking API
Adds support for opening external URLs in a new tab/window. Includes
patches to 'Text' to improve accessibility and 'createDOMElement' to
improve external link security.

Fix #198
2016-12-12 11:45:30 +00:00
Nicolas Gallagher
4d5819ae28 [fix] RTL translateX; Switch transition 2016-12-08 19:40:34 -08:00
Nicolas Gallagher
5c482ef3be 0.0.56 2016-12-08 18:29:38 -08:00
Nicolas Gallagher
f51592f96e [change] TouchableOpacity without Animated
Fix #259
2016-12-08 18:22:25 -08:00
Nicolas Gallagher
6bffe1775f Fix lint error 2016-12-07 16:49:53 -08:00
Nicolas Gallagher
7e75d037f2 [fix] Image passes unknown props to underlying View
Fix #267
2016-12-07 16:37:24 -08:00
Nicolas Gallagher
7536508fe3 Update docs 2016-12-07 16:22:39 -08:00
Maxime Thirouin
945fff0015 Add source files to published package 2016-11-25 12:38:29 -08:00
Nicolas Gallagher
5032ed6fe1 Update AppRegistry docs 2016-11-25 12:36:05 -08:00
Nicolas Gallagher
8c7cdbf4be 0.0.55 2016-11-24 10:24:19 -08:00
Nicolas Gallagher
e5d8857bcc [fix] inject ReactDefaultInjection
Fixes a regression introduced in the following commit to avoid directly
depending on the 'react-dom' entry file:

d65c92eea9

Injecting ReactDefaultInjection adds ~25 KB back to the UMD build.

Fix #263
2016-11-24 10:21:40 -08:00
Nicolas Gallagher
cda8d05bb7 0.0.54 2016-11-24 08:42:53 -08:00
Nicolas Gallagher
049edc4611 [change] don't prefix HTML id's with underscore 2016-11-23 10:28:20 -08:00
Nicolas Gallagher
e76d5a4e6c [change] export createDOMElement helper
Fix #184
2016-11-23 10:25:00 -08:00
Nicolas Gallagher
f71dae7d93 [add] support for vendor-prefixed font-smoothing styles
Fix #240
2016-11-23 09:58:18 -08:00
Nicolas Gallagher
94d31beaf4 [change] ignore unsupported React Native props
Ignores RN props that RN packages commonly applied to elements without
scoping them to supported platforms.

Fix #252
2016-11-23 09:27:27 -08:00
Nicolas Gallagher
f5f9389728 0.0.53 2016-11-22 17:46:57 -08:00
Nicolas Gallagher
fdbd19a4af [fix] PropTypes production error
See: https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types/issues/68
2016-11-22 17:44:48 -08:00
Nicolas Gallagher
36eafbc2f5 0.0.52 2016-11-22 17:10:14 -08:00
Nicolas Gallagher
bca3398c1c Correct devDependencies 2016-11-22 17:08:04 -08:00
Nicolas Gallagher
722d77e8e5 Smaller production builds
Builds on the exclusion of PropTypes from production builds:

- Remove 'lodash' and use smaller modules for equivalent functions.
- Remove use of some unnecessary Facebook utilities.
- Remove 'TouchableBounce'; it isn't part of React Native anymore.
- Remove stray import of 'react-dom/server'.
- Exclude 'StyleSheetValidation' from production.

Measuring the UMD build (gzip)…

Before: ~100KB
After: ~60KB
2016-11-22 16:59:20 -08:00
Nicolas Gallagher
d65c92eea9 [change] prepare for react-dom@15.4.0
Don't depend directly on the 'react-dom' module as it will be prebuilt
in 15.4. Leave server-side rendering to 'react-dom/server'.
2016-11-22 16:57:28 -08:00
Nicolas Gallagher
8dd39c681c [change] allow propTypes to be removed in production builds
Fix #254
2016-11-22 16:57:22 -08:00
Nicolas Gallagher
0b1759363d [add] promote ScrollView content to new layer 2016-11-22 13:08:11 -08:00
Nicolas Gallagher
abd1227a94 [change] ScrollView props and event handling
- Update 'scrollEventThrottle' prop
- Filter non-DOM props
- Persist debounced scroll events.

Fix #209
2016-11-21 21:39:08 -08:00
Nicolas Gallagher
8352c7cbda Use yarn for dependency management 2016-11-21 17:10:50 -08:00
Nicolas Gallagher
89f5a13891 [change] TextInput uses DOM elements
This patch changes TextInput to use DOM inputs directly, rather than
trying to reimplement 'placeholder'. Removes support for
'placeholderTextColor'.

Fix #54
Fix #224
Fix #229
Fix #235
Fix #253
2016-11-21 16:52:40 -08:00
Nicolas Gallagher
4005f9ddde 0.0.51 2016-11-21 12:42:58 -08:00
Nicolas Gallagher
f192a9ba26 [fix] Depend on React@15.3
React@15.4 includes changes that prevent the ResponderEventPlugin from
being properly injected, which breaks Touchables and PanResponder.

Fix #257
2016-11-21 12:38:37 -08:00
Nicolas Gallagher
e688a949fb 0.0.50 2016-11-20 14:00:42 -08:00
Nicolas Gallagher
77605cb27c [add] Text accessibility roles
Fix #199
2016-11-20 13:56:49 -08:00
Nicolas Gallagher
4f71833aec [fix] Image rendering in Safari
The use of 'max-height:100%' on the inner image can cause extremely poor
render performance in Safari. Remove the inner image and simplify
`Image` to use a single view. This fixes the following additional bugs:

Fix #202
Fix #226
2016-11-20 13:51:16 -08:00
Nicolas Gallagher
fa14995a35 Use jest for testing
Thanks to @paularmstrong:
https://github.com/necolas/react-native-web/pull/249
2016-11-09 10:00:49 -08:00
Paul Armstrong
4beae0dd78 [fix] NetInfo event handler registration 2016-11-04 10:20:19 -07:00
Nicolas Gallagher
5598961d2c Move ResponderEventPlugin injection to View 2016-11-04 10:09:55 -07:00
Nicolas Gallagher
4613baf0e8 [fix] StyleSheet check 'transform' is an array 2016-11-04 10:09:27 -07:00
Nicolas Gallagher
3b661d8d6d 0.0.49 2016-11-03 09:16:00 -07:00
Nicolas Gallagher
22d20706e3 Add React Native TextInput examples 2016-11-03 08:56:26 -07:00
Nicolas Gallagher
0b2813b186 [fix] View when 'style' is not defined 2016-11-03 08:52:25 -07:00
Nicolas Gallagher
b248de552d Fix tests 2016-11-03 08:51:51 -07:00
Nicolas Gallagher
2b826dc7f4 [add] TextInput support for selection 2016-10-28 23:37:19 -07:00
Nicolas Gallagher
b46acd4f50 [fix] TextInputState focus management 2016-10-28 22:12:20 -07:00
Nicolas Gallagher
5a03cb25cb [add] TextInput support for blurOnSubmit and onSubmitEditing 2016-10-28 21:15:35 -07:00
Nicolas Gallagher
44e60d12e3 [change] TextInput support for autoCorrect and autoComplete 2016-10-28 10:51:05 -07:00
Nicolas Gallagher
fc60f8d332 [add] TextInput support for autoCapitalize 2016-10-28 10:36:06 -07:00
Nicolas Gallagher
2a65ca6fc0 [add] TextInput support for isFocused 2016-10-27 22:31:43 -07:00
Nicolas Gallagher
9db3bd7e67 [add] TextInput support for onKeyPress
Fix #215
2016-10-27 22:17:59 -07:00
Nicolas Gallagher
1963e9109a 0.0.48 2016-10-27 21:12:04 -07:00
Nicolas Gallagher
14072c7471 [fix] View event handling
Fix #238
2016-10-27 21:00:17 -07:00
Nicolas Gallagher
0af6dc00fc [change] Image 'source' dimensions and RN layout
Adds support for 'width' and 'height' set via the 'source' property.
Emulates RN image layout (i.e., no dimensions by default).

Fix #10
2016-10-23 19:19:52 -07:00
Nicolas Gallagher
c9d401f09a [fix] Image resizeMode style
Fixes an issue in production where 'resizeMode' is deleted from style
simple objects, preventing it from being applied at render time.

Fix #233
2016-10-23 14:50:25 -07:00
Nicolas Gallagher
8aeeed0ef7 [fix] accept number or string for flexBasis style
Fix #230
2016-10-20 10:36:40 -07:00
Nicolas Gallagher
f5d0f73b4f 0.0.47 2016-10-13 10:30:49 -07:00
Xiaohan Zhang
ee7d367062 [fix] AsyncStorage.mergeItem to support deep merge
Mirrors behaviour of react-native
2016-10-13 10:27:51 -07:00
Nicolas Gallagher
dbd607ce47 0.0.46 2016-10-10 17:12:28 -07:00
Nicolas Gallagher
373cb38ca9 Fix lint error 2016-10-10 09:19:20 -07:00
Nicolas Gallagher
4576b42365 Correct the Image docs 2016-10-09 16:53:41 -07:00
Nicolas Gallagher
5a5707855b [fix] CSS reset 2016-10-04 16:26:03 -07:00
Paul Le Cam
0c76cc5d80 [fix] how ListView uses ScrollView
* Bind `_setScrollViewRef` to instance
* Fix getting ScrollView props for ListView
2016-09-19 16:24:25 -07:00
Nicolas Gallagher
d64df129b2 [fix] remove default 'input' border-radius 2016-09-12 11:46:47 -07:00
Nicolas Gallagher
763c5444ce Add 'ProgressBar' to README 2016-09-06 12:51:59 -07:00
Nicolas Gallagher
88d13f06f8 0.0.45 2016-09-06 12:42:36 -07:00
Nicolas Gallagher
3dd94880e0 [fix] ActivtyIndicator accessibility props
Markup the 'ActivityIndicator' as an indeterminate ARIA 'progressbar'
2016-09-06 12:41:07 -07:00
Nicolas Gallagher
e1080d72d7 [fix] ScrollView style issues
* Fix contentContainer not expanding to contain all child elements
* Add momentum scrolling on iOS

Fix #197
2016-09-06 12:33:53 -07:00
Nicolas Gallagher
55849cdd0d Add display names 2016-09-06 12:33:14 -07:00
Nicolas Gallagher
0aef117733 [add] ProgressBar component
Ref #91
2016-09-01 16:49:42 -07:00
Nicolas Gallagher
977d8729f5 Fix style prop validation in development 2016-09-01 16:37:45 -07:00
Nicolas Gallagher
9222cbf4bd [add] support numeric ActivityIndicator size value 2016-09-01 16:36:49 -07:00
Nicolas Gallagher
103560fc11 Add i18n docs and update README 2016-08-31 15:19:49 -07:00
Nicolas Gallagher
3a2daf386d [change] Hide more 'input' pseudo-elements
Removes the webkit spin-buttons from numeric inputs.

Fix #194
2016-08-31 14:07:08 -07:00
Nicolas Gallagher
6640b61b3e Change appearance of ActivityIndicator
This looks more like a traditional OS-level activity indicator.

The design of the Android activity indicator hasn't worked very well for
us on Web. The main problem is that if the main thread is locked, the
indicator (even if it's a gif) will stop animating and can look really
bad. This implementation looks like an activity indicator even when it
isn't animating.
2016-08-31 13:54:00 -07:00
Nicolas Gallagher
07d1124d60 Update runtime dependencies 2016-08-26 11:30:45 -07:00
Nicolas Gallagher
c7b3a8e60b Update react-storybook dependency 2016-08-26 11:30:44 -07:00
Nicolas Gallagher
d32eec7239 Update build and test dependencies 2016-08-26 11:30:38 -07:00
Nicolas Gallagher
f8f2898095 Use Twitter JavaScript style 2016-08-26 11:29:11 -07:00
Nicolas Gallagher
201bfd2e4d [change] remove 'label' element from Switch 2016-08-22 16:32:18 -07:00
Nicolas Gallagher
496839d19a Add tests for Switch 2016-08-20 20:33:55 -07:00
Nicolas Gallagher
6fe3f1f533 Don't persist RTL across examples 2016-08-19 16:46:07 -07:00
Nicolas Gallagher
aa53d931d5 Add I18nManager example 2016-08-19 14:04:10 -07:00
Nicolas Gallagher
88b184d540 Reorganize examples 2016-08-19 14:03:55 -07:00
Nicolas Gallagher
011affb110 0.0.44 2016-08-18 16:27:39 -07:00
Nicolas Gallagher
87a4f56a89 [add] Switch component
'Switch' on Web can support custom sizes and colors. To do so,
Web-specific propTypes are introduced: `trackColor`, `thumbColor`,
`activeTrackColor`, and `activeThumbColor`.
2016-08-18 16:25:16 -07:00
Nathan Tran
2f94295739 [fix] TextInput 'keyboardType' propType 2016-08-17 15:51:55 -07:00
Nicolas Gallagher
fdf4a88251 Refactor DOM element helper 2016-08-17 15:46:10 -07:00
Nicolas Gallagher
acc7032d60 0.0.43 2016-08-16 14:05:48 -07:00
Nicolas Gallagher
0fc5644959 Add more details to README
Fix #57
2016-08-16 14:04:22 -07:00
Nicolas Gallagher
be923ec453 [fix] disabled Touchable 2016-08-16 10:29:26 -07:00
Nicolas Gallagher
248c2e1258 [fix] ActivityIndicator animation
Use 'Animated' to animate the 'ActivityIndicator'

Fix #182
2016-08-15 16:58:20 -07:00
Nicolas Gallagher
2e822c068d [fix] Image render thrashing
This patch removes several avoidable uses of `setState`, only
re-rendering the `Image` when necessary.

It also fixes a style prop warning for `resizeMode`, which was not being
removed in development due to the use of `Object.freeze` when styles are
registered.

Close #116
2016-08-15 15:00:47 -07:00
Nicolas Gallagher
fb5406e4ec [change] I18nManager manages global writing direction 2016-08-15 11:36:59 -07:00
Vitor Balocco
638479991e Fix broken link and typo in StyleSheet API docs (#189) 2016-08-14 19:42:42 -07:00
Nicolas Gallagher
fb41be1bf7 0.0.42 2016-08-12 14:59:59 -07:00
Nicolas Gallagher
2291544373 [fix] React@15.3 propType warnings 2016-08-12 14:59:28 -07:00
Nicolas Gallagher
6416166bc3 [add] support for RTL layout
Add `I18nManager` API from React Native. This can be used to control
when the app displays in RTL mode.

Add `$noI18n` property suffix for properties that StyleSheet will
automatically flip. This can be used to opt-out of automatic flipping on
a per-declaration basis.
2016-08-12 14:58:53 -07:00
Nicolas Gallagher
f1e221e51e Add Github issue and pull request templates
Fix #180
Close #185
2016-08-05 10:59:00 -07:00
Nicolas Gallagher
27edfd9d88 Update guide for styling 2016-08-03 15:56:04 -07:00
Nicolas Gallagher
6cadc22ad5 0.0.41 2016-08-03 13:16:50 -07:00
Nicolas Gallagher
5c8cdd7742 [add] export a 'core' module 2016-08-03 13:16:09 -07:00
Nicolas Gallagher
e3450ed26c 0.0.40 2016-07-29 14:06:21 -07:00
Nicolas Gallagher
d54a84701a [fix] ScrollView scrolling
Scrolling is broken by the patch that adds ResponderEvent support for
multi-input devices: 6a9212df40

By calling 'preventDefault' on every touch event, scroll events were
cancelled. This patch shifts the responsibility for calling
'preventDefault' to the 'View' event handler normalizer, and only on
touch events within the Responder system.

Fix #175
2016-07-29 14:05:52 -07:00
Nicolas Gallagher
8201906703 Fix lint error 2016-07-27 15:08:10 -07:00
Nicolas Gallagher
10f88670ed [add] support for textShadow* and transition style props
Close #170
2016-07-23 19:16:48 -07:00
Nicolas Gallagher
1be2c810d1 Minor refactor of StyleSheet helpers 2016-07-23 18:54:41 -07:00
Nicolas Gallagher
6f75fb3e0d Fix gh-pages build 2016-07-22 16:10:14 -07:00
Nicolas Gallagher
424e6b5994 0.0.39 2016-07-22 15:54:02 -07:00
Nicolas Gallagher
769931061a Fix lint issue 2016-07-22 11:35:27 -07:00
Nicolas Gallagher
7aa760506a [fix] improve event normalization coverage 2016-07-22 11:28:08 -07:00
Nicolas Gallagher
9401eb9b47 Move webpack test entry 2016-07-20 14:30:20 -07:00
Nicolas Gallagher
6a9212df40 [change] ResponderEvent support for multi-input devices
Certain devices support both mouse and touch inputs. The Responder
plugin needs to support this. Previously it would specific touch-only
dependencies if touch support was detected.

The recommended way to prevent browsers firing mouse events after touch
events is to call `preventDefault` on the touch event. This may be
problematic if/when `View` and `Touchable` support URLs/hrefs.

Fix #169
2016-07-20 14:26:42 -07:00
Nicolas Gallagher
f2772b89bf [add] support for 'currentcolor' color 2016-07-16 19:31:10 -07:00
Nicolas Gallagher
9e970b3c34 Better layout for Text examples 2016-07-16 19:26:59 -07:00
Nicolas Gallagher
9677e9da0a Use React's 'TouchHistoryMath' module 2016-07-16 19:25:32 -07:00
Nicolas Gallagher
2440e74e99 [add] new Image resize modes 2016-07-16 19:24:25 -07:00
Nicolas Gallagher
66b0387023 add a script to deploy storybook to gh-pages 2016-07-14 00:04:15 -07:00
Nicolas Gallagher
94f37740af use react-storybook to display examples
Initial setup of react-storybook. Includes examples copied from the
React Native repository.

Close #148
2016-07-13 22:19:39 -07:00
Nicolas Gallagher
e1991f8f6b [change] remove maxWidth from default View styles
View's default styles include `maxWidth:"100%"` to fix a specific
flexbox bug in Internet Explorer. But it's not needed for the default View
layout and it limits the width of absolutely positioned elements (a
bug).
2016-07-13 21:56:35 -07:00
Nicolas Gallagher
21eeafabd5 0.0.38 2016-07-12 21:19:28 -07:00
Nicolas Gallagher
249f157ed9 [fix] ListView child layout
Fix #166
2016-07-12 21:18:18 -07:00
188 changed files with 16895 additions and 3420 deletions

View File

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

215
.eslintrc
View File

@@ -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
View 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
View 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
View File

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

View File

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

View File

@@ -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
View File

@@ -2,51 +2,104 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~41.0k-blue.svg)
[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

1
core.js Normal file
View File

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

View File

@@ -3,8 +3,7 @@
`AppRegistry` is the control point for registering, running, prerendering, and
unmounting all apps. App root components should register themselves with
`AppRegistry.registerComponent`. Apps can be run by invoking
`AppRegistry.runApplication`, 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
View 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
View 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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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
View 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"
/>
```

View File

@@ -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.

View 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.

View File

@@ -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
View 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>
)
}
}
```

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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}
/>
```

View File

@@ -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);
```

View File

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

View File

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

View File

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

View File

@@ -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')
}
}
}

View 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 />
));

View 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 />
))

View 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 />
));

View 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 />)

View File

@@ -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())
})

View File

@@ -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'
}
})

View 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());
});

View File

@@ -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%'
}
})

View File

@@ -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

View 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())
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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'
}
});

View 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())
})

View 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
}
})

View 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())
})

View 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 &nbsp;&nbsp;&nbsp; spaces
</Text>
);
},
}, {
title: 'Line Height',
render: function() {
return (
<Text>
<Text style={{lineHeight: 35}}>
A lot of space between the lines of this long passage that should
wrap once.
</Text>
</Text>
);
},
}, {
title: 'Empty Text',
description: 'It\'s ok to have Text with zero or null children.',
render: function() {
return (
<Text />
);
},
}, {
title: 'Toggling Attributes',
render: function(): 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())
})

View 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())
});

View 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())
})

View 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())
})

View 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())
})

View File

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

View 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 />
))

View 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 />
))

View File

@@ -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>

View File

@@ -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)

View File

@@ -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
}
})
}

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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');
});
});

View File

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

View File

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

View File

@@ -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();
});
});
});

View File

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

View File

@@ -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,
},
}
`;

View File

@@ -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();
});
});
});
});

View File

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

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

View File

@@ -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', () => {});
});

View File

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

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

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

View File

@@ -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
View 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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -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', () => {});
});

View File

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

View File

@@ -1,6 +1,6 @@
const Platform = {
OS: 'web',
select: (obj: Object) => obj.web
}
};
module.exports = Platform
module.exports = Platform;

View File

@@ -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

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -0,0 +1,10 @@
exports[`apis/StyleSheet resolve 1`] = `
Object {
"className": "test __style_df __style_pebn",
"style": Object {
"display": null,
"opacity": 1,
"pointerEvents": null,
},
}
`;

View File

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

View File

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

View 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();
});
});
});

View File

@@ -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();
});
});

View File

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

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

View File

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

View 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' });
});
});

View File

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

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

View File

@@ -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