mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-29 12:54:53 +08:00
Create CoreComponent; update View implementation
This commit is contained in:
3
src/components/CoreComponent/index.js
Normal file
3
src/components/CoreComponent/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import CoreComponent from './modules/CoreComponent'
|
||||||
|
|
||||||
|
export default CoreComponent
|
||||||
42
src/components/CoreComponent/modules/CoreComponent.js
Normal file
42
src/components/CoreComponent/modules/CoreComponent.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import restyle from './restyle'
|
||||||
|
import stylePropTypes from './stylePropTypes'
|
||||||
|
|
||||||
|
class CoreComponent extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
component: PropTypes.oneOfType([
|
||||||
|
PropTypes.func,
|
||||||
|
PropTypes.string
|
||||||
|
]),
|
||||||
|
style: PropTypes.object,
|
||||||
|
testID: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
static stylePropTypes = stylePropTypes;
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
className: '',
|
||||||
|
component: 'div'
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
component: Component,
|
||||||
|
style,
|
||||||
|
testID,
|
||||||
|
...other
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
{...other}
|
||||||
|
{...restyle({ className, style })}
|
||||||
|
data-testid={process.env.NODE_ENV === 'production' ? null : testID}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CoreComponent
|
||||||
47
src/components/CoreComponent/modules/autoprefix.js
Normal file
47
src/components/CoreComponent/modules/autoprefix.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export default function prefixStyles(style) {
|
||||||
|
if (style.hasOwnProperty('flexBasis')) {
|
||||||
|
style = {
|
||||||
|
WebkitFlexBasis: style.flexBasis,
|
||||||
|
msFlexBasis: style.flexBasis,
|
||||||
|
...style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.hasOwnProperty('flexGrow')) {
|
||||||
|
style = {
|
||||||
|
WebkitBoxFlex: style.flexGrow,
|
||||||
|
WebkitFlexGrow: style.flexGrow,
|
||||||
|
msFlexPositive: style.flexGrow,
|
||||||
|
...style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.hasOwnProperty('flexShrink')) {
|
||||||
|
style = {
|
||||||
|
WebkitFlexShrink: style.flexShrink,
|
||||||
|
msFlexNegative: style.flexShrink,
|
||||||
|
...style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: adding `;` to the string value prevents React from automatically
|
||||||
|
// adding a `px` suffix to the unitless value
|
||||||
|
if (style.hasOwnProperty('order')) {
|
||||||
|
style = {
|
||||||
|
WebkitBoxOrdinalGroup: `${parseInt(style.order, 10) + 1};`,
|
||||||
|
WebkitOrder: `${style.order}`,
|
||||||
|
msFlexOrder: `${style.order}`,
|
||||||
|
...style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.hasOwnProperty('transform')) {
|
||||||
|
style = {
|
||||||
|
WebkitTransform: style.transform,
|
||||||
|
msTransform: style.transform,
|
||||||
|
...style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style
|
||||||
|
}
|
||||||
40
src/components/CoreComponent/modules/restyle.js
Normal file
40
src/components/CoreComponent/modules/restyle.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import autoprefix from './autoprefix'
|
||||||
|
import styles from '../../../modules/styles'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the HTML class that corresponds to a style declaration
|
||||||
|
* @param prop {string} prop name
|
||||||
|
* @param style {Object} style
|
||||||
|
* @return {string} class name
|
||||||
|
*/
|
||||||
|
function getSinglePurposeClassName(prop, style) {
|
||||||
|
const className = `${prop}-${style[prop]}`
|
||||||
|
if (style.hasOwnProperty(prop) && styles[className]) {
|
||||||
|
return styles[className]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace inline styles with single purpose classes where possible
|
||||||
|
* @param props {Object} React Element properties
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
export default function stylingStrategy(props) {
|
||||||
|
let className
|
||||||
|
let style = {}
|
||||||
|
|
||||||
|
const classList = [ props.className ]
|
||||||
|
for (const prop in props.style) {
|
||||||
|
const styleClass = getSinglePurposeClassName(prop, props.style)
|
||||||
|
if (styleClass) {
|
||||||
|
classList.push(styleClass)
|
||||||
|
} else {
|
||||||
|
style[prop] = props.style[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
className = classList.join(' ')
|
||||||
|
style = autoprefix(style)
|
||||||
|
|
||||||
|
return { className: className, style }
|
||||||
|
}
|
||||||
104
src/components/CoreComponent/modules/stylePropTypes.js
Normal file
104
src/components/CoreComponent/modules/stylePropTypes.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { PropTypes } from 'react'
|
||||||
|
|
||||||
|
const numberOrString = PropTypes.oneOfType([
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.string
|
||||||
|
])
|
||||||
|
|
||||||
|
const { string } = PropTypes
|
||||||
|
|
||||||
|
export default {
|
||||||
|
alignContent: string,
|
||||||
|
alignItems: string,
|
||||||
|
alignSelf: string,
|
||||||
|
backfaceVisibility: string,
|
||||||
|
// background
|
||||||
|
backgroundAttachment: string,
|
||||||
|
backgroundClip: string,
|
||||||
|
backgroundColor: string,
|
||||||
|
backgroundImage: string,
|
||||||
|
backgroundOrigin: string,
|
||||||
|
backgroundPosition: string,
|
||||||
|
backgroundRepeat: string,
|
||||||
|
backgroundSize: string,
|
||||||
|
// border color
|
||||||
|
borderColor: numberOrString,
|
||||||
|
borderBottomColor: numberOrString,
|
||||||
|
borderLeftColor: numberOrString,
|
||||||
|
borderRightColor: numberOrString,
|
||||||
|
borderTopColor: numberOrString,
|
||||||
|
// border-radius
|
||||||
|
borderRadius: numberOrString,
|
||||||
|
borderTopLeftRadius: numberOrString,
|
||||||
|
borderTopRightRadius: numberOrString,
|
||||||
|
borderBottomLeftRadius: numberOrString,
|
||||||
|
borderBottomRightRadius: numberOrString,
|
||||||
|
// border style
|
||||||
|
borderStyle: numberOrString,
|
||||||
|
borderBottomStyle: numberOrString,
|
||||||
|
borderLeftStyle: numberOrString,
|
||||||
|
borderRightStyle: numberOrString,
|
||||||
|
borderTopStyle: numberOrString,
|
||||||
|
// border width
|
||||||
|
borderWidth: numberOrString,
|
||||||
|
borderBottomWidth: numberOrString,
|
||||||
|
borderLeftWidth: numberOrString,
|
||||||
|
borderRightWidth: numberOrString,
|
||||||
|
borderTopWidth: numberOrString,
|
||||||
|
bottom: numberOrString,
|
||||||
|
boxSizing: string,
|
||||||
|
clear: string,
|
||||||
|
color: string,
|
||||||
|
direction: string,
|
||||||
|
display: string,
|
||||||
|
flexBasis: string,
|
||||||
|
flexDirection: string,
|
||||||
|
flexGrow: numberOrString,
|
||||||
|
flexShrink: numberOrString,
|
||||||
|
flexWrap: string,
|
||||||
|
float: string,
|
||||||
|
font: string,
|
||||||
|
fontFamily: string,
|
||||||
|
fontSize: string,
|
||||||
|
fontStyle: string,
|
||||||
|
fontWeight: string,
|
||||||
|
height: numberOrString,
|
||||||
|
justifyContent: string,
|
||||||
|
left: numberOrString,
|
||||||
|
letterSpacing: string,
|
||||||
|
lineHeight: numberOrString,
|
||||||
|
// margin
|
||||||
|
margin: numberOrString,
|
||||||
|
marginBottom: numberOrString,
|
||||||
|
marginLeft: numberOrString,
|
||||||
|
marginRight: numberOrString,
|
||||||
|
marginTop: numberOrString,
|
||||||
|
// min/max
|
||||||
|
maxHeight: numberOrString,
|
||||||
|
maxWidth: numberOrString,
|
||||||
|
minHeight: numberOrString,
|
||||||
|
minWidth: numberOrString,
|
||||||
|
opacity: numberOrString,
|
||||||
|
order: numberOrString,
|
||||||
|
overflow: string,
|
||||||
|
overflowX: string,
|
||||||
|
overflowY: string,
|
||||||
|
// padding
|
||||||
|
padding: numberOrString,
|
||||||
|
paddingBottom: numberOrString,
|
||||||
|
paddingLeft: numberOrString,
|
||||||
|
paddingRight: numberOrString,
|
||||||
|
paddingTop: numberOrString,
|
||||||
|
position: string,
|
||||||
|
right: numberOrString,
|
||||||
|
textAlign: string,
|
||||||
|
textDecoration: string,
|
||||||
|
textTransform: string,
|
||||||
|
top: numberOrString,
|
||||||
|
userSelect: string,
|
||||||
|
visibility: string,
|
||||||
|
whiteSpace: string,
|
||||||
|
width: numberOrString,
|
||||||
|
wordWrap: string,
|
||||||
|
zIndex: numberOrString
|
||||||
|
}
|
||||||
85
src/components/View/ViewStylePropTypes.js
Normal file
85
src/components/View/ViewStylePropTypes.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { pickProps } from '../../modules/filterObjectProps'
|
||||||
|
import CoreComponent from '../CoreComponent'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...pickProps(CoreComponent.stylePropTypes, [
|
||||||
|
'alignContent',
|
||||||
|
'alignItems',
|
||||||
|
'alignSelf',
|
||||||
|
'backfaceVisibility',
|
||||||
|
// background
|
||||||
|
'backgroundAttachment',
|
||||||
|
'backgroundClip',
|
||||||
|
'backgroundColor',
|
||||||
|
'backgroundImage',
|
||||||
|
'backgroundPosition',
|
||||||
|
'backgroundOrigin',
|
||||||
|
'backgroundRepeat',
|
||||||
|
'backgroundSize',
|
||||||
|
// border-color
|
||||||
|
'borderColor',
|
||||||
|
'borderTopColor',
|
||||||
|
'borderRightColor',
|
||||||
|
'borderBottomColor',
|
||||||
|
'borderLeftColor',
|
||||||
|
// border-radius
|
||||||
|
'borderRadius',
|
||||||
|
'borderTopLeftRadius',
|
||||||
|
'borderTopRightRadius',
|
||||||
|
'borderBottomLeftRadius',
|
||||||
|
'borderBottomRightRadius',
|
||||||
|
// border style
|
||||||
|
'borderStyle',
|
||||||
|
'borderBottomStyle',
|
||||||
|
'borderLeftStyle',
|
||||||
|
'borderRightStyle',
|
||||||
|
'borderTopStyle',
|
||||||
|
// border width
|
||||||
|
'borderWidth',
|
||||||
|
'borderBottomWidth',
|
||||||
|
'borderLeftWidth',
|
||||||
|
'borderRightWidth',
|
||||||
|
'borderTopWidth',
|
||||||
|
'bottom',
|
||||||
|
'boxShadow',
|
||||||
|
'boxSizing',
|
||||||
|
'flexBasis',
|
||||||
|
'flexDirection',
|
||||||
|
'flexGrow',
|
||||||
|
'flexShrink',
|
||||||
|
'flexWrap',
|
||||||
|
'height',
|
||||||
|
'justifyContent',
|
||||||
|
'left',
|
||||||
|
// margin
|
||||||
|
'margin',
|
||||||
|
'marginBottom',
|
||||||
|
'marginLeft',
|
||||||
|
'marginRight',
|
||||||
|
'marginTop',
|
||||||
|
// max/min
|
||||||
|
'maxHeight',
|
||||||
|
'maxWidth',
|
||||||
|
'minHeight',
|
||||||
|
'minWidth',
|
||||||
|
'opacity',
|
||||||
|
'order',
|
||||||
|
'overflow',
|
||||||
|
'overflowX',
|
||||||
|
'overflowY',
|
||||||
|
// padding
|
||||||
|
'padding',
|
||||||
|
'paddingBottom',
|
||||||
|
'paddingLeft',
|
||||||
|
'paddingRight',
|
||||||
|
'paddingTop',
|
||||||
|
'position',
|
||||||
|
'right',
|
||||||
|
'top',
|
||||||
|
'transform',
|
||||||
|
'userSelect',
|
||||||
|
'visibility',
|
||||||
|
'width',
|
||||||
|
'zIndex'
|
||||||
|
])
|
||||||
|
}
|
||||||
72
src/components/View/index.js
Normal file
72
src/components/View/index.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { pickProps } from '../../modules/filterObjectProps'
|
||||||
|
import CoreComponent from '../CoreComponent'
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import ViewStylePropTypes from './ViewStylePropTypes'
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
// https://github.com/facebook/css-layout#default-values
|
||||||
|
initial: {
|
||||||
|
alignItems: 'stretch',
|
||||||
|
borderWidth: 0,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
flexBasis: 'auto',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flexShrink: 0,
|
||||||
|
listStyle: 'none',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
position: 'relative',
|
||||||
|
// button reset
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'inherit',
|
||||||
|
font: 'inherit',
|
||||||
|
textAlign: 'inherit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class View extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
accessibilityLabel: PropTypes.string,
|
||||||
|
children: PropTypes.any,
|
||||||
|
component: CoreComponent.propTypes.component,
|
||||||
|
pointerEvents: PropTypes.oneOf([
|
||||||
|
'auto',
|
||||||
|
'box-none',
|
||||||
|
'box-only',
|
||||||
|
'none'
|
||||||
|
]),
|
||||||
|
style: PropTypes.shape(ViewStylePropTypes),
|
||||||
|
testID: CoreComponent.propTypes.testID
|
||||||
|
}
|
||||||
|
|
||||||
|
static stylePropTypes = ViewStylePropTypes
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
component: 'div',
|
||||||
|
style: styles.initial
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { accessibilityLabel, pointerEvents, style, testID, ...other } = this.props
|
||||||
|
const pointerEventsStyle = pointerEvents && { pointerEvents }
|
||||||
|
const resolvedStyle = pickProps(style, Object.keys(ViewStylePropTypes))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CoreComponent
|
||||||
|
{...other}
|
||||||
|
aria-label={accessibilityLabel}
|
||||||
|
className={'View'}
|
||||||
|
style={{
|
||||||
|
...(styles.initial),
|
||||||
|
...resolvedStyle,
|
||||||
|
...pointerEventsStyle
|
||||||
|
}}
|
||||||
|
testID={testID}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default View
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import React from 'react/addons'
|
import React from 'react/addons'
|
||||||
|
|
||||||
import { ViewDefaultStyle } from './ViewStylePropTypes'
|
|
||||||
import View from '.'
|
import View from '.'
|
||||||
|
|
||||||
const ReactTestUtils = React.addons.TestUtils
|
const ReactTestUtils = React.addons.TestUtils
|
||||||
@@ -20,6 +19,14 @@ suite('View', () => {
|
|||||||
assert.equal((root.tagName).toLowerCase(), 'div')
|
assert.equal((root.tagName).toLowerCase(), 'div')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('prop "accessibilityLabel"', () => {
|
||||||
|
const accessibilityLabel = 'accessibilityLabel'
|
||||||
|
const result = ReactTestUtils.renderIntoDocument(<View accessibilityLabel={accessibilityLabel} />)
|
||||||
|
const root = React.findDOMNode(result)
|
||||||
|
|
||||||
|
assert.equal(root.getAttribute('aria-label'), accessibilityLabel)
|
||||||
|
})
|
||||||
|
|
||||||
test('prop "children"', () => {
|
test('prop "children"', () => {
|
||||||
const children = 'children'
|
const children = 'children'
|
||||||
const result = shallowRender(<View>{children}</View>)
|
const result = shallowRender(<View>{children}</View>)
|
||||||
@@ -27,16 +34,6 @@ suite('View', () => {
|
|||||||
assert.equal(result.props.children, children)
|
assert.equal(result.props.children, children)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('prop "className"', () => {
|
|
||||||
const className = 'className'
|
|
||||||
const result = shallowRender(<View className={className} />)
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
(result.props.className).indexOf(className) > -1,
|
|
||||||
'"className" was not transferred'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('prop "component"', () => {
|
test('prop "component"', () => {
|
||||||
const type = 'a'
|
const type = 'a'
|
||||||
const result = ReactTestUtils.renderIntoDocument(<View component={type} />)
|
const result = ReactTestUtils.renderIntoDocument(<View component={type} />)
|
||||||
@@ -62,8 +59,7 @@ suite('View', () => {
|
|||||||
const initial = shallowRender(<View />)
|
const initial = shallowRender(<View />)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
initial.props.style,
|
initial.props.style,
|
||||||
ViewDefaultStyle,
|
View.defaultProps.style
|
||||||
'default "style" is incorrect'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const unsupported = shallowRender(<View style={{ unsupported: 'true' }} />)
|
const unsupported = shallowRender(<View style={{ unsupported: 'true' }} />)
|
||||||
@@ -73,4 +69,15 @@ suite('View', () => {
|
|||||||
'unsupported "style" properties must not be transferred'
|
'unsupported "style" properties must not be transferred'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('prop "testID"', () => {
|
||||||
|
const testID = 'Example.view'
|
||||||
|
const result = ReactTestUtils.renderIntoDocument(<View testID={testID} />)
|
||||||
|
const root = React.findDOMNode(result)
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
root.getAttribute('data-testid'),
|
||||||
|
testID
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { PropTypes } from 'react'
|
|
||||||
import { StylePropTypes } from '../react-native-web-style'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
...StylePropTypes.BackgroundPropTypes,
|
|
||||||
...StylePropTypes.BorderThemePropTypes,
|
|
||||||
...StylePropTypes.LayoutPropTypes,
|
|
||||||
boxShadow: PropTypes.string,
|
|
||||||
opacity: PropTypes.number,
|
|
||||||
transform: PropTypes.string
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/facebook/css-layout#default-values
|
|
||||||
export const ViewDefaultStyle = {
|
|
||||||
alignItems: 'stretch',
|
|
||||||
borderWidth: 0,
|
|
||||||
borderStyle: 'solid',
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
display: 'flex',
|
|
||||||
flexBasis: 'auto',
|
|
||||||
flexDirection: 'column',
|
|
||||||
flexShrink: 0,
|
|
||||||
listStyle: 'none',
|
|
||||||
margin: 0,
|
|
||||||
padding: 0,
|
|
||||||
position: 'relative'
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { pickProps } from '../filterObjectProps'
|
|
||||||
import { WebStyleComponent } from '../react-native-web-style'
|
|
||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ViewStylePropTypes, { ViewDefaultStyle } from './ViewStylePropTypes'
|
|
||||||
|
|
||||||
class View extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.any,
|
|
||||||
className: PropTypes.string,
|
|
||||||
component: PropTypes.oneOfType([
|
|
||||||
PropTypes.func,
|
|
||||||
PropTypes.string
|
|
||||||
]),
|
|
||||||
pointerEvents: PropTypes.oneOf([
|
|
||||||
'auto',
|
|
||||||
'box-none',
|
|
||||||
'box-only',
|
|
||||||
'none'
|
|
||||||
]),
|
|
||||||
style: PropTypes.shape(ViewStylePropTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
className: '',
|
|
||||||
component: 'div'
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { className, pointerEvents, style, ...other } = this.props
|
|
||||||
const filteredStyle = pickProps(style, Object.keys(ViewStylePropTypes))
|
|
||||||
const pointerEventsStyle = pointerEvents && { pointerEvents }
|
|
||||||
const mergedStyle = {
|
|
||||||
...ViewDefaultStyle,
|
|
||||||
...filteredStyle,
|
|
||||||
...pointerEventsStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WebStyleComponent
|
|
||||||
{...other}
|
|
||||||
className={`View ${className}`}
|
|
||||||
style={mergedStyle}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default View
|
|
||||||
Reference in New Issue
Block a user