Create CoreComponent; update View implementation

This commit is contained in:
Nicolas Gallagher
2015-09-07 09:40:51 -07:00
parent abf2c0307f
commit e7a5d56058
10 changed files with 413 additions and 88 deletions

View File

@@ -0,0 +1,3 @@
import CoreComponent from './modules/CoreComponent'
export default CoreComponent

View 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

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

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

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

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

View 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

View File

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

View File

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

View File

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