mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 17:34:05 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
011affb110 | ||
|
|
87a4f56a89 | ||
|
|
2f94295739 | ||
|
|
fdf4a88251 | ||
|
|
acc7032d60 | ||
|
|
0fc5644959 | ||
|
|
be923ec453 | ||
|
|
248c2e1258 | ||
|
|
2e822c068d | ||
|
|
fb5406e4ec | ||
|
|
638479991e |
67
README.md
67
README.md
@@ -7,23 +7,16 @@
|
||||
|
||||
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
|
||||
[npm-image]: https://badge.fury.io/js/react-native-web.svg
|
||||
[npm-url]: https://npmjs.org/package/react-native-web
|
||||
[react-native-url]: https://facebook.github.io/react-native/
|
||||
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/necolas/react-native-web
|
||||
|
||||
## Overview
|
||||
|
||||
"React Native for Web" is a project to bring React Native's building blocks and
|
||||
touch handling to the Web.
|
||||
|
||||
React Native – unlike React DOM – is a comprehensive UI framework for
|
||||
application developers. React Native's components are higher-level building
|
||||
blocks than those provided by React DOM. React Native also provides
|
||||
platform-agnostic JavaScript APIs for animating and styling components,
|
||||
responding to touch events, and interacting with the host environment.
|
||||
|
||||
Bringing the React Native APIs and components to the Web allows React Native
|
||||
components to be run on the Web platform. But it also solves several problems
|
||||
facing the React DOM ecosystem: no framework-level animation or styling
|
||||
solution; difficulty sharing and composing UI components (without pulling in
|
||||
their build or runtime dependencies); and the lack of high-level base
|
||||
components.
|
||||
touch handling to the Web. [Read more](#why).
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -40,11 +33,8 @@ using [react-native-web-starter](https://github.com/grabcode/react-native-web-st
|
||||
|
||||
## Examples
|
||||
|
||||
Demos:
|
||||
|
||||
* React Native [examples running on Web](https://necolas.github.io/react-native-web/storybook/)
|
||||
* [React Native for Web: Playground](http://codepen.io/necolas/pen/PZzwBR).
|
||||
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
|
||||
* [2048](http://codepen.io/necolas/full/wMVvxj/)
|
||||
|
||||
Sample:
|
||||
|
||||
@@ -103,6 +93,7 @@ Exported modules:
|
||||
* [`Image`](docs/components/Image.md)
|
||||
* [`ListView`](docs/components/ListView.md)
|
||||
* [`ScrollView`](docs/components/ScrollView.md)
|
||||
* [`Switch`](docs/components/Switch.md)
|
||||
* [`Text`](docs/components/Text.md)
|
||||
* [`TextInput`](docs/components/TextInput.md)
|
||||
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
|
||||
@@ -124,12 +115,40 @@ Exported modules:
|
||||
* [`StyleSheet`](docs/apis/StyleSheet.md)
|
||||
* [`Vibration`](docs/apis/Vibration.md)
|
||||
|
||||
<span id="#why"></span>
|
||||
## Why?
|
||||
|
||||
React Native is a comprehensive JavaScript framework for building application
|
||||
user interfaces. It provides high-level, platform-agnostic components and APIs
|
||||
– e.g., `Text`, `View`, `Touchable*`, `Animated`, `StyleSheet` - that simplify
|
||||
working with layout, gestures, animations, and styles. The entire React Native
|
||||
ecosystem can depend on these shared building blocks.
|
||||
|
||||
In contrast, the React DOM ecosystem is limited by the lack of a higher-level
|
||||
framework. At Twitter, we want to seamlessly author and share React component
|
||||
libraries between different Web applications (with increasing interest from
|
||||
product teams for multi-platform solutions). This goal draws together multiple,
|
||||
inter-related problems including: styling, animation, gestures, themes,
|
||||
viewport adaptation, accessibility, diverse build processes, and RTL layouts.
|
||||
|
||||
Almost all these problems are avoided, solved, or can be solved in React
|
||||
Native. Central to this is React Native's JavaScript style API (not strictly
|
||||
"CSS-in-JS") which avoids the key [problems with
|
||||
CSS](https://speakerdeck.com/vjeux/react-css-in-js). By giving up some of the
|
||||
complexity of CSS it also provides a reliable surface for style composition,
|
||||
animation, gestures, server-side rendering, RTL layout; and removes the
|
||||
requirement for CSS-specific build tools.
|
||||
|
||||
Bringing the React Native APIs and components to the Web has the added benefit
|
||||
of allowing teams to explore code-sharing between Native and Web platforms.
|
||||
|
||||
## Related projects
|
||||
|
||||
* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter)
|
||||
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
|
||||
* [react-web](https://github.com/taobaofed/react-web)
|
||||
* [react-native-for-web](https://github.com/KodersLab/react-native-for-web)
|
||||
|
||||
## License
|
||||
|
||||
React Native for Web is [BSD licensed](LICENSE).
|
||||
|
||||
[npm-image]: https://badge.fury.io/js/react-native-web.svg
|
||||
[npm-url]: https://npmjs.org/package/react-native-web
|
||||
[react-native-url]: https://facebook.github.io/react-native/
|
||||
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/necolas/react-native-web
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# I18nManager
|
||||
|
||||
Control and set the layout and writing direction of the application. You must
|
||||
set `dir="rtl"` (and should set `lang="${lang}"`) on the root element of your
|
||||
app.
|
||||
Control and set the layout and writing direction of the application.
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -16,12 +14,12 @@ static **allowRTL**(allowRTL: bool)
|
||||
|
||||
Allow the application to display in RTL mode.
|
||||
|
||||
static **forceRTL**(allowRTL: bool)
|
||||
static **forceRTL**(forceRTL: bool)
|
||||
|
||||
Force the application to display in RTL mode.
|
||||
|
||||
static **setRTL**(allowRTL: bool)
|
||||
static **setPreferredLanguageRTL**(isRTL: bool)
|
||||
|
||||
Set the application to display in RTL mode. You will need to determine the
|
||||
user's preferred locale and if it is an RTL language. (This is best done on the
|
||||
server as it is notoriously inaccurate to deduce client-side.)
|
||||
Set the application's preferred writing direction to RTL. You will need to
|
||||
determine the user's preferred locale server-side (from HTTP headers) and
|
||||
decide whether it's an RTL language.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed)
|
||||
CSS without requiring a compile-time step. Some styles cannot be resolved
|
||||
outside of the render loop and are applied as inline styles. Read more about to
|
||||
[how style your application](docs/guides/style).
|
||||
outside of the render loop and are applied as inline styles. Read more about
|
||||
[how to style your application](../guides/style.md).
|
||||
|
||||
## Methods
|
||||
|
||||
|
||||
76
docs/components/Switch.md
Normal file
76
docs/components/Switch.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Switch
|
||||
|
||||
This is a controlled component that requires an `onValueChange` callback that
|
||||
updates the value prop in order for the component to reflect user actions. If
|
||||
the `value` prop is not updated, the component will continue to render the
|
||||
supplied `value` prop instead of the expected result of any user actions.
|
||||
|
||||
## Props
|
||||
|
||||
[...View props](./View.md)
|
||||
|
||||
**disabled**: bool = false
|
||||
|
||||
If `true` the user won't be able to interact with the switch.
|
||||
|
||||
**onValueChange**: func
|
||||
|
||||
Invoked with the new value when the value changes.
|
||||
|
||||
**value**: bool = false
|
||||
|
||||
The value of the switch. If `true` the switch will be turned on.
|
||||
|
||||
(web) **activeThumbColor**: color = #009688
|
||||
|
||||
The color of the thumb grip when the switch is turned on.
|
||||
|
||||
(web) **activeTrackColor**: color = #A3D3CF
|
||||
|
||||
The color of the track when the switch is turned on.
|
||||
|
||||
(web) **thumbColor**: color = #FAFAFA
|
||||
|
||||
The color of the thumb grip when the switch is turned off.
|
||||
|
||||
(web) **trackColor**: color = #939393
|
||||
|
||||
The color of the track when the switch is turned off.
|
||||
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Component } from 'react'
|
||||
import { Switch, View } from 'react-native'
|
||||
|
||||
class ColorSwitchExample extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
colorTrueSwitchIsOn: true,
|
||||
colorFalseSwitchIsOn: false
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
activeThumbColor='#428BCA'
|
||||
activeTrackColor='#A0C4E3'
|
||||
onValueChange={(value) => this.setState({ colorFalseSwitchIsOn: value })}
|
||||
value={this.state.colorFalseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
activeThumbColor='#5CB85C'
|
||||
activeTrackColor='#ADDAAD'
|
||||
onValueChange={(value) => this.setState({ colorTrueSwitchIsOn: value })}
|
||||
thumbColor='#EBA9A7'
|
||||
trackColor='#D9534F'
|
||||
value={this.state.colorTrueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -406,6 +406,7 @@ const examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: 'Tint Color',
|
||||
description: 'The `tintColor` style prop changes all the non-alpha ' +
|
||||
@@ -456,6 +457,7 @@ const examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
*/
|
||||
{
|
||||
title: 'Resize Mode',
|
||||
description: 'The `resizeMode` style prop controls how the image is ' +
|
||||
|
||||
190
examples/Switch/SwitchExample.js
Normal file
190
examples/Switch/SwitchExample.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Platform, Switch, Text, View } from 'react-native'
|
||||
import React from 'react';
|
||||
import { storiesOf, action } from '@kadira/storybook';
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
var BasicSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
trueSwitchIsOn: true,
|
||||
falseSwitchIsOn: false,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.falseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
|
||||
value={this.state.trueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DisabledSwitchExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
disabled={true}
|
||||
style={{marginBottom: 10}}
|
||||
value={true} />
|
||||
<Switch
|
||||
disabled={true}
|
||||
value={false} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var ColorSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
colorTrueSwitchIsOn: true,
|
||||
colorFalseSwitchIsOn: false,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
activeThumbColor="#428bca"
|
||||
activeTrackColor="#A0C4E3"
|
||||
onValueChange={(value) => this.setState({colorFalseSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.colorFalseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
activeThumbColor="#5CB85C"
|
||||
activeTrackColor="#ADDAAD"
|
||||
onValueChange={(value) => this.setState({colorTrueSwitchIsOn: value})}
|
||||
thumbColor="#EBA9A7"
|
||||
trackColor="#D9534F"
|
||||
value={this.state.colorTrueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var EventSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
eventSwitchIsOn: false,
|
||||
eventSwitchRegressionIsOn: true,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
|
||||
<View>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchIsOn} />
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchIsOn} />
|
||||
<Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchRegressionIsOn} />
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchRegressionIsOn} />
|
||||
<Text>{this.state.eventSwitchRegressionIsOn ? 'On' : 'Off'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SizeSwitchExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
trueSwitchIsOn: true,
|
||||
falseSwitchIsOn: false,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
|
||||
style={{marginBottom: 10, height: '3rem' }}
|
||||
value={this.state.falseSwitchIsOn}
|
||||
/>
|
||||
<Switch
|
||||
onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
|
||||
style={{marginBottom: 10, width: 150 }}
|
||||
value={this.state.trueSwitchIsOn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var examples = [
|
||||
{
|
||||
title: 'set to true or false',
|
||||
render(): ReactElement<any> { return <BasicSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'disabled',
|
||||
render(): ReactElement<any> { return <DisabledSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'change events',
|
||||
render(): ReactElement<any> { return <EventSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'custom colors',
|
||||
render(): ReactElement<any> { return <ColorSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'custom size',
|
||||
render(): ReactElement<any> { return <SizeSwitchExample />; }
|
||||
},
|
||||
{
|
||||
title: 'controlled component',
|
||||
render(): ReactElement<any> { return <Switch />; }
|
||||
}
|
||||
];
|
||||
|
||||
examples.forEach((example) => {
|
||||
storiesOf('<Switch>', module)
|
||||
.add(example.title, () => example.render())
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.42",
|
||||
"version": "0.0.44",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
|
||||
@@ -6,32 +6,40 @@ import I18nManager from '..'
|
||||
suite('apis/I18nManager', () => {
|
||||
suite('when RTL not enabled', () => {
|
||||
setup(() => {
|
||||
I18nManager.setRTL(false)
|
||||
I18nManager.setPreferredLanguageRTL(false)
|
||||
})
|
||||
|
||||
test('is "false" by default', () => {
|
||||
assert.equal(I18nManager.isRTL, false)
|
||||
assert.equal(document.documentElement.getAttribute('dir'), 'ltr')
|
||||
})
|
||||
|
||||
test('is "true" when forced', () => {
|
||||
I18nManager.forceRTL(true)
|
||||
assert.equal(I18nManager.isRTL, true)
|
||||
assert.equal(document.documentElement.getAttribute('dir'), 'rtl')
|
||||
I18nManager.forceRTL(false)
|
||||
})
|
||||
})
|
||||
|
||||
suite('when RTL is enabled', () => {
|
||||
setup(() => {
|
||||
I18nManager.setRTL(true)
|
||||
I18nManager.setPreferredLanguageRTL(true)
|
||||
})
|
||||
|
||||
teardown(() => {
|
||||
I18nManager.setPreferredLanguageRTL(false)
|
||||
})
|
||||
|
||||
test('is "true" by default', () => {
|
||||
assert.equal(I18nManager.isRTL, true)
|
||||
assert.equal(document.documentElement.getAttribute('dir'), 'rtl')
|
||||
})
|
||||
|
||||
test('is "false" when not allowed', () => {
|
||||
I18nManager.allowRTL(false)
|
||||
assert.equal(I18nManager.isRTL, false)
|
||||
assert.equal(document.documentElement.getAttribute('dir'), 'ltr')
|
||||
I18nManager.allowRTL(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'
|
||||
|
||||
type I18nManagerStatus = {
|
||||
allowRTL: (allowRTL: boolean) => {},
|
||||
forceRTL: (forceRTL: boolean) => {},
|
||||
@@ -5,28 +7,38 @@ type I18nManagerStatus = {
|
||||
isRTL: boolean
|
||||
}
|
||||
|
||||
let isApplicationLanguageRTL = false
|
||||
let isPreferredLanguageRTL = false
|
||||
let isRTLAllowed = true
|
||||
let isRTLForced = false
|
||||
|
||||
const isRTL = () => {
|
||||
if (isRTLForced) {
|
||||
return true
|
||||
}
|
||||
return isRTLAllowed && isPreferredLanguageRTL
|
||||
}
|
||||
|
||||
const onChange = () => {
|
||||
if (ExecutionEnvironment.canUseDOM) {
|
||||
document.documentElement.setAttribute('dir', isRTL() ? 'rtl' : 'ltr')
|
||||
}
|
||||
}
|
||||
|
||||
const I18nManager: I18nManagerStatus = {
|
||||
allowRTL(bool) {
|
||||
isRTLAllowed = bool
|
||||
onChange()
|
||||
},
|
||||
forceRTL(bool) {
|
||||
isRTLForced = bool
|
||||
onChange()
|
||||
},
|
||||
setRTL(bool) {
|
||||
isApplicationLanguageRTL = bool
|
||||
setPreferredLanguageRTL(bool) {
|
||||
isPreferredLanguageRTL = bool
|
||||
onChange()
|
||||
},
|
||||
get isRTL() {
|
||||
if (isRTLForced) {
|
||||
return true
|
||||
}
|
||||
if (isRTLAllowed && isApplicationLanguageRTL) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return isRTL()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import I18nManager from '../I18nManager'
|
||||
|
||||
const CSS_UNIT_RE = /^[+-]?\d*(?:\.\d+)?(?:[Ee][+-]?\d+)?(\w*)/
|
||||
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue'
|
||||
|
||||
/**
|
||||
* Map of property names to their BiDi equivalent.
|
||||
@@ -37,15 +36,7 @@ const PROPERTIES_SWAP_LTR_RTL = {
|
||||
/**
|
||||
* Invert the sign of a numeric-like value
|
||||
*/
|
||||
const additiveInverse = (value: String | Number) => {
|
||||
if (typeof value === 'string') {
|
||||
const number = parseFloat(value, 10) * -1
|
||||
const unit = getUnit(value)
|
||||
return `${number}${unit}`
|
||||
} else if (isNumeric(value)) {
|
||||
return value * -1
|
||||
}
|
||||
}
|
||||
const additiveInverse = (value: String | Number) => multiplyStyleLengthValue(value, -1)
|
||||
|
||||
/**
|
||||
* BiDi flip the given property.
|
||||
@@ -65,15 +56,6 @@ const flipTransform = (transform: Object): Object => {
|
||||
return transform
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS unit for string values
|
||||
*/
|
||||
const getUnit = (str) => str.match(CSS_UNIT_RE)[1]
|
||||
|
||||
const isNumeric = (n) => {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
||||
|
||||
const swapLeftRight = (value:String): String => {
|
||||
return value === 'left' ? 'right' : value === 'right' ? 'left' : value
|
||||
}
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
import Animated from '../../apis/Animated'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import Easing from 'animated/lib/Easing'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import View from '../View'
|
||||
|
||||
const GRAY = '#999999'
|
||||
|
||||
const animationEffectTimingProperties = {
|
||||
direction: 'alternate',
|
||||
duration: 700,
|
||||
easing: 'ease-in-out',
|
||||
fill: 'forwards',
|
||||
iterations: Infinity
|
||||
}
|
||||
|
||||
const keyframeEffects = [
|
||||
{ transform: 'scale(1)', opacity: 1.0 },
|
||||
{ transform: 'scale(0.95)', opacity: 0.5 }
|
||||
]
|
||||
const opacityInterpolation = { inputRange: [ 0, 1 ], outputRange: [ 0.5, 1 ] }
|
||||
const scaleInterpolation = { inputRange: [ 0, 1 ], outputRange: [ 0.95, 1 ] }
|
||||
|
||||
class ActivityIndicator extends Component {
|
||||
static propTypes = {
|
||||
animating: PropTypes.bool,
|
||||
color: PropTypes.string,
|
||||
hidesWhenStopped: PropTypes.bool,
|
||||
size: PropTypes.oneOf(['small', 'large']),
|
||||
size: PropTypes.oneOf([ 'small', 'large' ]),
|
||||
style: View.propTypes.style
|
||||
};
|
||||
|
||||
@@ -36,10 +26,14 @@ class ActivityIndicator extends Component {
|
||||
style: {}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (document.documentElement.animate) {
|
||||
this._player = ReactDOM.findDOMNode(this._indicatorRef).animate(keyframeEffects, animationEffectTimingProperties)
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
animation: new Animated.Value(1)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._manageAnimation()
|
||||
}
|
||||
|
||||
@@ -57,37 +51,55 @@ class ActivityIndicator extends Component {
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
const { animation } = this.state
|
||||
|
||||
return (
|
||||
<View {...other} style={[ styles.container, style ]}>
|
||||
<View
|
||||
ref={this._createIndicatorRef}
|
||||
<Animated.View
|
||||
style={[
|
||||
indicatorStyles[size],
|
||||
hidesWhenStopped && !animating && styles.hidesWhenStopped,
|
||||
{ borderColor: color }
|
||||
{
|
||||
borderColor: color,
|
||||
opacity: animation.interpolate(opacityInterpolation),
|
||||
transform: [ { scale: animation.interpolate(scaleInterpolation) } ]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_createIndicatorRef = (component) => {
|
||||
this._indicatorRef = component
|
||||
}
|
||||
|
||||
_manageAnimation() {
|
||||
if (this._player) {
|
||||
if (this.props.animating) {
|
||||
this._player.play()
|
||||
} else {
|
||||
this._player.cancel()
|
||||
}
|
||||
const { animation } = this.state
|
||||
|
||||
const cycleAnimation = () => {
|
||||
Animated.sequence([
|
||||
Animated.timing(animation, {
|
||||
duration: 600,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
toValue: 0
|
||||
}),
|
||||
Animated.timing(animation, {
|
||||
duration: 600,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
toValue: 1
|
||||
})
|
||||
]).start((event) => {
|
||||
if (event.finished) {
|
||||
cycleAnimation()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.props.animating) {
|
||||
cycleAnimation()
|
||||
} else {
|
||||
animation.stopAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyNativeMethods(ActivityIndicator)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
@@ -113,4 +125,4 @@ const indicatorStyles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = ActivityIndicator
|
||||
module.exports = applyNativeMethods(ActivityIndicator)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* global window */
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes'
|
||||
import createDOMElement from '../../modules/createDOMElement'
|
||||
import ImageResizeMode from './ImageResizeMode'
|
||||
import ImageStylePropTypes from './ImageStylePropTypes'
|
||||
import resolveAssetSource from './resolveAssetSource'
|
||||
@@ -26,8 +27,7 @@ class Image extends Component {
|
||||
static displayName = 'Image'
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||
accessible: createReactDOMComponent.propTypes.accessible,
|
||||
...BaseComponentPropTypes,
|
||||
children: PropTypes.any,
|
||||
defaultSource: ImageSourcePropType,
|
||||
onError: PropTypes.func,
|
||||
@@ -37,8 +37,7 @@ class Image extends Component {
|
||||
onLoadStart: PropTypes.func,
|
||||
resizeMode: PropTypes.oneOf(['center', 'contain', 'cover', 'none', 'repeat', 'stretch']),
|
||||
source: ImageSourcePropType,
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
testID: createReactDOMComponent.propTypes.testID
|
||||
style: StyleSheetPropType(ImageStylePropTypes)
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -51,17 +50,18 @@ class Image extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
const uri = resolveAssetSource(props.source)
|
||||
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
|
||||
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE
|
||||
this.state = { isLoaded: false }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.status === STATUS_PENDING) {
|
||||
if (this._imageState === STATUS_PENDING) {
|
||||
this._createImageLoader()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.status === STATUS_PENDING && !this.image) {
|
||||
if (this._imageState === STATUS_PENDING && !this.image) {
|
||||
this._createImageLoader()
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,7 @@ class Image extends Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const nextUri = resolveAssetSource(nextProps.source)
|
||||
if (resolveAssetSource(this.props.source) !== nextUri) {
|
||||
this.setState({
|
||||
status: nextUri ? STATUS_PENDING : STATUS_IDLE
|
||||
})
|
||||
this._updateImageState(nextUri ? STATUS_PENDING : STATUS_IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +78,7 @@ class Image extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLoaded } = this.state
|
||||
const {
|
||||
accessibilityLabel,
|
||||
accessible,
|
||||
@@ -90,13 +89,13 @@ class Image extends Component {
|
||||
testID
|
||||
} = this.props
|
||||
|
||||
const isLoaded = this.state.status === STATUS_LOADED
|
||||
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source)
|
||||
const backgroundImage = displayImage ? `url("${displayImage}")` : null
|
||||
const style = StyleSheet.flatten(this.props.style)
|
||||
let style = StyleSheet.flatten(this.props.style)
|
||||
|
||||
const resizeMode = this.props.resizeMode || style.resizeMode || ImageResizeMode.cover
|
||||
// remove resizeMode style, as it is not supported by View
|
||||
// remove 'resizeMode' style, as it is not supported by View (N.B. styles are frozen in dev)
|
||||
style = process.env.NODE_ENV !== 'production' ? { ...style } : style
|
||||
delete style.resizeMode
|
||||
|
||||
/**
|
||||
@@ -121,7 +120,7 @@ class Image extends Component {
|
||||
]}
|
||||
testID={testID}
|
||||
>
|
||||
{createReactDOMComponent({ component: 'img', src: displayImage, style: styles.img })}
|
||||
{createDOMElement('img', { src: displayImage, style: styles.img })}
|
||||
{children ? (
|
||||
<View children={children} pointerEvents='box-none' style={styles.children} />
|
||||
) : null}
|
||||
@@ -153,7 +152,7 @@ class Image extends Component {
|
||||
const event = { nativeEvent: e }
|
||||
|
||||
this._destroyImageLoader()
|
||||
this.setState({ status: STATUS_ERRORED })
|
||||
this._updateImageState(STATUS_ERRORED)
|
||||
this._onLoadEnd()
|
||||
if (onError) onError(event)
|
||||
}
|
||||
@@ -163,7 +162,7 @@ class Image extends Component {
|
||||
const event = { nativeEvent: e }
|
||||
|
||||
this._destroyImageLoader()
|
||||
this.setState({ status: STATUS_LOADED })
|
||||
this._updateImageState(STATUS_LOADED)
|
||||
if (onLoad) onLoad(event)
|
||||
this._onLoadEnd()
|
||||
}
|
||||
@@ -175,9 +174,17 @@ class Image extends Component {
|
||||
|
||||
_onLoadStart() {
|
||||
const { onLoadStart } = this.props
|
||||
this.setState({ status: STATUS_LOADING })
|
||||
this._updateImageState(STATUS_LOADING)
|
||||
if (onLoadStart) onLoadStart()
|
||||
}
|
||||
|
||||
_updateImageState(status) {
|
||||
this._imageState = status
|
||||
const isLoaded = this._imageState === STATUS_LOADED
|
||||
if (isLoaded !== this.state.isLoaded) {
|
||||
this.setState({ isLoaded })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyNativeMethods(Image)
|
||||
|
||||
5
src/components/Switch/__tests__/index-test.js
Normal file
5
src/components/Switch/__tests__/index-test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
suite('components/ActivityIndicator', () => {
|
||||
test.skip('NO TEST COVERAGE', () => {})
|
||||
})
|
||||
176
src/components/Switch/index.js
Normal file
176
src/components/Switch/index.js
Normal file
@@ -0,0 +1,176 @@
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createDOMElement from '../../modules/createDOMElement'
|
||||
import ColorPropType from '../../propTypes/ColorPropType'
|
||||
import multiplyStyleLengthValue from '../../modules/multiplyStyleLengthValue'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import UIManager from '../../apis/UIManager'
|
||||
import View from '../View'
|
||||
|
||||
const thumbDefaultBoxShadow = '0px 1px 3px rgba(0,0,0,0.5)'
|
||||
const thumbFocusedBoxShadow = `${thumbDefaultBoxShadow}, 0 0 0 10px rgba(0,0,0,0.1)`
|
||||
|
||||
class Switch extends Component {
|
||||
static propTypes = {
|
||||
...View.propTypes,
|
||||
activeThumbColor: ColorPropType,
|
||||
activeTrackColor: ColorPropType,
|
||||
disabled: PropTypes.bool,
|
||||
onValueChange: PropTypes.func,
|
||||
thumbColor: ColorPropType,
|
||||
trackColor: ColorPropType,
|
||||
value: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
activeThumbColor: '#009688',
|
||||
activeTrackColor: '#A3D3CF',
|
||||
disabled: false,
|
||||
style: {},
|
||||
thumbColor: '#FAFAFA',
|
||||
trackColor: '#939393',
|
||||
value: false
|
||||
};
|
||||
|
||||
blur() {
|
||||
UIManager.blur(this._checkbox)
|
||||
}
|
||||
|
||||
focus() {
|
||||
UIManager.focus(this._checkbox)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
activeThumbColor,
|
||||
activeTrackColor,
|
||||
disabled,
|
||||
onValueChange, // eslint-disable-line
|
||||
style,
|
||||
thumbColor,
|
||||
trackColor,
|
||||
value,
|
||||
// remove any iOS-only props
|
||||
onTintColor, // eslint-disable-line
|
||||
thumbTintColor, // eslint-disable-line
|
||||
tintColor, // eslint-disable-line
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
const { height: styleHeight, width: styleWidth } = StyleSheet.flatten(style)
|
||||
const height = styleHeight || 20
|
||||
const minWidth = multiplyStyleLengthValue(height, 2)
|
||||
const width = styleWidth > minWidth ? styleWidth : minWidth
|
||||
const trackBorderRadius = multiplyStyleLengthValue(height, 0.5)
|
||||
const trackCurrentColor = value ? activeTrackColor : trackColor
|
||||
const thumbCurrentColor = value ? activeThumbColor : thumbColor
|
||||
const thumbHeight = height
|
||||
const thumbWidth = thumbHeight
|
||||
|
||||
const rootStyle = [
|
||||
styles.root,
|
||||
style,
|
||||
{ height, width },
|
||||
disabled && styles.cursorDefault
|
||||
]
|
||||
|
||||
const trackStyle = [
|
||||
styles.track,
|
||||
{
|
||||
backgroundColor: trackCurrentColor,
|
||||
borderRadius: trackBorderRadius
|
||||
},
|
||||
disabled && styles.disabledTrack
|
||||
]
|
||||
|
||||
const thumbStyle = [
|
||||
styles.thumb,
|
||||
{
|
||||
alignSelf: value ? 'flex-end' : 'flex-start',
|
||||
backgroundColor: thumbCurrentColor,
|
||||
height: thumbHeight,
|
||||
width: thumbWidth
|
||||
},
|
||||
disabled && styles.disabledThumb
|
||||
]
|
||||
|
||||
const nativeControl = createDOMElement('label', {
|
||||
children: createDOMElement('input', {
|
||||
checked: value,
|
||||
disabled: disabled,
|
||||
onBlur: this._handleFocusState,
|
||||
onChange: this._handleChange,
|
||||
onFocus: this._handleFocusState,
|
||||
ref: this._setCheckboxRef,
|
||||
style: styles.cursorInherit,
|
||||
type: 'checkbox'
|
||||
}),
|
||||
pointerEvents: 'none',
|
||||
style: [ styles.nativeControl, styles.cursorInherit ]
|
||||
})
|
||||
|
||||
return (
|
||||
<View {...other} style={rootStyle}>
|
||||
<View style={trackStyle} />
|
||||
<View ref={this._setThumbRef} style={thumbStyle} />
|
||||
{nativeControl}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
_handleChange = (event: Object) => {
|
||||
const { onValueChange } = this.props
|
||||
onValueChange && onValueChange(event.nativeEvent.target.checked)
|
||||
}
|
||||
|
||||
_handleFocusState = (event: Object) => {
|
||||
const isFocused = event.nativeEvent.type === 'focus'
|
||||
const boxShadow = isFocused ? thumbFocusedBoxShadow : thumbDefaultBoxShadow
|
||||
this._thumb.setNativeProps({ style: { boxShadow } })
|
||||
}
|
||||
|
||||
_setCheckboxRef = (component) => {
|
||||
this._checkbox = component
|
||||
}
|
||||
|
||||
_setThumbRef = (component) => {
|
||||
this._thumb = component
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none'
|
||||
},
|
||||
cursorDefault: {
|
||||
cursor: 'default'
|
||||
},
|
||||
cursorInherit: {
|
||||
cursor: 'inherit'
|
||||
},
|
||||
track: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
height: '70%',
|
||||
margin: 'auto',
|
||||
transition: 'background-color 0.1s',
|
||||
width: '90%'
|
||||
},
|
||||
disabledTrack: {
|
||||
backgroundColor: '#D5D5D5'
|
||||
},
|
||||
thumb: {
|
||||
borderRadius: '100%',
|
||||
boxShadow: thumbDefaultBoxShadow,
|
||||
transition: 'background-color 0.1s'
|
||||
},
|
||||
disabledThumb: {
|
||||
backgroundColor: '#BDBDBD'
|
||||
},
|
||||
nativeControl: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
opacity: 0
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = applyNativeMethods(Switch)
|
||||
@@ -1,6 +1,7 @@
|
||||
import applyLayout from '../../modules/applyLayout'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes'
|
||||
import createDOMElement from '../../modules/createDOMElement'
|
||||
import { Component, PropTypes } from 'react'
|
||||
import StyleSheet from '../../apis/StyleSheet'
|
||||
import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
||||
@@ -10,16 +11,14 @@ class Text extends Component {
|
||||
static displayName = 'Text'
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||
...BaseComponentPropTypes,
|
||||
accessibilityRole: PropTypes.oneOf([ 'heading', 'link' ]),
|
||||
accessible: createReactDOMComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
numberOfLines: PropTypes.number,
|
||||
onLayout: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
selectable: PropTypes.bool,
|
||||
style: StyleSheetPropType(TextStylePropTypes),
|
||||
testID: createReactDOMComponent.propTypes.testID
|
||||
style: StyleSheetPropType(TextStylePropTypes)
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -37,9 +36,8 @@ class Text extends Component {
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
return createReactDOMComponent({
|
||||
return createDOMElement('span', {
|
||||
...other,
|
||||
component: 'span',
|
||||
onClick: this._onPress,
|
||||
style: [
|
||||
styles.initial,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import createDOMElement from '../../modules/createDOMElement'
|
||||
import omit from 'lodash/omit'
|
||||
import pick from 'lodash/pick'
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
@@ -22,7 +22,7 @@ class TextInput extends Component {
|
||||
clearTextOnFocus: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
editable: PropTypes.bool,
|
||||
keyboardType: PropTypes.oneOf(['default', 'email-address', 'numeric', 'phone-pad', 'url']),
|
||||
keyboardType: PropTypes.oneOf(['default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search']),
|
||||
maxLength: PropTypes.number,
|
||||
maxNumberOfLines: PropTypes.number,
|
||||
multiline: PropTypes.bool,
|
||||
@@ -135,23 +135,23 @@ class TextInput extends Component {
|
||||
onFocus: this._handleFocus,
|
||||
onSelect: onSelectionChange && this._handleSelectionChange,
|
||||
readOnly: !editable,
|
||||
ref: 'input',
|
||||
style: [ styles.input, textStyles, { outline: style.outline } ],
|
||||
value
|
||||
}
|
||||
|
||||
const propsMultiline = {
|
||||
...propsCommon,
|
||||
component: TextareaAutosize,
|
||||
maxRows: maxNumberOfLines || numberOfLines,
|
||||
minRows: numberOfLines
|
||||
}
|
||||
|
||||
const propsSingleline = {
|
||||
...propsCommon,
|
||||
component: 'input',
|
||||
type
|
||||
}
|
||||
|
||||
const component = multiline ? TextareaAutosize : 'input'
|
||||
const props = multiline ? propsMultiline : propsSingleline
|
||||
|
||||
const optionalPlaceholder = placeholder && this.state.showPlaceholder && (
|
||||
@@ -176,7 +176,7 @@ class TextInput extends Component {
|
||||
testID={testID}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
{createReactDOMComponent({ ...props, ref: 'input' })}
|
||||
{createDOMElement(component, props)}
|
||||
{optionalPlaceholder}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -31,6 +31,7 @@ var merge = require('../../modules/merge');
|
||||
type Event = Object;
|
||||
|
||||
var DEFAULT_PROPS = {
|
||||
accessibilityRole: 'button',
|
||||
activeOpacity: 0.8,
|
||||
underlayColor: 'black'
|
||||
};
|
||||
@@ -234,7 +235,8 @@ var TouchableHighlight = React.createClass({
|
||||
<View
|
||||
accessible={true}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole || this.props.accessibilityTraits || 'button'}
|
||||
accessibilityRole={this.props.accessibilityRole}
|
||||
disabled={this.props.disabled}
|
||||
hitSlop={this.props.hitSlop}
|
||||
onKeyDown={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
|
||||
onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
|
||||
@@ -247,8 +249,12 @@ var TouchableHighlight = React.createClass({
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
ref={UNDERLAY_REF}
|
||||
style={[ styles.root, this.state.underlayStyle ]}
|
||||
tabIndex='0'
|
||||
style={[
|
||||
styles.root,
|
||||
this.props.disabled && styles.disabled,
|
||||
this.state.underlayStyle
|
||||
]}
|
||||
tabIndex={this.props.disabled ? null : '0'}
|
||||
testID={this.props.testID}>
|
||||
{React.cloneElement(
|
||||
React.Children.only(this.props.children),
|
||||
@@ -274,6 +280,9 @@ var styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none'
|
||||
},
|
||||
disabled: {
|
||||
cursor: 'default'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ var TouchableOpacity = React.createClass({
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
accessibilityRole: 'button',
|
||||
activeOpacity: 0.2,
|
||||
};
|
||||
},
|
||||
@@ -168,8 +169,14 @@ var TouchableOpacity = React.createClass({
|
||||
<Animated.View
|
||||
accessible={this.props.accessible !== false}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole || 'button'}
|
||||
style={[styles.root, this.props.style, {opacity: this.state.anim}]}
|
||||
accessibilityRole={this.props.accessibilityRole}
|
||||
disabled={this.props.disabled}
|
||||
style={[
|
||||
styles.root,
|
||||
this.props.disabled && styles.disabled,
|
||||
this.props.style,
|
||||
{opacity: this.state.anim}
|
||||
]}
|
||||
testID={this.props.testID}
|
||||
onLayout={this.props.onLayout}
|
||||
hitSlop={this.props.hitSlop}
|
||||
@@ -182,7 +189,7 @@ var TouchableOpacity = React.createClass({
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
tabIndex='0'
|
||||
tabIndex={this.props.disabled ? null : '0'}
|
||||
>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
@@ -194,6 +201,9 @@ var styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none'
|
||||
},
|
||||
disabled: {
|
||||
cursor: 'default'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -161,12 +161,22 @@ const TouchableWithoutFeedback = React.createClass({
|
||||
children.push(Touchable.renderDebugView({color: 'red', hitSlop: this.props.hitSlop}));
|
||||
}
|
||||
const style = (Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'Text') ?
|
||||
[styles.root, child.props.style, {color: 'red'}] :
|
||||
[styles.root, child.props.style];
|
||||
[
|
||||
styles.root,
|
||||
this.props.disabled && styles.disabled,
|
||||
child.props.style,
|
||||
{color: 'red'}
|
||||
] :
|
||||
[
|
||||
styles.root,
|
||||
this.props.disabled && styles.disabled,
|
||||
child.props.style
|
||||
];
|
||||
return (React: any).cloneElement(child, {
|
||||
accessible: this.props.accessible !== false,
|
||||
accessibilityLabel: this.props.accessibilityLabel,
|
||||
accessibilityRole: this.props.accessibilityRole,
|
||||
disabled: this.props.disabled,
|
||||
testID: this.props.testID,
|
||||
onLayout: this.props.onLayout,
|
||||
hitSlop: this.props.hitSlop,
|
||||
@@ -178,7 +188,7 @@ const TouchableWithoutFeedback = React.createClass({
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
style,
|
||||
children,
|
||||
tabIndex: '0'
|
||||
tabIndex: this.props.disabled ? null : '0'
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -186,6 +196,9 @@ const TouchableWithoutFeedback = React.createClass({
|
||||
var styles = StyleSheet.create({
|
||||
root: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
disabled: {
|
||||
cursor: 'default'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import applyLayout from '../../modules/applyLayout'
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes'
|
||||
import createDOMElement from '../../modules/createDOMElement'
|
||||
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'
|
||||
import normalizeNativeEvent from '../../modules/normalizeNativeEvent'
|
||||
import { Component, PropTypes } from 'react'
|
||||
@@ -35,10 +36,7 @@ class View extends Component {
|
||||
static displayName = 'View'
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||
accessibilityLiveRegion: createReactDOMComponent.propTypes.accessibilityLiveRegion,
|
||||
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
|
||||
accessible: createReactDOMComponent.propTypes.accessible,
|
||||
...BaseComponentPropTypes,
|
||||
children: PropTypes.any,
|
||||
collapsable: PropTypes.bool,
|
||||
hitSlop: EdgeInsetsPropType,
|
||||
@@ -64,8 +62,7 @@ class View extends Component {
|
||||
onTouchStart: PropTypes.func,
|
||||
onTouchStartCapture: PropTypes.func,
|
||||
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
testID: createReactDOMComponent.propTypes.testID
|
||||
style: StyleSheetPropType(ViewStylePropTypes)
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -110,10 +107,10 @@ class View extends Component {
|
||||
return handlerProps
|
||||
}, {})
|
||||
|
||||
const component = this.context.isInAButtonView ? 'span' : 'div'
|
||||
const props = {
|
||||
...other,
|
||||
...normalizedEventHandlers,
|
||||
component: this.context.isInAButtonView ? 'span' : 'div',
|
||||
style: [
|
||||
styles.initial,
|
||||
style,
|
||||
@@ -122,7 +119,7 @@ class View extends Component {
|
||||
]
|
||||
}
|
||||
|
||||
return createReactDOMComponent(props)
|
||||
return createDOMElement(component, props)
|
||||
}
|
||||
|
||||
_normalizeEventForHandler(handler, handlerName) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import ActivityIndicator from './components/ActivityIndicator'
|
||||
import Image from './components/Image'
|
||||
import ListView from './components/ListView'
|
||||
import ScrollView from './components/ScrollView'
|
||||
import Switch from './components/Switch'
|
||||
import Text from './components/Text'
|
||||
import TextInput from './components/TextInput'
|
||||
import Touchable from './components/Touchable/Touchable'
|
||||
@@ -67,6 +68,7 @@ const ReactNative = {
|
||||
PixelRatio,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
UIManager,
|
||||
Vibration,
|
||||
|
||||
|
||||
@@ -1,61 +1,60 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import createReactDOMComponent from '..'
|
||||
import createDOMElement from '..'
|
||||
import { shallow } from 'enzyme'
|
||||
|
||||
suite('modules/createReactDOMComponent', () => {
|
||||
suite('modules/createDOMElement', () => {
|
||||
test('renders correct DOM element', () => {
|
||||
let element = shallow(createDOMElement('span'))
|
||||
assert.equal(element.is('span'), true)
|
||||
element = shallow(createDOMElement('main'))
|
||||
assert.equal(element.is('main'), true)
|
||||
})
|
||||
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const element = shallow(createReactDOMComponent({ accessibilityLabel }))
|
||||
const element = shallow(createDOMElement('span', { accessibilityLabel }))
|
||||
assert.equal(element.prop('aria-label'), accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityLiveRegion"', () => {
|
||||
const accessibilityLiveRegion = 'polite'
|
||||
const element = shallow(createReactDOMComponent({ accessibilityLiveRegion }))
|
||||
const element = shallow(createDOMElement('span', { accessibilityLiveRegion }))
|
||||
assert.equal(element.prop('aria-live'), accessibilityLiveRegion)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
const accessibilityRole = 'banner'
|
||||
let element = shallow(createReactDOMComponent({ accessibilityRole }))
|
||||
let element = shallow(createDOMElement('span', { accessibilityRole }))
|
||||
assert.equal(element.prop('role'), accessibilityRole)
|
||||
assert.equal(element.is('header'), true)
|
||||
|
||||
const button = 'button'
|
||||
element = shallow(createReactDOMComponent({ accessibilityRole: 'button' }))
|
||||
element = shallow(createDOMElement('span', { accessibilityRole: 'button' }))
|
||||
assert.equal(element.prop('type'), button)
|
||||
assert.equal(element.is('button'), true)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
// accessible (implicit)
|
||||
let element = shallow(createReactDOMComponent({}))
|
||||
let element = shallow(createDOMElement('span', {}))
|
||||
assert.equal(element.prop('aria-hidden'), null)
|
||||
// accessible (explicit)
|
||||
element = shallow(createReactDOMComponent({ accessible: true }))
|
||||
element = shallow(createDOMElement('span', { accessible: true }))
|
||||
assert.equal(element.prop('aria-hidden'), null)
|
||||
// not accessible
|
||||
element = shallow(createReactDOMComponent({ accessible: false }))
|
||||
element = shallow(createDOMElement('span', { accessible: false }))
|
||||
assert.equal(element.prop('aria-hidden'), true)
|
||||
})
|
||||
|
||||
test('prop "component"', () => {
|
||||
const component = 'main'
|
||||
let element = shallow(createReactDOMComponent({}))
|
||||
assert.equal(element.is('span'), true, 'Default element must be a "span"')
|
||||
element = shallow(createReactDOMComponent({ component }))
|
||||
assert.equal(element.is('main'), true)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
// no testID
|
||||
let element = shallow(createReactDOMComponent({}))
|
||||
let element = shallow(createDOMElement('span', {}))
|
||||
assert.equal(element.prop('data-testid'), null)
|
||||
// with testID
|
||||
const testID = 'Example.testID'
|
||||
element = shallow(createReactDOMComponent({ testID }))
|
||||
element = shallow(createDOMElement('span', { testID }))
|
||||
assert.equal(element.prop('data-testid'), testID)
|
||||
})
|
||||
})
|
||||
48
src/modules/createDOMElement/index.js
Normal file
48
src/modules/createDOMElement/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React 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'
|
||||
}
|
||||
|
||||
const createDOMElement = (component, rnProps = {}) => {
|
||||
const {
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityRole,
|
||||
accessible = true,
|
||||
testID,
|
||||
type,
|
||||
...other
|
||||
} = rnProps
|
||||
|
||||
const accessibilityComponent = accessibilityRole && roleComponents[accessibilityRole]
|
||||
const Component = accessibilityComponent || 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 = createDOMElement
|
||||
@@ -1,58 +0,0 @@
|
||||
import React, { 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'
|
||||
}
|
||||
|
||||
const createReactDOMComponent = ({
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityRole,
|
||||
accessible = true,
|
||||
component = 'span',
|
||||
testID,
|
||||
type,
|
||||
...other
|
||||
}) => {
|
||||
const role = accessibilityRole
|
||||
const Component = role && roleComponents[role] ? roleComponents[role] : component
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...other}
|
||||
{...StyleSheet.resolve(other)}
|
||||
aria-hidden={accessible ? null : true}
|
||||
aria-label={accessibilityLabel}
|
||||
aria-live={accessibilityLiveRegion}
|
||||
data-testid={testID}
|
||||
role={role}
|
||||
type={role === 'button' ? 'button' : type}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
createReactDOMComponent.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
|
||||
}
|
||||
|
||||
module.exports = createReactDOMComponent
|
||||
19
src/modules/multiplyStyleLengthValue/__tests__/index-test.js
Normal file
19
src/modules/multiplyStyleLengthValue/__tests__/index-test.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import assert from 'assert'
|
||||
import multiplyStyleLengthValue from '..'
|
||||
|
||||
suite('modules/multiplyStyleLengthValue', () => {
|
||||
test('number', () => {
|
||||
assert.equal(multiplyStyleLengthValue(2, -1), -2)
|
||||
assert.equal(multiplyStyleLengthValue(2, 2), 4)
|
||||
assert.equal(multiplyStyleLengthValue(2.5, 2), 5)
|
||||
})
|
||||
|
||||
test('length', () => {
|
||||
assert.equal(multiplyStyleLengthValue('2rem', -1), '-2rem')
|
||||
assert.equal(multiplyStyleLengthValue('2px', 2), '4px')
|
||||
assert.equal(multiplyStyleLengthValue('2.5em', 2), '5em')
|
||||
assert.equal(multiplyStyleLengthValue('2e3px', 2), '4000px')
|
||||
})
|
||||
})
|
||||
19
src/modules/multiplyStyleLengthValue/index.js
Normal file
19
src/modules/multiplyStyleLengthValue/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const CSS_UNIT_RE = /^[+-]?\d*(?:\.\d+)?(?:[Ee][+-]?\d+)?(\w*)/
|
||||
|
||||
const getUnit = (str) => str.match(CSS_UNIT_RE)[1]
|
||||
|
||||
const isNumeric = (n) => {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
||||
|
||||
const multiplyStyleLengthValue = (value: String | Number, multiple) => {
|
||||
if (typeof value === 'string') {
|
||||
const number = parseFloat(value, 10) * multiple
|
||||
const unit = getUnit(value)
|
||||
return `${number}${unit}`
|
||||
} else if (isNumeric(value)) {
|
||||
return value * multiple
|
||||
}
|
||||
}
|
||||
|
||||
export default multiplyStyleLengthValue
|
||||
13
src/propTypes/BaseComponentPropTypes.js
Normal file
13
src/propTypes/BaseComponentPropTypes.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { PropTypes } from 'react'
|
||||
const { array, bool, number, object, oneOf, oneOfType, string } = PropTypes
|
||||
|
||||
const BaseComponentPropTypes = {
|
||||
accessibilityLabel: string,
|
||||
accessibilityLiveRegion: oneOf([ 'assertive', 'off', 'polite' ]),
|
||||
accessibilityRole: string,
|
||||
accessible: bool,
|
||||
style: oneOfType([ array, number, object ]),
|
||||
testID: string
|
||||
}
|
||||
|
||||
module.exports = BaseComponentPropTypes
|
||||
Reference in New Issue
Block a user