mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-01-12 22:51:09 +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 React from 'react/addons'
|
||||
|
||||
import { ViewDefaultStyle } from './ViewStylePropTypes'
|
||||
import View from '.'
|
||||
|
||||
const ReactTestUtils = React.addons.TestUtils
|
||||
@@ -20,6 +19,14 @@ suite('View', () => {
|
||||
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"', () => {
|
||||
const children = 'children'
|
||||
const result = shallowRender(<View>{children}</View>)
|
||||
@@ -27,16 +34,6 @@ suite('View', () => {
|
||||
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"', () => {
|
||||
const type = 'a'
|
||||
const result = ReactTestUtils.renderIntoDocument(<View component={type} />)
|
||||
@@ -62,8 +59,7 @@ suite('View', () => {
|
||||
const initial = shallowRender(<View />)
|
||||
assert.deepEqual(
|
||||
initial.props.style,
|
||||
ViewDefaultStyle,
|
||||
'default "style" is incorrect'
|
||||
View.defaultProps.style
|
||||
)
|
||||
|
||||
const unsupported = shallowRender(<View style={{ unsupported: 'true' }} />)
|
||||
@@ -73,4 +69,15 @@ suite('View', () => {
|
||||
'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