Compare commits

...

115 Commits

Author SHA1 Message Date
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
Nicolas Gallagher
0f8cff6124 0.0.37 2016-07-12 17:47:40 -07:00
Nicolas Gallagher
30bf00a3bc [fix] TextInput styles
Ref #166
2016-07-12 17:47:10 -07:00
Nicolas Gallagher
f4515a3995 0.0.36 2016-07-12 13:56:34 -07:00
Nicolas Gallagher
17b30aceb2 [fix] default DOM element for 'View' (part 2)
First patch: 41159bcb10

@chriskjaer mentioned that changing from 'div' to 'span' introduces
different validation errors, e.g., <span><form>a</form></span>.

This patch uses 'context' to switch to a 'span' element if a 'View' is
being rendered within a 'button' element.
2016-07-12 11:03:31 -07:00
Nicolas Gallagher
5f3f4db7a6 [fix] iOS Touchable click handling 2016-07-12 10:26:00 -07:00
Nicolas Gallagher
eb8aa0a9db [add] 'selectable' prop to Text 2016-07-12 10:23:40 -07:00
Nicolas Gallagher
af60504ca4 [add] Vibration API 2016-07-11 21:51:00 -07:00
Nicolas Gallagher
41159bcb10 [fix] default DOM element for 'View'
There are certain contexts where using a `div` is invalid HTML and may
cause rendering issues. Change the default element created by
`createReactDOMComponent` to a `span`.

**Appendix**

The following HTML results a validator error.

  <!DOCTYPE html>
  <head>
  <meta charset="utf-8">
  <title>test</title>
  <button><div>a</div></button>

Error: Element `div` not allowed as child of element `button` in this
context.

Source: https://validator.w3.org/nu/#textarea
2016-07-11 20:25:05 -07:00
Nicolas Gallagher
640e41dc34 0.0.35 2016-07-11 19:03:33 -07:00
Andrew Palm
c609a6ff2b Fix TouchableWithoutFeedback propTypes (#164) 2016-07-11 19:02:39 -07:00
Nicolas Gallagher
294d94d869 0.0.34 2016-07-11 00:02:15 -07:00
Nicolas Gallagher
179d624917 [change] don't use invariant in StyleSheet validation 2016-07-11 00:01:29 -07:00
Nicolas Gallagher
61860b6d49 0.0.33 2016-07-10 22:20:03 -07:00
Nicolas Gallagher
597fcc65e8 [add] initial 'onLayout' support
Add initial support for 'onLayout' when components mount and update.

Ref #60
2016-07-10 22:15:51 -07:00
Nicolas Gallagher
5e1e0ec8e5 [fix] update Touchables 2016-07-10 22:15:24 -07:00
Nicolas Gallagher
0ac243038f remove Portal docs 2016-07-10 22:13:14 -07:00
Nicolas Gallagher
c9d68fe93e Resolve React@15.2.0 unknown props warnings 2016-07-10 18:32:02 -07:00
Nicolas Gallagher
77f72aa129 [change] StyleSheet: news APIs and refactor
This fixes several issues with 'StyleSheet' and simplifies the
implementation.

1. The generated style sheet could render after an apps existing style
sheets, potentially overwriting certain 'html' and 'body' styles. To fix
this, the style sheet is now rendered first in the document head.

2. 'StyleSheet' didn't make it easy to render app shells on the server.
The prerendered style sheet would contain classnames that didn't apply
to the client-generated style sheet (in part because the class names
were not generated as a hash of the declaration). When the client
initialized, server-rendered parts of the page could become unstyled. To
fix this 'StyleSheet' uses inline styles by default and a few predefined
CSS rules where inline styles are not possible.

3. Even with the strategy of mapping declarations to unique CSS rules,
very large apps can produce very large style sheets. For example,
twitter.com would produce a gzipped style sheet ~30 KB. Issues related
to this are also alleviated by using inline styles.

4. 'StyleSheet' didn't really work unless you rendered an app using
'AppRegistry'. To fix this, 'StyleSheet' now handles injection of the
DOM style sheet.

Using inline styles doesn't appear to have any serious performance
problems compared to using single classes (ref #110).

Fix #90
Fix #106
2016-07-10 18:31:12 -07:00
Nicolas Gallagher
216885406f [fix] TouchableHighlight inactive styles 2016-07-10 17:42:23 -07:00
Nicolas Gallagher
f15bf2664a fix View propTypes 2016-07-10 14:23:05 -07:00
Nicolas Gallagher
79998e0acc move 'normalizeNativeEvent' and 'injectResponderEventPlugin' 2016-07-09 11:17:05 -07:00
Nicolas Gallagher
44fc48f7a0 use 'normalizeValue' in 'processTransform' 2016-07-07 22:24:05 -07:00
Nicolas Gallagher
37f2d78f34 Minor tweaks 2016-07-07 22:22:37 -07:00
Nicolas Gallagher
1dc769bfb1 move propTypes and normalizeColor 2016-07-07 22:14:08 -07:00
Nicolas Gallagher
4b3cb41107 rename createNativeComponent to createReactDOMComponent 2016-07-07 21:21:45 -07:00
Nicolas Gallagher
ed2cbfd5d3 [fix] React Native styles -> React DOM styles
Add 'createReactStyleObject' to transform a React Native style object
into a React DOM-compatible style object. This is also needed to ensure
that 'setNativeProps' works as expected.
2016-07-06 19:49:55 -07:00
Nicolas Gallagher
8c4b5b68c3 Fix eslint error 2016-07-06 18:57:51 -07:00
Nicolas Gallagher
3564bbf840 0.0.32 2016-07-06 18:50:25 -07:00
Nicolas Gallagher
297b2e5afb [fix] support for Animated transform styles (part 2)
Only add 'px' to numeric translate values
2016-07-06 18:48:53 -07:00
Nicolas Gallagher
215697234e 0.0.31 2016-07-06 18:33:12 -07:00
Nicolas Gallagher
9efa7e94bd [fix] support for Animated transform styles
Animated uses 'setNativeProps' to update styles. This mutates the DOM
without using React. But the code path was not adding 'px' units to
transform values and browsers were ignoring the style.

Fix #129
2016-07-06 17:16:55 -07:00
Nicolas Gallagher
c44da41497 0.0.30 2016-07-06 15:30:40 -07:00
Nicolas Gallagher
331c92fb3a Use enzyme for all React component tests 2016-07-06 15:26:32 -07:00
Nicolas Gallagher
26758e905c [fix] TextInput alignment of small inner 'input'
Fix #139
2016-07-06 15:25:00 -07:00
Nicolas Gallagher
a15b15c55d Use babel-preset-react-native 2016-07-06 10:27:43 -07:00
Nicolas Gallagher
f0202dbe61 Remove use of decorator syntax 2016-07-06 10:11:03 -07:00
Nicolas Gallagher
d4d67dafc0 [fix] StyleSheet expansion of shortform properties
The previous implementation relied on a buggy sorting strategy. It could
result in shortform properties replacing the values set for longform
properties. This patch avoids expanding shorthand values to a longform
property if it declared in the original style object.

Fix #141
2016-07-05 19:18:11 -07:00
Nicolas Gallagher
579bdeb8a5 [fix] setNativeProps on TextInput 2016-07-05 18:52:44 -07:00
Nicolas Gallagher
7132a18440 Remove unused import 2016-07-05 18:48:49 -07:00
Nicolas Gallagher
18881b1edb [fix] support border styles on Image
Fix #128
2016-07-05 13:57:42 -07:00
Nicolas Gallagher
4d1e7d8c0b 0.0.29 2016-07-05 13:50:25 -07:00
Nicolas Gallagher
a7158aeb6f [change] remove Portal component
Portal was undocumented and has been removed from React Native.

Fix #149
2016-07-05 13:49:37 -07:00
Nicolas Gallagher
03d413bca4 0.0.28 2016-07-05 11:33:29 -07:00
IjzerenHein
aef5efbad3 [add] basic ListView component
Close #87
2016-07-05 11:33:02 -07:00
Nicolas Gallagher
8fb8645723 Use 'enzyme' for 'View' tests 2016-07-05 11:33:02 -07:00
Cesar Andreu
d69406b4b1 Add an API to wrap and initialize animated (#159) 2016-07-03 11:00:50 -07:00
Nicolas Gallagher
2c2a96a183 update rendering docs 2016-06-29 17:42:06 -07:00
Nicolas Gallagher
b4a3053b5b fix README install command 2016-06-29 17:00:50 -07:00
Nicolas Gallagher
24836afd6a 0.0.26 2016-06-28 16:38:31 -07:00
Nicolas Gallagher
c46f242f6b [add] ReactDOM server API to ReactNative API 2016-06-28 16:38:21 -07:00
Nicolas Gallagher
1940868065 [fix] TextInput support for Text styles
Fix #81
Fix #133
2016-06-28 15:55:27 -07:00
Nicolas Gallagher
65a9317756 [fix] TextInput placeholder layout and focus
Fix the layout of placeholder text and shift focus to the DOM input when
`TextInput` is clicked or pressed.

Thanks to @tuckerconnelly and @Dremora.

Fix #138
Fix #119
Close #137
2016-06-28 15:04:38 -07:00
Nicolas Gallagher
3da05c48b0 [fix] support 'onClick' prop in 'View' 2016-06-28 15:04:17 -07:00
Nicolas Gallagher
f33312a4dd [change] Use animatedjs/animated
Depend on 'animatedjs/animated' for the Animation implementation. This
patch also replaces 'es6-set' with a small shim to reduce impact on
production bundle size.

Fix #95
2016-06-23 15:10:43 -07:00
Nicolas Gallagher
4516c72296 Use enzyme for Image tests 2016-06-23 13:52:08 -07:00
Nicolas Gallagher
7f94c4bf06 Install enzyme
Fix #83
2016-06-23 13:50:06 -07:00
Nicolas Gallagher
37781171aa [add] more ReactNative exports 2016-06-23 10:16:45 -07:00
Nicolas Gallagher
22f45e350b [fix] React@15: remove inline-style fallback values
React 15 has no way to handle fallback CSS values (for example, vendor
prefixed 'display:flex' values) in inline styles. This patch drops all
fallback values for inline styles at the cost of regressing browser
support (those without standard flexbox support will not layout React
Native components correctly).

Fix #131
2016-06-22 16:13:48 -07:00
Nicolas Gallagher
af40f98f23 [fix] AppState event handler registration
Fix #151
2016-06-22 15:41:35 -07:00
Nicolas Gallagher
eca2f69593 Remove a React error from test report 2016-06-21 14:59:45 -07:00
Nicolas Gallagher
d03d89ac71 [fix] CoreComponent -> createNativeComponent
'CoreComponent' creates new component instances and clutters the React
component tree during debugging. This patch converts 'CoreComponent' to
a simple function that creates a native web element.

This patch also includes a fix for use of the 'flexShrink' style on
'View'.

Fix #140
2016-06-21 14:59:38 -07:00
Nicolas Gallagher
393a6ef835 [fix] don't use 'bind' in JSX props 2016-06-20 11:31:38 -07:00
Nicolas Gallagher
36e89d5275 [fix] installation on Windows
Fix #114
2016-06-20 11:22:42 -07:00
Nicolas Gallagher
d53d1e6e56 Add link to react-native-web-starter 2016-06-20 11:09:50 -07:00
Nicolas Gallagher
2cb68a45be [add] Platform.select 2016-06-18 16:43:22 -07:00
Nicolas Gallagher
b56b8e494a Update various packages (inc. babel and eslint) 2016-06-14 16:05:30 -07:00
Nicolas Gallagher
60ad0e9ec5 Further fixes to examples following react@15 update 2016-06-14 15:56:10 -07:00
Nicolas Gallagher
f2ea7c089c [change] separate the React and React Native APIs
Fix #136
2016-06-14 13:47:47 -07:00
Nicolas Gallagher
a3b59ed2b4 [fix] Touchable with React@15
Fix #123
2016-06-14 13:47:39 -07:00
Nicolas Gallagher
a378d3cce2 [change] update to React@15 2016-06-14 13:04:30 -07:00
Nicolas Gallagher
462f9793ea Fix code style issue 2016-06-13 15:05:03 -07:00
Monir Abu Hilal
ae38bb538c Do not treat lineHeight as a unitless numbers
Match the behavior of react-native for iOS and Android

The browser treats the 'line-height' CSS property as an 'em' value,
while react-native treats it as pixel unit (or device unit, which should
be 'px' for the web), this issue is causing the 'TextInput' component to
be sized incorrectly.

Close #142
2016-06-13 12:04:53 -07:00
Nicolas Gallagher
93d1488cc7 Fix README link to View 2016-06-13 11:59:24 -07:00
Nicolas Gallagher
a16e542bd8 [fix] don't replace 'className' value 2016-06-13 11:58:05 -07:00
Nicolas Gallagher
62cd335788 [fix] TouchableHighlight default underlay style 2016-06-13 11:57:02 -07:00
Nicolas Gallagher
288e14cd70 Remove MediaQueryWidget from examples 2016-06-13 11:56:15 -07:00
Nicolas Gallagher
71cfd23624 0.0.25 2016-04-29 12:49:13 -07:00
Nicolas Gallagher
77b8e4a1fc [fix] pin inline-style-prefix-all
Version 1.1.0 contains a breaking change
2016-04-29 12:48:40 -07:00
Nicolas Gallagher
9543a79c3f 0.0.24 2016-04-20 11:38:38 -07:00
Nicolas Gallagher
e3eea6e132 [fix] TouchableHighlight
The fix in 97c0a31ce6 was incomplete due
to state key not being renamed.
2016-04-20 11:37:15 -07:00
Nicolas Gallagher
4d3418a968 0.0.23 2016-04-19 17:11:26 -07:00
Nicolas Gallagher
ea9bc734f1 [fix] TouchableWithoutFeedback
Fix #127
2016-04-19 17:10:50 -07:00
Nicolas Gallagher
e03af435ac 0.0.22 2016-04-18 16:55:57 -07:00
Nicolas Gallagher
97c0a31ce6 [fix] TouchableHighlight default underlayColor 2016-04-18 16:46:09 -07:00
Nicolas Gallagher
25d11ded46 [fix] NetInfo event handlers 2016-04-18 16:38:09 -07:00
Nicolas Gallagher
6a73d77030 Fix build 2016-03-24 17:01:38 -07:00
Nicolas Gallagher
0b63ba4e89 0.0.21 2016-03-24 11:50:01 -07:00
Nicolas Gallagher
51109d0768 [fix] update inline-style-prefix-all
inline-style-prefix-all@1.0.4 doesn't depend on `Set` anymore
2016-03-24 11:49:22 -07:00
Nicolas Gallagher
ac04ecd69e Update Dimensions when window resizes 2016-03-24 11:44:02 -07:00
Nicolas Gallagher
1a670ba6a7 Fix UMD bundle
Include React and ReactDOM in the UMD bundle. The library occupies the
`React` global.

Fix #105
2016-03-22 18:39:24 -07:00
Nicolas Gallagher
7a16d5711c 0.0.20 2016-03-20 12:19:40 -07:00
Nicolas Gallagher
9dde70fff5 Update documentation 2016-03-20 12:19:29 -07:00
Nicolas Gallagher
203980ab66 [fix] fbjs version compatible with React Native
React Native 0.21 currently uses fbjs@0.6.x, and React Native 0.22 will
use fbjs@0.7.x.

Fix #103
2016-03-20 12:11:31 -07:00
Nicolas Gallagher
924dc36d4a [fix] refactor StyleSheet
**Problem**

StyleSheet's implementation was overly complex. It required
`flattenStyle` to use `expandStyle`, and couldn't support mapping React
Native style props to CSS properties without also exposing those CSS
properties in the API.

**Response**

- `flattenStyle` is concerned only with flattening style objects.

- `StyleSheetRegistry` is responsible for registering styles, mapping
  the React Native style prop to DOM props, and generating the CSS for
  the backing style element.

- `StyleSheetRegistry` uses a simpler approach to caching styles and
  generating style sheet strings. It also drops the unobfuscated class
  names from development mode, as the React Dev Tools can provide a
  better debugging experience (pending a fix to allow props/styles to be
  changed from the dev tools).

- `StyleSheet` will fall back to inline styles if it doesn't think a
  style sheet has been rendered into the document. The relationship is
  currently only implicit. This should be revisited.

- `StyleSheet` exports `renderToString` as part of the documented API.

- Fix processing of `transformMatrix` and add tests for
  `processTransform`.

- Fix `input[type=search]` rendering in Safari by using `display:none`
  on its pseudo-elements.

- Add support for `textDecorationLine` and `textAlignVertical`.

- Note the `View` hack to conditionally apply the `flex-shrink:0` reset
  from css-layout. This is required because React Native's approach to
  resolving `style` is to give precendence to long-hand styles
  (e.g., `flexShrink`) over short-hand styles (e.g., `flex`). This means
  the `View` reset overrides any `flex:1` declaration. To get around
  this, `flexShrink` is only set in `View` if `flex` is not set.
2016-03-20 12:09:04 -07:00
Nicolas Gallagher
9b2421cdfa [fix] Server-side rendering
`AppRegistry.prerenderApplication` now returns a style element for use
in app shells.

Guard use of `window` in APIs and Event plugin.

Fix #107
Fix #108
2016-03-20 11:43:13 -07:00
148 changed files with 5035 additions and 4731 deletions

View File

@@ -1,10 +1,5 @@
{
"presets": [
"es2015",
"stage-1",
"react"
],
"plugins": [
"transform-decorators-legacy"
"react-native"
]
}

1
.gitignore vendored
View File

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

View File

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

View File

@@ -2,7 +2,7 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~41.0k-blue.svg)
![gzipped size](https://img.shields.io/badge/gzipped-~48.6k-blue.svg)
[React Native][react-native-url] components and APIs for the Web.
@@ -20,7 +20,7 @@ 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/apis/View.md) component makes it easy to build
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".
@@ -30,11 +30,14 @@ styles defined in JavaScript into "Atomic CSS".
To install in your app:
```
npm install --save react@0.14 react-dom@0.14 react-native-web
npm install --save react 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).
## Examples
Demos:
@@ -46,7 +49,8 @@ Demos:
Sample:
```js
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
import React from 'react'
import { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
// Components
const Card = ({ children }) => <View style={styles.card}>{children}</View>
@@ -98,7 +102,6 @@ Exported modules:
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`Portal`](docs/components/Portal.md)
* [`ScrollView`](docs/components/ScrollView.md)
* [`Text`](docs/components/Text.md)
* [`TextInput`](docs/components/TextInput.md)
@@ -118,6 +121,7 @@ Exported modules:
* [`PixelRatio`](docs/apis/PixelRatio.md)
* [`Platform`](docs/apis/Platform.md)
* [`StyleSheet`](docs/apis/StyleSheet.md)
* [`Vibration`](docs/apis/Vibration.md)
## License

View File

@@ -16,8 +16,11 @@ into `runApplication`. These should always be used as a pair.
(web) static **prerenderApplication**(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; }`, where
`html` the prerendered HTML, and `style` is the prerendered style sheet.
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.
static **registerConfig**(config: Array<AppConfig>)

View File

@@ -37,7 +37,6 @@ class Example extends React.Component {
constructor(props) {
super(props)
this.state = { currentAppState: AppState.currentState }
this._handleAppStateChange = this._handleAppStateChange.bind(this)
}
componentDidMount() {
@@ -48,7 +47,7 @@ class Example extends React.Component {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange(currentAppState) {
_handleAppStateChange = (currentAppState) => {
this.setState({ currentAppState });
}

View File

@@ -10,19 +10,36 @@ specific.
`Platform.OS` will be `web` when running in a Web browser.
**userAgent**: string
On Web, the `Platform` module can be also be used to detect the browser
`userAgent`.
## Examples
```js
import { Platform } from 'react-native';
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100,
});
if (Platform.userAgent.includes('Android')) {
console.log('Running on Android!');
}
```
## Methods
**select**(object): any
`Platform.select` takes an object containing `Platform.OS` as keys and returns
the value for the platform you are currently running on.
```js
import { Platform } from 'react-native';
const containerStyles = {
flex: 1,
...Platform.select({
android: {
backgroundColor: 'blue'
},
ios: {
backgroundColor: 'red'
},
web: {
backgroundColor: 'green'
}
})
});
```

View File

@@ -11,15 +11,56 @@ outside of the render loop and are applied as inline styles. Read more about to
Each key of the object passed to `create` must define a style object.
**flatten**: function
Flattens an array of styles into a single style object.
**render**: function
Returns a React `<style>` element for use in server-side rendering.
## Properties
**hairlineWidth**: number
**absoluteFill**: number
**flatten**: function
A very common pattern is to create overlays with position absolute and zero positioning,
so `absoluteFill` can be used for convenience and to reduce duplication of these repeated
styles.
```js
<View style={StyleSheet.absoluteFill} />
```
**absoluteFillObject**: object
Sometimes you may want `absoluteFill` but with a couple tweaks - `absoluteFillObject` can be
used to create a customized entry in a `StyleSheet`, e.g.:
```js
const styles = StyleSheet.create({
wrapper: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'transparent',
top: 10
}
})
```
**hairlineWidth**: number
## Example
```js
<View style={styles.container}>
<Text
children={'Title text'}
style={[
styles.title,
this.props.isActive && styles.activeTitle
]}
/>
</View>
const styles = StyleSheet.create({
container: {
borderRadius: 4,
@@ -35,29 +76,3 @@ const styles = StyleSheet.create({
}
})
```
Use styles:
```js
<View style={styles.container}>
<Text
style={[
styles.title,
this.props.isActive && styles.activeTitle
]}
/>
</View>
```
Or:
```js
<View style={styles.container}>
<Text
style={{
...styles.title,
...(this.props.isActive && styles.activeTitle)
}}
/>
</View>
```

35
docs/apis/Vibration.md Normal file
View File

@@ -0,0 +1,35 @@
# Vibration
Vibration is described as a pattern of on-off pulses, which may be of varying
lengths. The pattern may consist of either a single integer, describing the
number of milliseconds to vibrate, or an array of integers describing a pattern
of vibrations and pauses. Vibration is controlled with a single method:
`Vibration.vibrate()`.
The vibration is asynchronous so this method will return immediately. There
will be no effect on devices that do not support vibration.
## Methods
static **cancel**()
Stop the vibration.
static **vibrate**(pattern)
Start the vibration pattern.
## Examples
Vibrate once for 200ms:
```js
Vibration.vibrate(200);
Vibration.vibrate([200]);
```
Vibrate for 200ms, pause for 100ms, vibrate for 200ms:
```js
Vibration.vibrate([200, 100, 200]);
```

View File

@@ -23,7 +23,8 @@ Size of the indicator. Small has a height of `20`, large has a height of `36`.
## Examples
```js
import React, { ActivityIndicator, Component, StyleSheet, View } from 'react-native'
import React, { Component } from 'react'
import { ActivityIndicator, StyleSheet, View } from 'react-native'
class ToggleAnimatingActivityIndicator extends Component {
constructor(props) {

View File

@@ -31,7 +31,8 @@ Invoked on load error with `{nativeEvent: {error}}`.
**onLayout**: function
TODO
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
height } } }`, where `x` and `y` are the offsets from the parent node.
**onLoad**: function
@@ -45,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') = 'stretch'
Determines how to resize the image when the frame doesn't match the raw image
dimensions.
@@ -57,7 +58,7 @@ could be an http address or a base64 encoded image.
**style**: style
+ ...[View#style](View.md)
+ ...[View#style](./View.md)
+ `resizeMode`
**testID**: string
@@ -78,7 +79,8 @@ Example usage:
```js
import placeholderAvatar from './placeholderAvatar.png'
import React, { Component, Image, PropTypes, StyleSheet } from 'react-native'
import React, { Component } from 'react'
import { Image, PropTypes, StyleSheet } from 'react-native'
export default class ImageExample extends Component {
constructor(props, context) {

View File

@@ -4,6 +4,8 @@ TODO
## Props
[...ScrollView props](./ScrollView.md)
**children**: any
Content to display over the image.
@@ -15,7 +17,8 @@ Content to display over the image.
## Examples
```js
import React, { Component, ListView, PropTypes } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { ListView } from 'react-native'
export default class ListViewExample extends Component {
static propTypes = {}

View File

@@ -1,67 +0,0 @@
# Portal
`Portal` is used to render modal content on top of everything else in the
application. It passes modal views all the way up to the root element created
by `AppRegistry.runApplication`.
There can only be one `Portal` instance rendered in an application, and this
instance is controlled by React Native for Web.
## Methods
static **allocateTag**()
Creates a new unique tag for the modal that your component is rendering. A
good place to allocate a tag is in `componentWillMount`. Returns a string. See
`showModal` and `closeModal`.
static **closeModal**(tag: string)
Remove a modal from the collection of modals to be rendered. The `tag` must
exactly match the tag previous passed to `showModal` to identify the React
component.
static **getOpenModals**()
Get an array of all the open modals, as identified by their tag string.
static **showModal**(tag: string, component: any)
Render a new modal. The `tag` must be unique as it is used to identify the
React component to render. This same tag can later be used in `closeModal`.
## Examples
```js
import React, { Portal, Text, Touchable } from 'react-native'
export default class PortalExample extends Component {
componentWillMount() {
this._portalTag = Portal.allocateTag()
}
render() {
return (
<Touchable onPress={this._handlePortalOpen.bind(this)}>
<Text>Open portal</Text>
</Touchable>
)
}
_handlePortalClose(e) {
Portal.closeModal(this._portalTag)
}
_handlePortalOpen(e) {
Portal.showModal(this._portalTag, this._renderPortalContent())
}
_renderPortalContent() {
return (
<Touchable onPress={this._handlePortalClose.bind(this)}>
<Text>Close portal</Text>
</Touchable>
)
}
}
```

View File

@@ -29,8 +29,6 @@ Determines whether the keyboard gets dismissed in response to a scroll drag.
**onContentSizeChange**: function
TODO
Called when scrollable content view of the `ScrollView` changes. It's
implemented using the `onLayout` handler attached to the content container
which this `ScrollView` renders.
@@ -83,7 +81,8 @@ Scrolls to a given `x`, `y` offset (animation is not currently supported).
## Examples
```js
import React, { Component, ScrollView, StyleSheet } from 'react-native'
import React, { Component } from 'react'
import { ScrollView, StyleSheet } from 'react-native'
import Item from './Item'
export default class ScrollViewExample extends Component {

View File

@@ -32,7 +32,7 @@ Note: Avoid changing `accessibilityRole` values over time or after user
actions. Generally, accessibility APIs do not provide a means of notifying
assistive technologies of a `role` value change.
(web) **accessible**: bool = true
**accessible**: bool = true
When `false`, the text is hidden from assistive technologies. (This is
implemented using `aria-hidden`.)
@@ -45,10 +45,19 @@ Child content.
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
**onLayout**: function
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
height } } }`, where `x` and `y` are the offsets from the parent node.
**onPress**: function
This function is called on press.
**selectable**: bool = true
Lets the user select the text.
**style**: style
+ ...[View#style](View.md)
@@ -60,7 +69,8 @@ This function is called on press.
+ `letterSpacing`
+ `lineHeight`
+ `textAlign`
+ `textDecoration`
+ `textAlignVertical`
+ `textDecorationLine`
+ `textShadow`
+ `textTransform`
+ `whiteSpace`
@@ -74,7 +84,8 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Component, PropTypes, StyleSheet, Text } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { StyleSheet, Text } from 'react-native'
export default class PrettyText extends Component {
static propTypes = {

View File

@@ -14,16 +14,11 @@ Unsupported React Native props:
`enablesReturnKeyAutomatically` (ios),
`returnKeyType` (ios),
`selectionState` (ios),
`textAlign` (android),
`textAlignVertical` (android),
`underlineColorAndroid` (android)
## Props
(web) **accessibilityLabel**: string
Defines the text label available to assistive technologies upon interaction
with the element. (This is implemented using `aria-label`.)
[...View props](./View.md)
(web) **autoComplete**: bool = false
@@ -92,10 +87,6 @@ as an argument to the callback handler.
Callback that is called when the text input is focused.
**onLayout**: function
TODO
(web) **onSelectionChange**: function
Callback that is called when the text input's selection changes. The following
@@ -132,7 +123,7 @@ If `true`, all text will automatically be selected on focus.
**style**: style
+ ...[Text#style](Text.md)
+ ...[Text#style](./Text.md)
+ `outline`
**testID**: string
@@ -164,7 +155,8 @@ Focus the underlying DOM input.
## Examples
```js
import React, { Component, StyleSheet, TextInput } from 'react-native'
import React, { Component } from 'react'
import { StyleSheet, TextInput } from 'react-native'
export default class TextInputExample extends Component {
constructor(props, context) {

View File

@@ -9,6 +9,8 @@ several child components, wrap them in a View.
## Props
[...View props](./View.md)
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
@@ -22,6 +24,8 @@ Allows assistive technologies to present and support interaction with the view
When `false`, the view is hidden from screenreaders.
**children**: View
**delayLongPress**: number
Delay in ms, from `onPressIn`, before `onLongPress` is called.
@@ -47,9 +51,8 @@ always takes precedence if a touch hits two overlapping views.
**onLayout**: function
Invoked on mount and layout changes with.
`{nativeEvent: {layout: {x, y, width, height}}}`
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
height } } }`, where `x` and `y` are the offsets from the parent node.
**onLongPress**: function

View File

@@ -48,7 +48,8 @@ implemented using `aria-hidden`.)
**onLayout**: function
(TODO)
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
height } } }`, where `x` and `y` are the offsets from the parent node.
**onMoveShouldSetResponder**: function
@@ -184,7 +185,8 @@ Used to locate this view in end-to-end tests.
## Examples
```js
import React, { Component, PropTypes, StyleSheet, View } from 'react-native'
import React, { Component, PropTypes } from 'react'
import { StyleSheet, View } from 'react-native'
export default class ViewExample extends Component {
render() {

View File

@@ -40,11 +40,7 @@ module.exports = {
Minor platform differences can use the `Platform` module.
```js
import { AppRegistry, Platform, StyleSheet } from 'react-native'
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100
})
import { AppRegistry, Platform } from 'react-native'
AppRegistry.registerComponent('MyApp', () => MyApp)

View File

@@ -21,77 +21,38 @@ module.exports = {
Rendering without using the `AppRegistry`:
```js
import React from 'react-native'
import React from 'react'
import ReactNative from 'react-native'
// component that renders the app
const AppHeaderContainer = (props) => { /* ... */ }
// DOM render
React.render(<div />, document.getElementById('react-app'))
ReactNative.render(<AppHeaderContainer />, document.getElementById('react-app-header'))
// Server render
React.renderToString(<div />)
React.renderToStaticMarkup(<div />)
ReactNative.renderToString(<AppHeaderContainer />)
ReactNative.renderToStaticMarkup(<AppHeaderContainer />)
```
Rendering using the `AppRegistry`:
```
// App.js
import React, { AppRegistry } from 'react-native'
```js
import React from 'react'
import ReactNative, { AppRegistry } from 'react-native'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
export default AppContainer
```
```js
// client.js
import React, { AppRegistry } from 'react-native'
import App from './App'
// registers the app
AppRegistry.registerComponent('App', () => App)
// mounts and runs the app within the `rootTag` DOM node
AppRegistry.runApplication('App', { initialProps, rootTag: document.getElementById('react-app') })
```
React Native for Web extends `AppRegistry` to provide support for server-side
rendering.
```js
// AppShell.js
import React from 'react-native'
const AppShell = (html, style) => (
<html>
<head>
<meta charSet="utf-8" />
<meta content="initial-scale=1,width=device-width" name="viewport" />
{style}
</head>
<body>
<div id="react-app" dangerouslySetInnerHTML={{ __html: html }} />
</body>
</html>
)
export default AppShell
```
```js
// server.js
import React, { AppRegistry } from 'react-native'
import App from './App'
import AppShell from './AppShell'
// registers the app
AppRegistry.registerComponent('App', () => App)
// prerenders the app
const { html, style } = AppRegistry.prerenderApplication('App', { initialProps })
// renders the full-page markup
const renderedApplicationHTML = React.renderToString(<AppShell html={html} style={style} />)
// register the app
AppRegistry.registerComponent('App', () => AppContainer)
// DOM render
AppRegistry.runApplication('App', {
initialProps: {},
rootTag: document.getElementById('react-app')
})
// prerender the app
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
```

View File

@@ -174,16 +174,16 @@ const styles = StyleSheet.create({
CSS output:
```css
._s1 { color: gray; }
._s2 { font-size: 2rem; }
._s3 { font-size: 1.25rem; }
.__style1 { color: gray; }
.__style2 { font-size: 2rem; }
.__style3 { font-size: 1.25rem; }
```
Rendered HTML:
```html
<span className="_s1 _s2">Heading</span>
<span className="_s1 _s3">Text</span>
<span className="__style1 __style2">Heading</span>
<span className="__style1 __style3">Text</span>
```
### Reset
@@ -200,6 +200,7 @@ 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 {
@@ -214,12 +215,6 @@ input::-moz-focus-inner {
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
ol,
ul,
li {
list-style:none
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,22 +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')
),
new webpack.optimize.OccurenceOrderPlugin()
],
resolve: {
alias: {
'react-native': path.join(__dirname, '../src')
'react-native': path.join(__dirname, '../../src')
}
}
}

View File

@@ -0,0 +1,175 @@
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 (small, white)',
render() {
return (
<ActivityIndicator
style={[styles.centering, styles.gray]}
color="white"
/>
);
}
},
{
title: 'Gray',
render() {
return (
<View>
<ActivityIndicator
style={[styles.centering]}
/>
<ActivityIndicator
style={[styles.centering, {backgroundColor: '#eeeeee'}]}
/>
</View>
);
}
},
{
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 (
<ActivityIndicator
style={[styles.centering, {transform: [{scale: 1.5}]}]}
size="large"
/>
);
}
},
];
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('<ActivityIndicator>', module)
.add(example.title, () => example.render())
})

View File

@@ -16,7 +16,8 @@
*/
'use strict';
var React = require('react-native');
var React = require('react');
var ReactNative = require('react-native');
var {
Animated,
AppRegistry,
@@ -24,7 +25,7 @@ var {
Text,
TouchableBounce,
View,
} = React;
} = ReactNative;
var GameBoard = require('./GameBoard');

View File

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

View File

@@ -0,0 +1,655 @@
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'}}
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('<Image>', module)
.addDecorator((renderStory) => <View>{renderStory()}</View>)
.add(example.title, () => example.render())
})

BIN
examples/Image/bunny.png Normal file

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,4 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { ListView } from 'react-native'

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('PanResponder', module)
.add('example', () => <PanResponderExample />)

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, View } from 'react-native'
storiesOf('<ScrollView>', module)
.add('vertical', () => (
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
onScroll={e => console.log('ScrollView.onScroll', e)}
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>
))
.add('horizontal', () => (
<View style={styles.scrollViewContainer}>
<ScrollView
contentContainerStyle={styles.scrollViewContentContainerStyle}
horizontal
onScroll={e => console.log('ScrollView.onScroll', e)}
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>
))
const styles = StyleSheet.create({
box: {
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
borderWidth: 1
},
scrollViewContainer: {
height: '200px',
width: 300
},
scrollViewStyle: {
borderWidth: '1px'
},
scrollViewContentContainerStyle: {
padding: '10px'
}
})

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>
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: '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('<Text>', module)
.addDecorator((renderStory) => <View style={{ width: 320 }}>{renderStory()}</View>)
.add(example.title, () => example.render())
})

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, TextInput, View } from 'react-native'
storiesOf('<TextInput>', module)
.add('tbd', () => (
<View>
<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}
/>
</View>
))
const styles = StyleSheet.create({
textInput: {
borderWidth: 1
}
})

View File

@@ -16,14 +16,15 @@
*/
'use strict';
var React = require('react-native');
var React = require('react');
var ReactNative = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
TouchableHighlight,
View,
} = React;
} = ReactNative;
class Board {
grid: Array<Array<number>>;

View File

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

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('<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('<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('<View> transforms', module)
.add(example.title, () => example.render())
})

View File

@@ -1,267 +0,0 @@
import GridView from './GridView'
import Heading from './Heading'
import MediaQueryWidget from './MediaQueryWidget'
import React, { Image, StyleSheet, ScrollView, Text, TextInput, TouchableHighlight, View } from 'react-native'
export default class App extends React.Component {
static propTypes = {
mediaQuery: React.PropTypes.object,
style: View.propTypes.style
}
constructor(...args) {
super(...args)
this.state = {
scrollEnabled: true
}
}
render() {
const { mediaQuery } = this.props
const finalRootStyles = [
rootStyles.common,
mediaQuery.small.matches && rootStyles.mqSmall,
mediaQuery.large.matches && rootStyles.mqLarge
]
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>
<MediaQueryWidget mediaQuery={mediaQuery} />
<Heading size='large'>Image</Heading>
<Image
accessibilityLabel='accessible image'
children={<Text>Inner content</Text>}
defaultSource={{
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wkGESkdPWMDggAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAD5UlEQVR42u3UMQ0AAAgEMcC/x7eCCgaSVsIN10kK4IORADAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAkAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAADAvAsADDAjAsAMMCDAvAsAAMCzAsAMMCMCzAsAAMC8CwAMMCMCzAsAAMC8CwAMMCMCwAwwIMC8CwAAwLMCwAwwIwLMCwAAwLwLAAwwIwLADDAgwLwLAADAswLADDAjAswLAALi04UQW9HF910gAAAABJRU5ErkJggg=='
}}
onError={(e) => { console.log('Image.onError', e) }}
onLoad={(e) => { console.log('Image.onLoad', e) }}
onLoadEnd={() => { console.log('Image.onLoadEnd') }}
onLoadStart={() => { console.log('Image.onLoadStart') }}
resizeMode={'contain'}
source={{
height: 400,
uri: 'http://facebook.github.io/react/img/logo_og.png',
width: 400
}}
style={{
borderWidth: '5px'
}}
testID='Example.image'
/>
<Heading size='large'>Text</Heading>
<Text
onPress={(e) => { console.log('Text.onPress', e) }}
testID={'Example.text'}
>
PRESS ME.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
hendrerit consequat. Aliquam lorem quam, elementum eget ex nec,
ultrices porttitor nibh. Nulla pellentesque urna leo, a aliquet elit
rhoncus a. Aenean ultricies, nunc a interdum dictum, dui odio
scelerisque mauris, a fringilla elit ligula vel sem. Sed vel aliquet
ipsum, sed rhoncus velit. Vivamus commodo pretium libero id placerat.
</Text>
<Text numberOfLines={1}>
TRUNCATED after 1 line.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent vel
lectus urna. Aliquam vitae justo porttitor, aliquam erat nec,
venenatis diam. Vivamus facilisis augue non urna mattis ultricies.
Suspendisse et vulputate enim, a maximus nulla. Vivamus imperdiet
hendrerit consequat.
</Text>
<Heading size='large'>TextInput</Heading>
<TextInput
keyboardType='default'
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
onChange={(e) => { console.log('TextInput.onChange', e) }}
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
/>
<TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
<TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
multiline
numberOfLines={5}
/>
<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'
},
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

@@ -1,65 +0,0 @@
import React, { Component, PropTypes, 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: '0',
gutter: '0'
}
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,34 +0,0 @@
import React, { 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

@@ -1,39 +0,0 @@
import React, { StyleSheet, Text, View } from 'react-native'
const MediaQueryWidget = ({ mediaQuery = {} }) => {
const active = Object.keys(mediaQuery).reduce((active, alias) => {
if (mediaQuery[alias].matches) {
active = {
alias,
mql: mediaQuery[alias]
}
}
return active
}, {})
return (
<View style={styles.root}>
<Text style={styles.heading}>Active Media Query</Text>
<Text style={styles.text}>{`"${active.alias}"`} {active.mql && active.mql.media}</Text>
</View>
)
}
const styles = StyleSheet.create({
root: {
alignItems: 'center',
borderWidth: 1,
marginVertical: 10,
padding: 10
},
heading: {
fontWeight: 'bold',
padding: 5,
textAlign: 'center'
},
text: {
textAlign: 'center'
}
})
export default MediaQueryWidget

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,7 +0,0 @@
import React, { AppRegistry } from 'react-native'
import Game2048 from './2048/Game2048'
import TicTacToeApp from './TicTacToe/TicTacToe'
AppRegistry.runApplication('Game2048', {
rootTag: document.getElementById('react-root')
})

View File

@@ -1,6 +1,6 @@
var webpack = require('webpack')
const webpack = require('webpack')
var testEntry = 'tests.webpack.js'
const testEntry = 'src/tests.webpack.js'
module.exports = function (config) {
config.set({
@@ -19,17 +19,24 @@ module.exports = function (config) {
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-mocha',
'karma-mocha-reporter',
'karma-sourcemap-loader',
'karma-spec-reporter',
'karma-webpack'
],
preprocessors: {
[testEntry]: [ 'webpack', 'sourcemap' ]
},
reporters: process.env.TRAVIS ? [ 'dots' ] : [ 'spec' ],
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: [
{

View File

@@ -1,62 +1,65 @@
{
"name": "react-native-web",
"version": "0.0.19",
"version": "0.0.40",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"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",
"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"
},
"dependencies": {
"fbjs": "^0.7.2",
"inline-style-prefix-all": "^1.0.3",
"lodash.debounce": "^4.0.3",
"react-textarea-autosize": "^3.1.0",
"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",
"react-timer-mixin": "^0.13.3"
},
"devDependencies": {
"babel-cli": "^6.3.17",
"babel-core": "^6.3.13",
"babel-eslint": "^4.1.6",
"babel-loader": "^6.2.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"babel-runtime": "^6.3.19",
"eslint": "^1.10.3",
"eslint-config-standard": "^4.4.0",
"eslint-config-standard-react": "^1.2.1",
"eslint-plugin-react": "^3.13.1",
"eslint-plugin-standard": "^1.3.1",
"karma": "^0.13.16",
"karma-browserstack-launcher": "^0.1.8",
"karma-chrome-launcher": "^0.2.2",
"karma-firefox-launcher": "^0.1.7",
"karma-mocha": "^0.2.1",
"karma-sourcemap-loader": "^0.3.6",
"karma-spec-reporter": "0.0.23",
"@kadira/storybook": "^1.38.0",
"babel-cli": "^6.10.1",
"babel-core": "^6.10.4",
"babel-eslint": "^6.1.0",
"babel-loader": "^6.2.4",
"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",
"file-loader": "^0.9.0",
"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.3.4",
"mocha": "^2.5.3",
"node-libs-browser": "^0.5.3",
"react": "^0.14.3",
"react-addons-test-utils": "^0.14.3",
"react-dom": "^0.14.3",
"react-media-queries": "^2.0.1",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0"
"react": "^15.2.0",
"react-addons-test-utils": "^15.2.0",
"url-loader": "^0.5.7",
"webpack": "^1.13.1"
},
"peerDependencies": {
"react": "^0.14.3",
"react-dom": "^0.14.3"
"react": "^15.1.0"
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause",

View File

@@ -1,23 +0,0 @@
/* eslint-env mocha */
import assert from 'assert'
import React from '..'
suite('ReactNativeWeb', () => {
suite('exports', () => {
test('React', () => {
assert.ok(React)
})
test('ReactDOM methods', () => {
assert.ok(React.findDOMNode)
assert.ok(React.render)
assert.ok(React.unmountComponentAtNode)
})
test('ReactDOM/server methods', () => {
assert.ok(React.renderToString)
assert.ok(React.renderToStaticMarkup)
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,288 +0,0 @@
/* eslint-disable */
/**
* Copyright (c) 2015-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.
*
* @providesModule Interpolation
* @flow
*/
/* eslint no-bitwise: 0 */
'use strict';
/* @edit start */
var normalizeColor = require('../StyleSheet/normalizeColor');
var invariant = require('fbjs/lib/invariant');
/* @edit end */
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
export type InterpolationConfigType = {
inputRange: Array<number>;
outputRange: (Array<number> | Array<string>);
easing?: ((input: number) => number);
extrapolate?: ExtrapolateType;
extrapolateLeft?: ExtrapolateType;
extrapolateRight?: ExtrapolateType;
};
var linear = (t) => t;
/**
* Very handy helper to map input ranges to output ranges with an easing
* function and custom behavior outside of the ranges.
*/
class Interpolation {
static create(config: InterpolationConfigType): (input: number) => number | string {
if (config.outputRange && typeof config.outputRange[0] === 'string') {
return createInterpolationFromStringOutputRange(config);
}
var outputRange: Array<number> = (config.outputRange: any);
checkInfiniteRange('outputRange', outputRange);
var inputRange = config.inputRange;
checkInfiniteRange('inputRange', inputRange);
checkValidInputRange(inputRange);
invariant(
inputRange.length === outputRange.length,
'inputRange (' + inputRange.length + ') and outputRange (' +
outputRange.length + ') must have the same length'
);
var easing = config.easing || linear;
var extrapolateLeft: ExtrapolateType = 'extend';
if (config.extrapolateLeft !== undefined) {
extrapolateLeft = config.extrapolateLeft;
} else if (config.extrapolate !== undefined) {
extrapolateLeft = config.extrapolate;
}
var extrapolateRight: ExtrapolateType = 'extend';
if (config.extrapolateRight !== undefined) {
extrapolateRight = config.extrapolateRight;
} else if (config.extrapolate !== undefined) {
extrapolateRight = config.extrapolate;
}
return (input) => {
invariant(
typeof input === 'number',
'Cannot interpolation an input which is not a number'
);
var range = findRange(input, inputRange);
return interpolate(
input,
inputRange[range],
inputRange[range + 1],
outputRange[range],
outputRange[range + 1],
easing,
extrapolateLeft,
extrapolateRight,
);
};
}
}
function interpolate(
input: number,
inputMin: number,
inputMax: number,
outputMin: number,
outputMax: number,
easing: ((input: number) => number),
extrapolateLeft: ExtrapolateType,
extrapolateRight: ExtrapolateType,
) {
var result = input;
// Extrapolate
if (result < inputMin) {
if (extrapolateLeft === 'identity') {
return result;
} else if (extrapolateLeft === 'clamp') {
result = inputMin;
} else if (extrapolateLeft === 'extend') {
// noop
}
}
if (result > inputMax) {
if (extrapolateRight === 'identity') {
return result;
} else if (extrapolateRight === 'clamp') {
result = inputMax;
} else if (extrapolateRight === 'extend') {
// noop
}
}
if (outputMin === outputMax) {
return outputMin;
}
if (inputMin === inputMax) {
if (input <= inputMin) {
return outputMin;
}
return outputMax;
}
// Input Range
if (inputMin === -Infinity) {
result = -result;
} else if (inputMax === Infinity) {
result = result - inputMin;
} else {
result = (result - inputMin) / (inputMax - inputMin);
}
// Easing
result = easing(result);
// Output Range
if (outputMin === -Infinity) {
result = -result;
} else if (outputMax === Infinity) {
result = result + outputMin;
} else {
result = result * (outputMax - outputMin) + outputMin;
}
return result;
}
function colorToRgba(input: string): string {
var int32Color = normalizeColor(input);
if (int32Color === null) {
return input;
}
int32Color = int32Color || 0; // $FlowIssue
var r = (int32Color & 0xff000000) >>> 24;
var g = (int32Color & 0x00ff0000) >>> 16;
var b = (int32Color & 0x0000ff00) >>> 8;
var a = (int32Color & 0x000000ff) / 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
var stringShapeRegex = /[0-9\.-]+/g;
/**
* Supports string shapes by extracting numbers so new values can be computed,
* and recombines those values into new strings of the same shape. Supports
* things like:
*
* rgba(123, 42, 99, 0.36) // colors
* -45deg // values with units
*/
function createInterpolationFromStringOutputRange(
config: InterpolationConfigType,
): (input: number) => string {
var outputRange: Array<string> = (config.outputRange: any);
invariant(outputRange.length >= 2, 'Bad output range');
outputRange = outputRange.map(colorToRgba);
checkPattern(outputRange);
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
// ->
// [
// [0, 50],
// [100, 150],
// [200, 250],
// [0, 0.5],
// ]
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
* guard against this possibility.
*/
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
outputRange.forEach(value => {
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
* against this possibility.
*/
value.match(stringShapeRegex).forEach((number, i) => {
outputRanges[i].push(+number);
});
});
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
* guard against this possibility.
*/
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
return Interpolation.create({
...config,
outputRange: outputRanges[i],
});
});
return (input) => {
var i = 0;
// 'rgba(0, 100, 200, 0)'
// ->
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
return outputRange[0].replace(stringShapeRegex, () => {
return String(interpolations[i++](input));
});
};
}
function checkPattern(arr: Array<string>) {
var pattern = arr[0].replace(stringShapeRegex, '');
for (var i = 1; i < arr.length; ++i) {
invariant(
pattern === arr[i].replace(stringShapeRegex, ''),
'invalid pattern ' + arr[0] + ' and ' + arr[i],
);
}
}
function findRange(input: number, inputRange: Array<number>) {
for (var i = 1; i < inputRange.length - 1; ++i) {
if (inputRange[i] >= input) {
break;
}
}
return i - 1;
}
function checkValidInputRange(arr: Array<number>) {
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
for (var i = 1; i < arr.length; ++i) {
invariant(
arr[i] >= arr[i - 1],
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
* one or both of the operands may be something that doesn't cleanly
* convert to a string, like undefined, null, and object, etc. If you really
* mean this implicit string conversion, you can do something like
* String(myThing)
*/
'inputRange must be monotonically increasing ' + arr
);
}
}
function checkInfiniteRange(name: string, arr: Array<number>) {
invariant(arr.length >= 2, name + ' must have at least 2 elements');
invariant(
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
* one or both of the operands may be something that doesn't cleanly convert
* to a string, like undefined, null, and object, etc. If you really mean
* this implicit string conversion, you can do something like
* String(myThing)
*/
name + 'cannot be ]-infinity;+infinity[ ' + arr
);
}
module.exports = Interpolation;

View File

@@ -1,103 +0,0 @@
/* eslint-disable */
/**
* Copyright (c) 2015-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.
*
* @providesModule SpringConfig
* @flow
*/
'use strict';
type SpringConfigType = {
tension: number,
friction: number,
};
function tensionFromOrigamiValue(oValue) {
return (oValue - 30) * 3.62 + 194;
}
function frictionFromOrigamiValue(oValue) {
return (oValue - 8) * 3 + 25;
}
function fromOrigamiTensionAndFriction(
tension: number,
friction: number,
): SpringConfigType {
return {
tension: tensionFromOrigamiValue(tension),
friction: frictionFromOrigamiValue(friction)
};
}
function fromBouncinessAndSpeed(
bounciness: number,
speed: number,
): SpringConfigType {
function normalize(value, startValue, endValue) {
return (value - startValue) / (endValue - startValue);
}
function projectNormal(n, start, end) {
return start + (n * (end - start));
}
function linearInterpolation(t, start, end) {
return t * end + (1 - t) * start;
}
function quadraticOutInterpolation(t, start, end) {
return linearInterpolation(2 * t - t * t, start, end);
}
function b3Friction1(x) {
return (0.0007 * Math.pow(x, 3)) -
(0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28;
}
function b3Friction2(x) {
return (0.000044 * Math.pow(x, 3)) -
(0.006 * Math.pow(x, 2)) + 0.36 * x + 2;
}
function b3Friction3(x) {
return (0.00000045 * Math.pow(x, 3)) -
(0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84;
}
function b3Nobounce(tension) {
if (tension <= 18) {
return b3Friction1(tension);
} else if (tension > 18 && tension <= 44) {
return b3Friction2(tension);
} else {
return b3Friction3(tension);
}
}
var b = normalize(bounciness / 1.7, 0, 20);
b = projectNormal(b, 0, 0.8);
var s = normalize(speed / 1.7, 0, 20);
var bouncyTension = projectNormal(s, 0.5, 200);
var bouncyFriction = quadraticOutInterpolation(
b,
b3Nobounce(bouncyTension),
0.01
);
return {
tension: tensionFromOrigamiValue(bouncyTension),
friction: frictionFromOrigamiValue(bouncyFriction)
};
}
module.exports = {
fromOrigamiTensionAndFriction,
fromBouncinessAndSpeed,
};

View File

@@ -1,19 +1,14 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import AnimatedImplementation from './AnimatedImplementation'
import Animated from 'animated'
import StyleSheet from '../StyleSheet'
import Image from '../../components/Image'
import Text from '../../components/Text'
import View from '../../components/View'
Animated.inject.FlattenStyle(StyleSheet.flatten)
module.exports = {
...AnimatedImplementation,
View: AnimatedImplementation.createAnimatedComponent(View),
Text: AnimatedImplementation.createAnimatedComponent(Text),
Image: AnimatedImplementation.createAnimatedComponent(Image)
...Animated,
Image: Animated.createAnimatedComponent(Image),
Text: Animated.createAnimatedComponent(Text),
View: Animated.createAnimatedComponent(View)
}

View File

@@ -1,15 +0,0 @@
function SetPolyfill() {
this._cache = []
}
SetPolyfill.prototype.add = function (e) {
if (this._cache.indexOf(e) === -1) {
this._cache.push(e)
}
}
SetPolyfill.prototype.forEach = function (cb) {
this._cache.forEach(cb)
}
export default SetPolyfill

View File

@@ -1,6 +1,4 @@
import Portal from '../../components/Portal'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../StyleSheet'
import View from '../../components/View'
@@ -11,22 +9,12 @@ class ReactNativeApp extends Component {
rootTag: PropTypes.any
};
constructor(props, context) {
super(props, context)
this._handleModalVisibilityChange = this._handleModalVisibilityChange.bind(this)
}
_handleModalVisibilityChange(modalVisible) {
ReactDOM.findDOMNode(this._root).setAttribute('aria-hidden', `${modalVisible}`)
}
render() {
const { initialProps, rootComponent: RootComponent, rootTag } = this.props
return (
<View style={styles.appContainer}>
<RootComponent {...initialProps} ref={(c) => { this._root = c }} rootTag={rootTag} />
<Portal onModalVisibilityChanged={this._handleModalVisibilityChange} />
<RootComponent {...initialProps} rootTag={rootTag} />
</View>
)
}
@@ -34,8 +22,7 @@ class ReactNativeApp extends Component {
const styles = StyleSheet.create({
/**
* Ensure that the application covers the whole screen. This prevents the
* Portal content from being clipped.
* Ensure that the application covers the whole screen.
*/
appContainer: {
position: 'absolute',

View File

@@ -0,0 +1,16 @@
/* eslint-env mocha */
import assert from 'assert'
import { prerenderApplication } from '../renderApplication'
import React from 'react'
const component = () => <div />
suite('apis/AppRegistry/renderApplication', () => {
test('prerenderApplication', () => {
const { html, styleElement } = prerenderApplication(component, {})
assert.ok(html.indexOf('<div ') > -1)
assert.equal(styleElement.type, 'style')
})
})

View File

@@ -33,7 +33,7 @@ class AppRegistry {
invariant(
runnables[appKey] && runnables[appKey].prerender,
`Application ${appKey} has not been registered. ` +
`This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.`
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
)
return runnables[appKey].prerender(appParameters)
@@ -78,7 +78,7 @@ class AppRegistry {
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.`
'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.'
)
runnables[appKey].run(appParameters)

View File

@@ -13,15 +13,9 @@ import ReactDOMServer from 'react-dom/server'
import ReactNativeApp from './ReactNativeApp'
import StyleSheet from '../../apis/StyleSheet'
const renderStyleSheetToString = () => `<style id="${StyleSheet.elementId}">${StyleSheet._renderToString()}</style>`
export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)
// insert style sheet if needed
const styleElement = document.getElementById(StyleSheet.elementId)
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', renderStyleSheetToString()) }
const component = (
<ReactNativeApp
initialProps={initialProps}
@@ -40,6 +34,6 @@ export function prerenderApplication(RootComponent: Component, initialProps: Obj
/>
)
const html = ReactDOMServer.renderToString(component)
const style = renderStyleSheetToString()
return { html, style }
const styleElement = StyleSheet.render()
return { html, styleElement }
}

View File

@@ -1,5 +1,31 @@
/* eslint-env mocha */
import AppState from '..'
import assert from 'assert'
suite('apis/AppState', () => {
test.skip('NO TEST COVERAGE', () => {})
const handler = () => {}
teardown(() => {
try { AppState.removeEventListener('change', handler) } catch (e) {}
})
suite('addEventListener', () => {
test('throws if the provided "eventType" is not supported', () => {
assert.throws(() => AppState.addEventListener('foo', handler))
assert.doesNotThrow(() => AppState.addEventListener('change', handler))
})
})
suite('removeEventListener', () => {
test('throws if the handler is not registered', () => {
assert.throws(() => AppState.removeEventListener('change', handler))
})
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))
})
})
})

View File

@@ -1,30 +1,53 @@
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import findIndex from 'lodash/findIndex'
import invariant from 'fbjs/lib/invariant'
const listeners = {}
const eventTypes = [ 'change' ]
const EVENT_TYPES = [ 'change' ]
const VISIBILITY_CHANGE_EVENT = 'visibilitychange'
const AppStates = {
BACKGROUND: 'background',
ACTIVE: 'active'
}
const listeners = []
class AppState {
static isSupported = ExecutionEnvironment.canUseDOM && document.visibilityState
static get currentState() {
if (!AppState.isSupported) {
return AppState.ACTIVE
}
switch (document.visibilityState) {
case 'hidden':
case 'prerender':
case 'unloaded':
return 'background'
return AppStates.BACKGROUND
default:
return 'active'
return AppStates.ACTIVE
}
}
static addEventListener(type: string, handler: Function) {
listeners[handler] = () => handler(AppState.currentState)
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
document.addEventListener('visibilitychange', listeners[handler], false)
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)
}
}
static removeEventListener(type: string, handler: Function) {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type)
document.removeEventListener('visibilitychange', listeners[handler], false)
delete listeners[handler]
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)
}
}
}

View File

@@ -6,28 +6,38 @@
* @flow
*/
import debounce from 'lodash/debounce'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
const dimensions = {
screen: {
fontScale: 1,
get height() { return window.screen.height },
scale: window.devicePixelRatio || 1,
get width() { return window.screen.width }
},
window: {
fontScale: 1,
get height() { return window.innerHeight },
scale: window.devicePixelRatio || 1,
get width() { return window.innerWidth }
}
}
const win = ExecutionEnvironment.canUseDOM ? window : { screen: {} }
const dimensions = {}
class Dimensions {
static get(dimension: string): Object {
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
return dimensions[dimension]
}
static set(): void {
dimensions.window = {
fontScale: 1,
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))
module.exports = Dimensions

View File

@@ -1,83 +0,0 @@
/* eslint-disable */
/**
* https://github.com/arian/cubic-bezier
*
* MIT License
*
* Copyright (c) 2013 Arian Stolwijk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @providesModule bezier
* @nolint
*/
module.exports = function(x1, y1, x2, y2, epsilon){
var curveX = function(t){
var v = 1 - t;
return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
};
var curveY = function(t){
var v = 1 - t;
return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
};
var derivativeCurveX = function(t){
var v = 1 - t;
return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2;
};
return function(t){
var x = t, t0, t1, t2, x2, d2, i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++){
x2 = curveX(t2) - x;
if (Math.abs(x2) < epsilon) { return curveY(t2); }
d2 = derivativeCurveX(t2);
if (Math.abs(d2) < 1e-6) { break; }
t2 = t2 - x2 / d2;
}
t0 = 0;
t1 = 1;
t2 = x;
if (t2 < t0) { return curveY(t0); }
if (t2 > t1) { return curveY(t1); }
// Fallback to the bisection method for reliability.
while (t0 < t1){
x2 = curveX(t2);
if (Math.abs(x2 - x) < epsilon) { return curveY(t2); }
if (x > x2) { t0 = t2; }
else { t1 = t2; }
t2 = (t1 - t0) * 0.5 + t0;
}
// Failure
return curveY(t2);
};
};

View File

@@ -1,155 +0,0 @@
/* eslint-disable */
/**
* Copyright (c) 2015-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.
*
* @providesModule Easing
* @flow
*/
'use strict';
var _bezier = require('./bezier');
/**
* This class implements common easing functions. The math is pretty obscure,
* but this cool website has nice visual illustrations of what they represent:
* http://xaedes.de/dev/transitions/
*/
class Easing {
static step0(n) {
return n > 0 ? 1 : 0;
}
static step1(n) {
return n >= 1 ? 1 : 0;
}
static linear(t) {
return t;
}
static ease(t: number): number {
return ease(t);
}
static quad(t) {
return t * t;
}
static cubic(t) {
return t * t * t;
}
static poly(n) {
return (t) => Math.pow(t, n);
}
static sin(t) {
return 1 - Math.cos(t * Math.PI / 2);
}
static circle(t) {
return 1 - Math.sqrt(1 - t * t);
}
static exp(t) {
return Math.pow(2, 10 * (t - 1));
}
/**
* A simple elastic interaction, similar to a spring. Default bounciness
* is 1, which overshoots a little bit once. 0 bounciness doesn't overshoot
* at all, and bounciness of N > 1 will overshoot about N times.
*
* Wolfram Plots:
*
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
* http://tiny.cc/elastic_b_3 (bounciness = 3)
*/
static elastic(bounciness: number = 1): (t: number) => number {
var p = bounciness * Math.PI;
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
}
static back(s: number): (t: number) => number {
if (s === undefined) {
s = 1.70158;
}
return (t) => t * t * ((s + 1) * t - s);
}
static bounce(t: number): number {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
}
if (t < 2 / 2.75) {
t -= 1.5 / 2.75;
return 7.5625 * t * t + 0.75;
}
if (t < 2.5 / 2.75) {
t -= 2.25 / 2.75;
return 7.5625 * t * t + 0.9375;
}
t -= 2.625 / 2.75;
return 7.5625 * t * t + 0.984375;
}
static bezier(
x1: number,
y1: number,
x2: number,
y2: number,
epsilon?: ?number,
): (t: number) => number {
if (epsilon === undefined) {
// epsilon determines the precision of the solved values
// a good approximation is:
var duration = 500; // duration of animation in milliseconds.
epsilon = (1000 / 60 / duration) / 4;
}
return _bezier(x1, y1, x2, y2, epsilon);
}
static in(
easing: (t: number) => number,
): (t: number) => number {
return easing;
}
/**
* Runs an easing function backwards.
*/
static out(
easing: (t: number) => number,
): (t: number) => number {
return (t) => 1 - easing(1 - t);
}
/**
* Makes any easing function symmetrical.
*/
static inOut(
easing: (t: number) => number,
): (t: number) => number {
return (t) => {
if (t < 0.5) {
return easing(t * 2) / 2;
}
return 1 - easing((1 - t) * 2) / 2;
};
}
}
var ease = Easing.bezier(0.42, 0, 1, 1);
module.exports = Easing;

View File

@@ -6,9 +6,15 @@
* @flow
*/
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import invariant from 'fbjs/lib/invariant'
const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection
const connection = ExecutionEnvironment.canUseDOM && (
window.navigator.connection ||
window.navigator.mozConnection ||
window.navigator.webkitConnection
)
const eventTypes = [ 'change' ]
/**
@@ -50,8 +56,8 @@ const NetInfo = {
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(true), false)
window.addEventListener('offline', handler.bind(false), false)
window.addEventListener('online', handler.bind(null, true), false)
window.addEventListener('offline', handler.bind(null, false), false)
return {
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
@@ -60,8 +66,8 @@ const NetInfo = {
removeEventListener(type: string, handler: Function): void {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type)
window.removeEventListener('online', handler.bind(true), false)
window.removeEventListener('offline', handler.bind(false), false)
window.removeEventListener('online', handler.bind(null, true), false)
window.removeEventListener('offline', handler.bind(null, false), false)
},
fetch(): Promise {

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 './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,8 +1,6 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'
const Platform = {
OS: 'web',
userAgent: canUseDOM ? window.navigator.userAgent : ''
select: (obj: Object) => obj.web
}
module.exports = Platform

View File

@@ -1,99 +0,0 @@
import prefixAll from 'inline-style-prefix-all'
import hyphenate from './hyphenate'
class Store {
constructor(
initialState:Object = {},
options:Object = { obfuscateClassNames: false }
) {
this._counter = 0
this._classNames = { ...initialState.classNames }
this._declarations = { ...initialState.declarations }
this._options = options
}
get(property, value) {
const key = this._getDeclarationKey(property, value)
return this._classNames[key]
}
set(property, value) {
if (value != null) {
const values = this._getPropertyValues(property) || []
if (values.indexOf(value) === -1) {
values.push(value)
this._setClassName(property, value)
this._setPropertyValues(property, values)
}
}
}
toString() {
const obfuscate = this._options.obfuscateClassNames
// sort the properties to ensure shorthands are first in the cascade
const properties = Object.keys(this._declarations).sort()
// transform the class name to a valid CSS selector
const getCssSelector = (property, value) => {
let className = this.get(property, value)
if (!obfuscate && className) {
className = className.replace(/[(),":?.%\\$#]/g, '\\$&')
}
return className
}
// transform the declarations into CSS rules with vendor-prefixes
const buildCSSRules = (property, values) => {
return values.reduce((cssRules, value) => {
const declarations = prefixAll({ [property]: value })
const cssDeclarations = Object.keys(declarations).reduce((str, prop) => {
const value = declarations[prop]
str += `${hyphenate(prop)}:${value};`
return str
}, '')
const selector = getCssSelector(property, value)
cssRules += `\n.${selector}{${cssDeclarations}}`
return cssRules
}, '')
}
const css = properties.reduce((css, property) => {
const values = this._declarations[property]
css += buildCSSRules(property, values)
return css
}, '')
return (`/* ${this._counter} unique declarations */${css}`)
}
_getDeclarationKey(property, value) {
return `${property}:${value}`
}
_getPropertyValues(property) {
return this._declarations[property]
}
_setPropertyValues(property, values) {
this._declarations[property] = values.map(value => value)
}
_setClassName(property, value) {
const key = this._getDeclarationKey(property, value)
const exists = !!this._classNames[key]
if (!exists) {
this._counter += 1
if (this._options.obfuscateClassNames) {
this._classNames[key] = `_s_${this._counter}`
} else {
const val = `${value}`.replace(/\s/g, '-')
this._classNames[key] = `${property}:${val}`
}
}
}
}
module.exports = Store

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import prefixAll from 'inline-style-prefix-all'
import flattenStyle from './flattenStyle'
import processTransform from './processTransform'
class StyleSheetRegistry {
static registerStyle(style: Object, store): number {
if (process.env.NODE_ENV !== 'production') {
Object.freeze(style)
}
const normalizedStyle = processTransform(flattenStyle(style))
Object.keys(normalizedStyle).forEach((prop) => {
// add each declaration to the store
store.set(prop, normalizedStyle[prop])
})
}
static getStyleAsNativeProps(style, store) {
let _className
let _style = {}
const classList = []
const normalizedStyle = processTransform(flattenStyle(style))
for (const prop in normalizedStyle) {
let styleClass = store.get(prop, normalizedStyle[prop])
if (styleClass) {
classList.push(styleClass)
} else {
_style[prop] = normalizedStyle[prop]
}
}
_className = classList.join(' ')
_style = prefixAll(_style)
return { className: _className, style: _style }
}
}
module.exports = StyleSheetRegistry

View File

@@ -10,19 +10,22 @@ import { PropTypes } from 'react'
import ImageStylePropTypes from '../../components/Image/ImageStylePropTypes'
import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
import invariant from 'fbjs/lib/invariant'
import warning from 'fbjs/lib/warning'
const allStylePropTypes = {}
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 message1 = `"${prop}" is not a valid style property on Web.`
const message2 = '\nValid style props: ' + JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
styleError(message1, style, caller, message2)
}
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
if (error) {
styleError(error.message, style, caller)
} else {
const error = allStylePropTypes[prop](style, prop, caller, 'prop')
if (error) {
styleError(error.message, style, caller)
}
}
}
}
@@ -43,15 +46,13 @@ class StyleSheetValidation {
}
const styleError = (message1, style, caller, message2) => {
invariant(
warning(
false,
message1 + '\n' + (caller || '<<unknown>>') + ': ' +
JSON.stringify(style, null, ' ') + (message2 || '')
)
}
const allStylePropTypes = {}
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes)
@@ -63,8 +64,7 @@ StyleSheetValidation.addValidStylePropTypes({
direction: PropTypes.string, /* @private */
float: PropTypes.oneOf([ 'left', 'none', 'right' ]),
font: PropTypes.string, /* @private */
listStyle: PropTypes.string,
verticalAlign: PropTypes.string
listStyle: PropTypes.string
})
module.exports = StyleSheetValidation

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import { PropTypes } from 'react'
const ArrayOfNumberPropType = PropTypes.arrayOf(PropTypes.number)
const numberOrString = PropTypes.oneOfType([ PropTypes.number, PropTypes.string ])
const TransformMatrixPropType = function (
props : Object,
propName : string,
componentName : string
) : ?Error {
if (props.transform && props.transformMatrix) {
return new Error(
'transformMatrix and transform styles cannot be used on the same ' +
'component'
)
}
return ArrayOfNumberPropType(props, propName, componentName)
}
const TransformPropTypes = {
transform: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.shape({ perspective: numberOrString }),
PropTypes.shape({ rotate: numberOrString }),
PropTypes.shape({ rotateX: numberOrString }),
PropTypes.shape({ rotateY: numberOrString }),
PropTypes.shape({ rotateZ: numberOrString }),
PropTypes.shape({ scale: numberOrString }),
PropTypes.shape({ scaleX: numberOrString }),
PropTypes.shape({ scaleY: numberOrString }),
PropTypes.shape({ skewX: numberOrString }),
PropTypes.shape({ skewY: numberOrString }),
PropTypes.shape({ translateX: numberOrString }),
PropTypes.shape({ translateY: numberOrString }),
PropTypes.shape({ translateZ: numberOrString }),
PropTypes.shape({ translate3d: PropTypes.string })
])
),
transformMatrix: TransformMatrixPropType
}
module.exports = TransformPropTypes

View File

@@ -1,116 +0,0 @@
/* eslint-env mocha */
import assert from 'assert'
import Store from '../Store'
suite('apis/StyleSheet/Store', () => {
suite('the constructor', () => {
test('initialState', () => {
const initialState = { classNames: { 'textAlign:center': '__classname__' } }
const store = new Store(initialState)
assert.deepEqual(store._classNames['textAlign:center'], '__classname__')
})
})
suite('#get', () => {
test('returns a declaration-specific className', () => {
const initialState = {
classNames: {
'textAlign:center': '__expected__',
'textAlign:left': '__error__'
}
}
const store = new Store(initialState)
assert.deepEqual(store.get('textAlign', 'center'), '__expected__')
})
})
suite('#set', () => {
test('stores declarations', () => {
const store = new Store()
store.set('textAlign', 'center')
store.set('marginTop', 0)
store.set('marginTop', 1)
store.set('marginTop', 2)
assert.deepEqual(store._declarations, {
textAlign: [ 'center' ],
marginTop: [ 0, 1, 2 ]
})
})
test('human-readable classNames', () => {
const store = new Store()
store.set('textAlign', 'center')
store.set('marginTop', 0)
store.set('marginTop', 1)
store.set('marginTop', 2)
assert.deepEqual(store._classNames, {
'textAlign:center': 'textAlign:center',
'marginTop:0': 'marginTop:0',
'marginTop:1': 'marginTop:1',
'marginTop:2': 'marginTop:2'
})
})
test('obfuscated classNames', () => {
const store = new Store({}, { obfuscateClassNames: true })
store.set('textAlign', 'center')
store.set('marginTop', 0)
store.set('marginTop', 1)
store.set('marginTop', 2)
assert.deepEqual(store._classNames, {
'textAlign:center': '_s_1',
'marginTop:0': '_s_2',
'marginTop:1': '_s_3',
'marginTop:2': '_s_4'
})
})
test('replaces space characters', () => {
const store = new Store()
store.set('backgroundPosition', 'top left')
assert.equal(store.get('backgroundPosition', 'top left'), 'backgroundPosition\:top-left')
})
})
suite('#toString', () => {
test('human-readable style sheet', () => {
const store = new Store()
store.set('textAlign', 'center')
store.set('backgroundColor', 'rgba(0,0,0,0)')
store.set('color', '#fff')
store.set('fontFamily', '"Helvetica Neue", Arial, sans-serif')
store.set('marginBottom', '0px')
store.set('width', '100%')
const expected = '/* 6 unique declarations */\n' +
'.backgroundColor\\:rgba\\(0\\,0\\,0\\,0\\){background-color:rgba(0,0,0,0);}\n' +
'.color\\:\\#fff{color:#fff;}\n' +
'.fontFamily\\:\\"Helvetica-Neue\\"\\,-Arial\\,-sans-serif{font-family:"Helvetica Neue", Arial, sans-serif;}\n' +
'.marginBottom\\:0px{margin-bottom:0px;}\n' +
'.textAlign\\:center{text-align:center;}\n' +
'.width\\:100\\%{width:100%;}'
assert.equal(store.toString(), expected)
})
test('obfuscated style sheet', () => {
const store = new Store({}, { obfuscateClassNames: true })
store.set('textAlign', 'center')
store.set('marginBottom', '0px')
store.set('margin', '1px')
store.set('margin', '2px')
store.set('margin', '3px')
const expected = '/* 5 unique declarations */\n' +
'._s_3{margin:1px;}\n' +
'._s_4{margin:2px;}\n' +
'._s_5{margin:3px;}\n' +
'._s_2{margin-bottom:0px;}\n' +
'._s_1{text-align:center;}'
assert.equal(store.toString(), expected)
})
})
})

View File

@@ -0,0 +1,13 @@
/* eslint-env mocha */
import assert from 'assert'
import createReactStyleObject from '../createReactStyleObject'
suite('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 }
assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle)
})
})

View File

@@ -4,20 +4,29 @@ import assert from 'assert'
import expandStyle from '../expandStyle'
suite('apis/StyleSheet/expandStyle', () => {
test('style resolution', () => {
test('shortform -> longform', () => {
const initial = {
borderTopWidth: 1,
borderWidth: 2,
borderStyle: 'solid',
boxSizing: 'border-box',
borderBottomColor: 'white',
borderBottomWidth: 1,
borderWidth: 0,
marginTop: 50,
marginVertical: 25,
margin: 10
}
const expected = {
borderTopWidth: '1px',
borderLeftWidth: '2px',
borderRightWidth: '2px',
borderBottomWidth: '2px',
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',
@@ -27,6 +36,18 @@ suite('apis/StyleSheet/expandStyle', () => {
assert.deepEqual(expandStyle(initial), expected)
})
test('textAlignVertical', () => {
const initial = {
textAlignVertical: 'center'
}
const expected = {
verticalAlign: 'middle'
}
assert.deepEqual(expandStyle(initial), expected)
})
test('flex', () => {
const value = 10

View File

@@ -1,16 +0,0 @@
/* eslint-env mocha */
import assert from 'assert'
import hyphenate from '../hyphenate'
suite('apis/StyleSheet/hyphenate', () => {
test('style property', () => {
assert.equal(hyphenate('alignItems'), 'align-items')
assert.equal(hyphenate('color'), 'color')
})
test('vendor prefixed style property', () => {
assert.equal(hyphenate('MozTransition'), '-moz-transition')
assert.equal(hyphenate('msTransition'), '-ms-transition')
assert.equal(hyphenate('WebkitTransition'), '-webkit-transition')
})
})

View File

@@ -1,66 +1,71 @@
/* eslint-env mocha */
import { resetCSS, predefinedCSS } from '../predefs'
import assert from 'assert'
import { getDefaultStyleSheet } from '../css'
import isPlainObject from 'lodash/isPlainObject'
import StyleSheet from '..'
const styles = { root: { borderWidth: 1 } }
suite('apis/StyleSheet', () => {
setup(() => {
StyleSheet._destroy()
StyleSheet._reset()
})
test('absoluteFill', () => {
assert(Number.isInteger(StyleSheet.absoluteFill) === true)
})
test('absoluteFillObject', () => {
assert.ok(isPlainObject(StyleSheet.absoluteFillObject) === true)
})
suite('create', () => {
const div = document.createElement('div')
setup(() => {
document.body.appendChild(div)
StyleSheet.create(styles)
div.innerHTML = `<style id='${StyleSheet.elementId}'>${StyleSheet._renderToString()}</style>`
test('replaces styles with numbers', () => {
const style = StyleSheet.create({ root: { opacity: 1 } })
assert(Number.isInteger(style.root) === true)
})
teardown(() => {
document.body.removeChild(div)
})
test('returns styles object', () => {
assert.equal(StyleSheet.create(styles), styles)
})
test('updates already-rendered style sheet', () => {
test('renders a style sheet in the browser', () => {
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
document.getElementById(StyleSheet.elementId).textContent,
`${resetCSS}\n${predefinedCSS}\n` +
`/* 5 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}\n` +
`.color\\:red{color:red;}`
document.getElementById('__react-native-style').textContent,
getDefaultStyleSheet()
)
})
})
test('resolve', () => {
const props = { style: styles.root }
const expected = { className: 'borderTopWidth:1px borderRightWidth:1px borderBottomWidth:1px borderLeftWidth:1px', style: {} }
StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected)
test('flatten', () => {
assert(typeof StyleSheet.flatten === 'function')
})
test('_renderToString', () => {
StyleSheet.create(styles)
test('hairlineWidth', () => {
assert(Number.isInteger(StyleSheet.hairlineWidth) === true)
})
test('render', () => {
assert.equal(
StyleSheet._renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
StyleSheet.render().props.dangerouslySetInnerHTML.__html,
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: null,
opacity: 1,
pointerEvents: null
}
}
)
})
})

View File

@@ -9,5 +9,6 @@ suite('apis/StyleSheet/normalizeValue', () => {
})
test('ignores unitless property values', () => {
assert.deepEqual(normalizeValue('flexGrow', 1), 1)
assert.deepEqual(normalizeValue('scale', 2), 2)
})
})

View File

@@ -0,0 +1,24 @@
/* eslint-env mocha */
import assert from 'assert'
import processTextShadow from '../processTextShadow'
suite('apis/StyleSheet/processTextShadow', () => {
test('textShadowOffset', () => {
const style = {
textShadowColor: 'red',
textShadowOffset: { width: 2, height: 2 },
textShadowRadius: 5
}
assert.deepEqual(
processTextShadow(style),
{
textShadow: '2px 2px 5px red',
textShadowColor: null,
textShadowOffset: null,
textShadowRadius: null
}
)
})
})

View File

@@ -0,0 +1,35 @@
/* eslint-env mocha */
import assert from 'assert'
import processTransform from '../processTransform'
suite('apis/StyleSheet/processTransform', () => {
test('transform', () => {
const style = {
transform: [
{ scaleX: 20 },
{ translateX: 20 },
{ rotate: '20deg' }
]
}
assert.deepEqual(
processTransform(style),
{ 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)',
transformMatrix: null
}
)
})
})

View File

@@ -0,0 +1,17 @@
/* eslint-env mocha */
import assert from 'assert'
import processVendorPrefixes from '../processVendorPrefixes'
suite('apis/StyleSheet/processVendorPrefixes', () => {
test('handles array values', () => {
const style = {
display: [ '-webkit-flex', 'flex' ]
}
assert.deepEqual(
processVendorPrefixes(style),
{ display: 'flex' }
)
})
})

View File

@@ -0,0 +1,17 @@
import expandStyle from './expandStyle'
import flattenStyle from '../../modules/flattenStyle'
import processTextShadow from './processTextShadow'
import processTransform from './processTransform'
import processVendorPrefixes from './processVendorPrefixes'
const plugins = [
processTextShadow,
processTransform,
processVendorPrefixes
]
const applyPlugins = (style) => plugins.reduce((style, plugin) => plugin(style), style)
const createReactDOMStyleObject = (reactNativeStyle) => applyPlugins(expandStyle(flattenStyle(reactNativeStyle)))
module.exports = createReactDOMStyleObject

View File

@@ -0,0 +1,38 @@
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'
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[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-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}`
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

@@ -1,6 +1,18 @@
/**
* The browser implements the CSS cascade, where the order of properties is a
* factor in determining which styles to paint. React Native is different in
* giving precedence to the more specific styles. For example, the value of
* `paddingTop` takes precedence over that of `padding`.
*
* This module creates mutally exclusive style declarations by expanding all of
* React Native's supported shortform properties (e.g. `padding`) to their
* longfrom equivalents.
*/
import normalizeValue from './normalizeValue'
const styleShortHands = {
const emptyObject = {}
const styleShortFormProperties = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ],
@@ -8,52 +20,54 @@ const styleShortHands = {
margin: [ 'marginTop', 'marginRight', 'marginBottom', 'marginLeft' ],
marginHorizontal: [ 'marginRight', 'marginLeft' ],
marginVertical: [ 'marginTop', 'marginBottom' ],
overflow: [ 'overflowX', 'overflowY' ],
padding: [ 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft' ],
paddingHorizontal: [ 'paddingRight', 'paddingLeft' ],
paddingVertical: [ 'paddingTop', 'paddingBottom' ],
textDecorationLine: [ 'textDecoration' ],
writingDirection: [ 'direction' ]
}
/**
* Alpha-sort properties, apart from shorthands which appear before the
* properties they expand into. This ensures that more specific styles override
* the shorthands, whatever the order in which they were originally declared.
*/
const sortProps = (propsArray) => propsArray.sort((a, b) => {
const expandedA = styleShortHands[a]
const expandedB = styleShortHands[b]
if (expandedA && expandedA.indexOf(b) > -1) {
return -1
} else if (expandedB && expandedB.indexOf(a) > -1) {
return 1
}
return a < b ? -1 : a > b ? 1 : 0
const alphaSort = (arr) => arr.sort((a, b) => {
if (a < b) { return -1 }
if (a > b) { return 1 }
return 0
})
/**
* Expand the shorthand properties to isolate every declaration from the others.
*/
const expandStyle = (style) => {
const propsArray = Object.keys(style)
const sortedProps = sortProps(propsArray)
const createStyleReducer = (originalStyle) => {
const originalStyleProps = Object.keys(originalStyle)
return sortedProps.reduce((resolvedStyle, key) => {
const expandedProps = styleShortHands[key]
const value = normalizeValue(key, style[key])
return (style, prop) => {
const value = normalizeValue(prop, originalStyle[prop])
const longFormProperties = styleShortFormProperties[prop]
if (expandedProps) {
expandedProps.forEach((prop, i) => {
resolvedStyle[expandedProps[i]] = value
// React Native treats `flex:1` like `flex:1 1 auto`
if (prop === 'flex') {
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)
} 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
}
})
} else if (key === 'flex') {
resolvedStyle.flexGrow = value
resolvedStyle.flexShrink = 1
resolvedStyle.flexBasis = 'auto'
} else {
resolvedStyle[key] = value
style[prop] = value
}
return resolvedStyle
}, {})
return style
}
}
const expandStyle = (style = emptyObject) => {
const sortedStyleProps = alphaSort(Object.keys(style))
const styleReducer = createStyleReducer(style)
return sortedStyleProps.reduce(styleReducer, {})
}
module.exports = expandStyle

View File

@@ -1,34 +0,0 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import invariant from 'fbjs/lib/invariant'
import expandStyle from './expandStyle'
module.exports = function flattenStyle(style): ?Object {
if (!style) {
return undefined
}
invariant(style !== true, 'style may be false but not true')
if (!Array.isArray(style)) {
// we must expand styles during the flattening because expanded styles
// override shorthands
return expandStyle(style)
}
const result = {}
for (let i = 0; i < style.length; ++i) {
const computedStyle = flattenStyle(style[i])
if (computedStyle) {
for (const key in computedStyle) {
result[key] = computedStyle[key]
}
}
}
return result
}

View File

@@ -1 +0,0 @@
module.exports = (string) => (string.replace(/([A-Z])/g, '-$1').toLowerCase()).replace(/^ms-/, '-ms-')

View File

@@ -1,76 +1,88 @@
import { resetCSS, predefinedCSS, predefinedClassNames } from './predefs'
import flattenStyle from './flattenStyle'
import Store from './Store'
import StyleSheetRegistry from './StyleSheetRegistry'
import * as css from './css'
import createReactStyleObject from './createReactStyleObject'
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
import flattenStyle from '../../modules/flattenStyle'
import React from 'react'
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'
import StyleSheetValidation from './StyleSheetValidation'
const ELEMENT_ID = 'react-stylesheet'
let isRendered = false
let lastStyleSheet = ''
let styleElement
let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM
/**
* Initialize the store with pointer-event styles mapping to our custom pointer
* event classes
*/
const initialState = { classNames: predefinedClassNames }
const options = { obfuscateClassNames: !(process.env.NODE_ENV !== 'production') }
const createStore = () => new Store(initialState, options)
let store = createStore()
const STYLE_SHEET_ID = '__react-native-style'
/**
* Destroy existing styles
*/
const _destroy = () => {
store = createStore()
isRendered = false
}
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }
/**
* Render the styles as a CSS style sheet
*/
const _renderToString = () => {
const css = store.toString()
isRendered = true
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
const defaultStyleSheet = css.getDefaultStyleSheet()
const create = (styles: Object): Object => {
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
StyleSheetRegistry.registerStyle(styles[key], store)
const insertStyleSheet = () => {
// check if the server rendered the style sheet
styleElement = document.getElementById(STYLE_SHEET_ID)
// if not, inject the style sheet
if (!styleElement) {
document.head.insertAdjacentHTML(
'afterbegin',
`<style id="${STYLE_SHEET_ID}">${defaultStyleSheet}</style>`
)
shouldInsertStyleSheet = false
}
// update the style sheet in place
if (isRendered) {
const stylesheet = document.getElementById(ELEMENT_ID)
if (stylesheet) {
const newStyleSheet = _renderToString()
if (lastStyleSheet !== newStyleSheet) {
stylesheet.textContent = newStyleSheet
lastStyleSheet = newStyleSheet
}
} else if (process.env.NODE_ENV !== 'production') {
console.error('ReactNative: cannot find "react-stylesheet" element')
}
}
return styles
}
/**
* Accepts React props and converts inline styles to single purpose classes
* where possible.
*/
const resolve = ({ style = {} }) => {
return StyleSheetRegistry.getStyleAsNativeProps(style, store)
}
module.exports = {
_destroy,
_renderToString,
create,
elementId: ELEMENT_ID,
/**
* For testing
* @private
*/
_reset() {
if (styleElement) {
document.head.removeChild(styleElement)
styleElement = null
shouldInsertStyleSheet = true
}
},
absoluteFill: ReactNativePropRegistry.register(absoluteFillObject),
absoluteFillObject,
create(styles) {
if (shouldInsertStyleSheet) {
insertStyleSheet()
}
const result = {}
for (const key in styles) {
StyleSheetValidation.validateStyle(key, styles)
result[key] = ReactNativePropRegistry.register(styles[key])
}
return result
},
hairlineWidth: 1,
flatten: flattenStyle,
resolve
/* @platform web */
render() {
return <style dangerouslySetInnerHTML={{ __html: defaultStyleSheet }} id={STYLE_SHEET_ID} />
},
/**
* Accepts React props and converts style declarations to classNames when necessary
* @platform web
*/
resolve(props) {
let className = props.className || ''
let style = createReactStyleObject(props.style)
for (const prop in style) {
const value = style[prop]
const replacementClassName = css.getStyleAsHelperClassName(prop, value)
if (replacementClassName) {
className += ` ${replacementClassName}`
style[prop] = null
}
}
return { className, style }
}
}

View File

@@ -9,7 +9,6 @@ const unitlessNumbers = {
flexNegative: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
opacity: true,
order: true,
orphans: true,
@@ -20,7 +19,12 @@ const unitlessNumbers = {
fillOpacity: true,
strokeDashoffset: true,
strokeOpacity: true,
strokeWidth: true
strokeWidth: true,
// transform types
scale: true,
scaleX: true,
scaleY: true,
scaleZ: true
}
const normalizeValue = (property, value) => {

View File

@@ -1,24 +0,0 @@
/**
* Reset unwanted styles beyond the control of React inline styles
*/
export const resetCSS =
`/* React Native Web */
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 {-webkit-appearance:none}`
/**
* Custom pointer event styles
*/
export const predefinedCSS =
`/* pointer-events */
._s_pe-a, ._s_pe-bo, ._s_pe-bn * {pointer-events:auto}
._s_pe-n, ._s_pe-bo *, ._s_pe-bn {pointer-events:none}`
export const predefinedClassNames = {
'pointerEvents:auto': '_s_pe-a',
'pointerEvents:box-none': '_s_pe-bn',
'pointerEvents:box-only': '_s_pe-bo',
'pointerEvents:none': '_s_pe-n'
}

View File

@@ -0,0 +1,19 @@
import normalizeValue from './normalizeValue'
const processTextShadow = (style) => {
if (style && style.textShadowOffset) {
const { height, width } = style.textShadowOffset
const offsetX = normalizeValue(null, height || 0)
const offsetY = normalizeValue(null, width || 0)
const blurRadius = normalizeValue(null, style.textShadowRadius || 0)
const color = style.textShadowColor || 'currentcolor'
style.textShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`
style.textShadowColor = null
style.textShadowOffset = null
style.textShadowRadius = null
}
return style
}
module.exports = processTextShadow

View File

@@ -1,7 +1,11 @@
import normalizeValue from './normalizeValue'
// { scale: 2 } => 'scale(2)'
// { translateX: 20 } => 'translateX(20px)'
const mapTransform = (transform) => {
var key = Object.keys(transform)[0]
return `${key}(${transform[key]})`
const type = Object.keys(transform)[0]
const value = normalizeValue(type, transform[type])
return `${type}(${value})`
}
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
@@ -15,7 +19,8 @@ const processTransform = (style) => {
if (style.transform) {
style.transform = style.transform.map(mapTransform).join(' ')
} else if (style.transformMatrix) {
style.transformMatrix = convertTransformMatrix(style.transformMatrix)
style.transform = convertTransformMatrix(style.transformMatrix)
style.transformMatrix = null
}
}
return style

View File

@@ -0,0 +1,16 @@
import prefixAll from 'inline-style-prefixer/static'
const processVendorPrefixes = (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
}
module.exports = processVendorPrefixes

View File

@@ -94,19 +94,26 @@ suite('apis/UIManager', () => {
})
suite('updateView', () => {
const componentStub = {
_reactInternalInstance: {
_currentElement: { _owner: {} },
_debugID: 1
}
}
test('add new className to existing className', () => {
const node = createNode()
node.className = 'existing'
const props = { className: 'extra' }
UIManager.updateView(node, props)
UIManager.updateView(node, props, componentStub)
assert.equal(node.getAttribute('class'), 'existing extra')
})
test('adds new style to existing style', () => {
test('adds correct DOM styles to existing style', () => {
const node = createNode({ color: 'red' })
const props = { style: { opacity: 0 } }
UIManager.updateView(node, props)
assert.equal(node.getAttribute('style'), 'color: red; opacity: 0;')
const props = { style: { marginVertical: 0, opacity: 0 } }
UIManager.updateView(node, props, componentStub)
assert.equal(node.getAttribute('style'), 'color: red; margin-top: 0px; margin-bottom: 0px; opacity: 0;')
})
test('replaces input and textarea text', () => {

View File

@@ -1,6 +1,5 @@
import createReactStyleObject from '../StyleSheet/createReactStyleObject'
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations'
import flattenStyle from '../StyleSheet/flattenStyle'
import processTransform from '../StyleSheet/processTransform'
const _measureLayout = (node, relativeToNativeNode, callback) => {
const relativeNode = relativeToNativeNode || node.parentNode
@@ -34,23 +33,27 @@ const UIManager = {
_measureLayout(node, relativeTo, onSuccess)
},
updateView(node, props) {
updateView(node, props, component /* only needed to surpress React errors in development */) {
for (const prop in props) {
let nativeProp
const value = props[prop]
switch (prop) {
case 'style':
// convert styles to DOM-styles
CSSPropertyOperations.setValueForStyles(node, processTransform(flattenStyle(value)))
CSSPropertyOperations.setValueForStyles(
node,
createReactStyleObject(value),
component._reactInternalInstance
)
break
case 'class':
case 'className':
nativeProp = 'class'
case 'className': {
const nativeProp = 'class'
// prevent class names managed by React Native from being replaced
const className = node.getAttribute(nativeProp) + ' ' + value
node.setAttribute(nativeProp, className)
break
}
case 'text':
case 'value':
// native platforms use `text` prop to replace text input value

View File

@@ -0,0 +1,20 @@
const vibrate = (pattern) => {
if ('vibrate' in window.navigator) {
if (typeof pattern === 'number' || Array.isArray(pattern)) {
window.navigator.vibrate(pattern)
} else {
throw new Error('Vibration pattern should be a number or array')
}
}
}
const Vibration = {
cancel() {
vibrate(0)
},
vibrate(pattern) {
vibrate(pattern)
}
}
module.exports = Vibration

View File

@@ -1,4 +1,4 @@
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import applyNativeMethods from '../../modules/applyNativeMethods'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
@@ -19,7 +19,6 @@ const keyframeEffects = [
{ transform: 'scale(0.95)', opacity: 0.5 }
]
@NativeMethodsDecorator
class ActivityIndicator extends Component {
static propTypes = {
animating: PropTypes.bool,
@@ -61,7 +60,7 @@ class ActivityIndicator extends Component {
return (
<View {...other} style={[ styles.container, style ]}>
<View
ref={(c) => { this._indicatorRef = c }}
ref={this._createIndicatorRef}
style={[
indicatorStyles[size],
hidesWhenStopped && !animating && styles.hidesWhenStopped,
@@ -72,6 +71,10 @@ class ActivityIndicator extends Component {
)
}
_createIndicatorRef = (component) => {
this._indicatorRef = component
}
_manageAnimation() {
if (this._player) {
if (this.props.animating) {
@@ -83,6 +86,8 @@ class ActivityIndicator extends Component {
}
}
applyNativeMethods(ActivityIndicator)
const styles = StyleSheet.create({
container: {
alignItems: 'center',

View File

@@ -1,62 +0,0 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import CoreComponent from '../'
suite('components/CoreComponent', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const dom = utils.renderToDOM(<CoreComponent accessibilityLabel={accessibilityLabel} />)
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const dom = utils.renderToDOM(<CoreComponent accessibilityLiveRegion={accessibilityLiveRegion} />)
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'banner'
let dom = utils.renderToDOM(<CoreComponent accessibilityRole={accessibilityRole} />)
assert.equal(dom.getAttribute('role'), accessibilityRole)
assert.equal((dom.tagName).toLowerCase(), 'header')
const button = 'button'
dom = utils.renderToDOM(<CoreComponent accessibilityRole={button} />)
assert.equal(dom.getAttribute('type'), button)
assert.equal((dom.tagName).toLowerCase(), button)
})
test('prop "accessible"', () => {
// accessible (implicit)
let dom = utils.renderToDOM(<CoreComponent />)
assert.equal(dom.getAttribute('aria-hidden'), null)
// accessible (explicit)
dom = utils.renderToDOM(<CoreComponent accessible />)
assert.equal(dom.getAttribute('aria-hidden'), null)
// not accessible
dom = utils.renderToDOM(<CoreComponent accessible={false} />)
assert.equal(dom.getAttribute('aria-hidden'), 'true')
})
test('prop "component"', () => {
const component = 'main'
const dom = utils.renderToDOM(<CoreComponent component={component} />)
const tagName = (dom.tagName).toLowerCase()
assert.equal(tagName, component)
})
test('prop "testID"', () => {
// no testID
let dom = utils.renderToDOM(<CoreComponent />)
assert.equal(dom.getAttribute('data-testid'), null)
// with testID
const testID = 'Example.testID'
dom = utils.renderToDOM(<CoreComponent testID={testID} />)
assert.equal(dom.getAttribute('data-testid'), testID)
})
})

View File

@@ -1,68 +0,0 @@
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
const roleComponents = {
article: 'article',
banner: 'header',
button: 'button',
complementary: 'aside',
contentinfo: 'footer',
form: 'form',
heading: 'h1',
link: 'a',
list: 'ul',
listitem: 'li',
main: 'main',
navigation: 'nav',
region: 'section'
}
@NativeMethodsDecorator
class CoreComponent extends Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
style: PropTypes.oneOfType([ PropTypes.array, PropTypes.object ]),
testID: PropTypes.string,
type: PropTypes.string
};
static defaultProps = {
accessible: true,
component: 'div'
};
render() {
const {
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible,
component,
testID,
type,
...other
} = this.props
const Component = roleComponents[accessibilityRole] || component
return (
<Component
{...other}
{...StyleSheet.resolve(other)}
aria-hidden={accessible ? null : true}
aria-label={accessibilityLabel}
aria-live={accessibilityLiveRegion}
data-testid={testID}
role={accessibilityRole}
type={accessibilityRole === 'button' ? 'button' : type}
/>
)
}
}
module.exports = CoreComponent

View File

@@ -1,9 +1,11 @@
import keyMirror from 'fbjs/lib/keyMirror'
const ImageResizeMode = keyMirror({
center: null,
contain: null,
cover: null,
none: null,
repeat: null,
stretch: null
})

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