Compare commits

..

4 Commits

Author SHA1 Message Date
Nicolas Gallagher
011affb110 0.0.44 2016-08-18 16:27:39 -07:00
Nicolas Gallagher
87a4f56a89 [add] Switch component
'Switch' on Web can support custom sizes and colors. To do so,
Web-specific propTypes are introduced: `trackColor`, `thumbColor`,
`activeTrackColor`, and `activeThumbColor`.
2016-08-18 16:25:16 -07:00
Nathan Tran
2f94295739 [fix] TextInput 'keyboardType' propType 2016-08-17 15:51:55 -07:00
Nicolas Gallagher
fdf4a88251 Refactor DOM element helper 2016-08-17 15:46:10 -07:00
18 changed files with 591 additions and 125 deletions

View File

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

76
docs/components/Switch.md Normal file
View File

@@ -0,0 +1,76 @@
# Switch
This is a controlled component that requires an `onValueChange` callback that
updates the value prop in order for the component to reflect user actions. If
the `value` prop is not updated, the component will continue to render the
supplied `value` prop instead of the expected result of any user actions.
## Props
[...View props](./View.md)
**disabled**: bool = false
If `true` the user won't be able to interact with the switch.
**onValueChange**: func
Invoked with the new value when the value changes.
**value**: bool = false
The value of the switch. If `true` the switch will be turned on.
(web) **activeThumbColor**: color = #009688
The color of the thumb grip when the switch is turned on.
(web) **activeTrackColor**: color = #A3D3CF
The color of the track when the switch is turned on.
(web) **thumbColor**: color = #FAFAFA
The color of the thumb grip when the switch is turned off.
(web) **trackColor**: color = #939393
The color of the track when the switch is turned off.
## Examples
```js
import React, { Component } from 'react'
import { Switch, View } from 'react-native'
class ColorSwitchExample extends Component {
constructor(props) {
super(props)
this.state = {
colorTrueSwitchIsOn: true,
colorFalseSwitchIsOn: false
}
}
render() {
return (
<View>
<Switch
activeThumbColor='#428BCA'
activeTrackColor='#A0C4E3'
onValueChange={(value) => this.setState({ colorFalseSwitchIsOn: value })}
value={this.state.colorFalseSwitchIsOn}
/>
<Switch
activeThumbColor='#5CB85C'
activeTrackColor='#ADDAAD'
onValueChange={(value) => this.setState({ colorTrueSwitchIsOn: value })}
thumbColor='#EBA9A7'
trackColor='#D9534F'
value={this.state.colorTrueSwitchIsOn}
/>
</View>
)
}
}
```

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.43",
"version": "0.0.44",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [

View File

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

View File

@@ -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 = {
@@ -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}

View File

@@ -0,0 +1,5 @@
/* eslint-env mocha */
suite('components/ActivityIndicator', () => {
test.skip('NO TEST COVERAGE', () => {})
})

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

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

View 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

View 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