diff --git a/.flowconfig b/.flowconfig index 05b2c988f..6d5c6fd43 100644 --- a/.flowconfig +++ b/.flowconfig @@ -24,3 +24,4 @@ module.name_mapper='^\(emotion-theming\)$' -> '/packages/\1/src' module.name_mapper='^\(emotion-server\)$' -> '/packages/\1/src' module.name_mapper='^\(create-emotion-server\)$' -> '/packages/\1/src' module.name_mapper='^\(jest-emotion\)$' -> '/packages/\1/src' +module.name_mapper='^@emotion\/\([a-zA-Z0-9_\-]+\)$' -> '/packages/\1/src/index' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9cc9e1c7b..89d896366 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ packages/site/build package-lock.json .DS_Store .cache -public/ \ No newline at end of file +public/ +/extract.test.emotion.css \ No newline at end of file diff --git a/.yarnrc b/.yarnrc index 19daacaa0..5d4bf9c46 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1 +1 @@ -workspaces-experimental true +--publish.access public diff --git a/docs/docs.yaml b/docs/docs.yaml index daf2ac568..fc923dbca 100644 --- a/docs/docs.yaml +++ b/docs/docs.yaml @@ -40,6 +40,7 @@ - emotion-server - emotion-theming - jest-emotion + - "@emotion/primitives" - create-emotion - create-emotion-styled - create-emotion-server diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index feafcee4c..8a41f7f4c 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -218,6 +218,7 @@ export function buildStyledCallExpression( args: Node[], path: BabelPath, state: EmotionBabelPluginPass, + isCallExpression: boolean, t: Types ) { // unpacking "manually" to prevent array out of bounds access (deopt) @@ -281,9 +282,18 @@ export function buildStyledCallExpression( targetProperty ) - return t.callExpression( + let styledCall = + t.isStringLiteral(tag) && + !isCallExpression && // $FlowFixMe - t.callExpression(identifier, [tag, finalOptions, ...restArgs]), + tag.value[0] !== tag.value[0].toLowerCase() + ? // $FlowFixMe + t.memberExpression(identifier, t.identifier(tag.value)) + : // $FlowFixMe + t.callExpression(identifier, [tag, finalOptions, ...restArgs]) + + return t.callExpression( + styledCall, appendStringToExpressions( getExpressionsFromTemplateLiteral(path.node.quasi, t), stringToAppend, @@ -304,7 +314,7 @@ export function buildStyledObjectCallExpression( const tag = t.isCallExpression(path.node.callee) ? path.node.callee.arguments[0] : t.stringLiteral(path.node.callee.property.name) - + let isCallExpression = t.isCallExpression(path.node.callee) let styledOptions = null let restStyledArgs = [] if (t.isCallExpression(path.node.callee)) { @@ -335,14 +345,18 @@ export function buildStyledObjectCallExpression( path.addComment('leading', '#__PURE__') - return t.callExpression( - t.callExpression(identifier, [ - tag, - buildFinalOptions(t, styledOptions, targetProperty, labelProperty), - ...restStyledArgs - ]), - args - ) + let styledCall = + t.isStringLiteral(tag) && + !isCallExpression && + tag.value[0] !== tag.value[0].toLowerCase() + ? t.memberExpression(identifier, t.identifier(tag.value)) + : t.callExpression(identifier, [ + tag, + buildFinalOptions(t, styledOptions, targetProperty, labelProperty), + ...restStyledArgs + ]) + + return t.callExpression(styledCall, args) } const visited = Symbol('visited') @@ -359,7 +373,12 @@ const importedNameKeys = Object.keys(defaultImportedNames).map( key => (key === 'styled' ? 'default' : key) ) -const defaultEmotionPaths = ['emotion', 'react-emotion', 'preact-emotion'] +const defaultEmotionPaths = [ + 'emotion', + 'react-emotion', + 'preact-emotion', + '@emotion/primitives' +] function getRelativePath(filepath: string, absoluteInstancePath: string) { let relativePath = nodePath.relative( @@ -670,6 +689,7 @@ export default function(babel: Babel) { [t.stringLiteral(path.node.tag.property.name)], path, state, + false, t ) ) @@ -684,6 +704,7 @@ export default function(babel: Babel) { path.node.tag.arguments, path, state, + true, t ) ) diff --git a/packages/babel-plugin-emotion/src/macro-styled.js b/packages/babel-plugin-emotion/src/macro-styled.js index 95d64eb2a..9525adf40 100644 --- a/packages/babel-plugin-emotion/src/macro-styled.js +++ b/packages/babel-plugin-emotion/src/macro-styled.js @@ -30,6 +30,7 @@ function macro(options) { [t.stringLiteral(path.node.tag.property.name)], path, state, + false, t ) ) @@ -40,6 +41,7 @@ function macro(options) { path.node.tag.arguments, path, state, + true, t ) ) diff --git a/packages/babel-plugin-emotion/test/__snapshots__/primitives.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/primitives.test.js.snap new file mode 100644 index 000000000..4e866f2d7 --- /dev/null +++ b/packages/babel-plugin-emotion/test/__snapshots__/primitives.test.js.snap @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`styled inline babel 6 does not change call expressions 1`] = ` +" +/*#__PURE__*/styled('SomeFakeComponent', { + target: 'e8nbyc50' +})('color:hotpink;'); +/*#__PURE__*/styled('SomeFakeComponent', { + target: 'e8nbyc51' +})({});" +`; + +exports[`styled inline babel 6 does not change to a call expression when beginning with a upper case letter 1`] = ` +" +/*#__PURE__*/styled.View(\\"color:hotpink;\\"); +/*#__PURE__*/styled.View({});" +`; + +exports[`styled inline babel 6 other name with @emotion/primitives 1`] = ` +" +import someOtherName from '@emotion/primitives'; +/*#__PURE__*/someOtherName.View('color:hotpink;'); +/*#__PURE__*/someOtherName.View({});" +`; + +exports[`styled inline babel 7 does not change call expressions 1`] = ` +"/*#__PURE__*/ +styled('SomeFakeComponent', { + target: \\"e1u6ll0g0\\" +})(\\"color:hotpink;\\"); + +/*#__PURE__*/ +styled('SomeFakeComponent', { + target: \\"e1u6ll0g1\\" +})({});" +`; + +exports[`styled inline babel 7 does not change to a call expression when beginning with a upper case letter 1`] = ` +"/*#__PURE__*/ +styled.View(\\"color:hotpink;\\"); + +/*#__PURE__*/ +styled.View({});" +`; + +exports[`styled inline babel 7 other name with @emotion/primitives 1`] = ` +"import someOtherName from '@emotion/primitives'; + +/*#__PURE__*/ +someOtherName.View(\\"color:hotpink;\\"); + +/*#__PURE__*/ +someOtherName.View({});" +`; diff --git a/packages/babel-plugin-emotion/test/primitives.test.js b/packages/babel-plugin-emotion/test/primitives.test.js new file mode 100644 index 000000000..83316783a --- /dev/null +++ b/packages/babel-plugin-emotion/test/primitives.test.js @@ -0,0 +1,27 @@ +// @flow + +import { createInlineTests } from './util' + +const cases = { + 'does not change to a call expression when beginning with a upper case letter': { + code: ` + styled.View\`color: hotpink;\` + styled.View({}) + ` + }, + 'other name with @emotion/primitives': { + code: ` + import someOtherName from '@emotion/primitives' + someOtherName.View\`color: hotpink;\` + someOtherName.View({}) + ` + }, + 'does not change call expressions': { + code: ` + styled('SomeFakeComponent')\`color: hotpink;\` + styled('SomeFakeComponent')({}) + ` + } +} + +createInlineTests('styled inline', cases) diff --git a/packages/primitives/README.md b/packages/primitives/README.md new file mode 100644 index 000000000..54f9b0110 --- /dev/null +++ b/packages/primitives/README.md @@ -0,0 +1,151 @@ +# @emotion/primitives + +> Style and render primitive interfaces across multiple targets with emotion + +## Introduction + +Emotion primitives makes it easy to style and render primitives across multiple platforms like the web, React Native and Sketch using the `emotion` API. + +## Install + +``` +npm install @emotion/primitives +``` + +or if you use yarn + +``` +yarn add @emotion/primitives +``` + +This package also depends on `react`, `react-primitives` and `prop-types` so make sure you've them installed. + +## Example + +```js +import React from 'react' +import styled, { css } from '@emotion/primitives' + +import { ThemeProvider } from 'emotion-theming' + +const theme = { + color: 'hotpink', + backgroundColor: 'purple' +} + +const Container = styled.View` + display: flex; + justify-content: center; + align-items: center; + margin: 50px; + border: 5px solid red; + background-color: ${props => props.theme.backgroundColor}; +` + +const Description = styled.Text({ + color: 'hotpink' +}) + +const Image = styled.Image` + padding: 40px; +` + +const emotionLogo = + 'https://cdn.rawgit.com/emotion-js/emotion/master/emotion.png' + +class App extends React.Component { + render() { + return ( + + + + Emotion Primitives + + + + + ) + } +} +``` + +[![Edit n3nmq8v46j](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/n3nmq8v46j) + +## Supported primitives + +* **Text** + +* **View** + +* **Image** + +## Usage with `react-sketchapp` + +### Installing dependencies + +* [`react-sketchapp`](https://github.com/airbnb/react-sketchapp) + +``` +yarn add react-sketchapp +``` + +* `skpm` for building and publishing sketch plugins. This is required to render the components and build them to Sketch environment. + +``` +yarn add @skpm/builder --dev +``` + +### Configuring `skpm` builder + +Once you've installed `skpm`, + +* create a new field `skpm` in `package.json` + +```json + "skpm": { + "main": "some-name.sketchplugin", + "manifest": "manifest.json" +}, +``` + +* Create `manifest.json` file with following content - + +```json +{ + "compatibleVersion": 3, + "bundleVersion": 1, + "commands": [ + { + "name": "react-sketchapp: some-name", + "identifier": "main", + "script": "./your-app-file.js" + } + ], + "menu": { + "isRoot": true, + "items": ["main"] + } +} +``` + +* add this to your `scripts` section of your `package.json` + +```json +scripts: { + "render": "skpm-build --watch --run", +} +``` + +and finally run `yarn render` to render the components to Sketch. + +For a complete reference, checkout [this](https://github.com/airbnb/react-sketchapp/blob/master/examples/emotion/src) project setup. diff --git a/packages/primitives/examples/index.js b/packages/primitives/examples/index.js new file mode 100644 index 000000000..96cc8d3aa --- /dev/null +++ b/packages/primitives/examples/index.js @@ -0,0 +1,32 @@ +import React from 'react' + +import styled from '@emotion/primitives' + +const Container = styled.View` + display: flex; + justify-content: center; + margin-top: 50px; +` + +const Title = styled.Text` + font-size: 20px; + color: hotpink; +` + +const Description = styled.Text` + font-size: 15px; + color: #4c4c4c; +` + +class App extends React.Component { + render() { + return ( + + Emotion Primitives + Style and render primitives across targets. + + ) + } +} + +export default App diff --git a/packages/primitives/macro.js b/packages/primitives/macro.js new file mode 100644 index 000000000..153d6cd3d --- /dev/null +++ b/packages/primitives/macro.js @@ -0,0 +1 @@ +module.exports = require('babel-plugin-emotion').macros.styled diff --git a/packages/primitives/package.json b/packages/primitives/package.json new file mode 100644 index 000000000..a6a67dc2f --- /dev/null +++ b/packages/primitives/package.json @@ -0,0 +1,31 @@ +{ + "name": "@emotion/primitives", + "version": "1.0.0", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "files": ["src", "dist"], + "dependencies": { + "css-to-react-native": "^2.2.0", + "babel-plugin-emotion": "^9.2.4", + "@emotion/is-prop-valid": "^0.6.3" + }, + "peerDependencies": { + "react": "15.x || 16.x", + "react-primitives": "^0.5.1", + "prop-types": "15.x" + }, + "devDependencies": { + "emotion-theming": "^9.1.2", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", + "react": "^16.4.0", + "react-primitives": "^0.5.1" + }, + "homepage": "https://emotion.sh", + "license": "MIT", + "repository": "https://github.com/emotion-js/emotion/tree/master/packages/emotion-primitives", + "keywords": ["styles", "emotion", "react", "css", "css-in-js"], + "bugs": { + "url": "https://github.com/emotion-js/emotion/issues" + } +} diff --git a/packages/primitives/src/css.js b/packages/primitives/src/css.js new file mode 100644 index 000000000..754828faf --- /dev/null +++ b/packages/primitives/src/css.js @@ -0,0 +1,122 @@ +// @flow +import transform from 'css-to-react-native' +import { StyleSheet } from 'react-primitives' +import { interleave } from './utils' + +// this is for handleInterpolation +// they're reset on every call to css +// this is done so we don't create a new +// handleInterpolation function on every css call +let styles +let buffer +let lastType + +function handleInterpolation(interpolation: *, i: number, arr: Array<*>) { + let type = typeof interpolation + + if (type === 'function') { + if (this === undefined) { + if (process.env.NODE_ENV !== 'production') { + console.error( + 'Interpolating functions in css calls is not allowed.\n' + + 'If you want to have a css call based on props, create a function that returns a css call like this\n' + + 'let dynamicStyle = (props) => css`color: ${props.color}`\n' + + 'It can be called directly with props or interpolated in a styled call like this\n' + + 'let SomeComponent = styled.View`${dynamicStyle}`' + ) + } + } else { + handleInterpolation.call( + this, + interpolation( + // $FlowFixMe + this.mergedProps + ), + i, + arr + ) + } + return + } + let isIrrelevant = interpolation == null || type === 'boolean' + let isRnStyle = + (type === 'object' && !Array.isArray(interpolation)) || type === 'number' + if (lastType === 'string' && (isRnStyle || isIrrelevant)) { + let converted = convertStyles(buffer) + if (converted !== undefined) { + styles.push(converted) + } + buffer = '' + } + if (isIrrelevant) { + return + } + + if (type === 'string') { + buffer += interpolation + + if (arr.length - 1 === i) { + let converted = convertStyles(buffer) + if (converted !== undefined) { + styles.push(converted) + } + buffer = '' + } + } + if (isRnStyle) { + styles.push(interpolation) + } + if (Array.isArray(interpolation)) { + interpolation.forEach(handleInterpolation, this) + } + lastType = type +} + +export function css(...args: any) { + let vals + + // these are declared earlier + // this is done so we don't create a new + // handleInterpolation function on every css call + styles = [] + buffer = '' + lastType = undefined + + if (args[0] == null || args[0].raw === undefined) { + vals = args + } else { + vals = interleave(args) + } + + vals.forEach(handleInterpolation, this) + + return StyleSheet.flatten(styles) +} + +let propertyValuePattern = /\s*([^\s]+)\s*:\s*(.+?)\s*$/ + +function convertPropertyValue(style) { + // Get prop name and prop value + let match = propertyValuePattern.exec(style) + // match[2] will be " " in cases where there is no value + // but there is whitespace, e.g. "color: " + if (match !== null && match[2] !== ' ') { + // the first value in the array will + // be the whole string so we remove it + match.shift() + // yes i know this looks funny + this.push(match) + } +} + +function convertStyles(str: string) { + if (str.trim() === '') return + + const stylePairs = [] + + const parsedString = str.split(';') + + parsedString.forEach(convertPropertyValue, stylePairs) + + return transform(stylePairs) +} diff --git a/packages/primitives/src/index.js b/packages/primitives/src/index.js new file mode 100644 index 000000000..a6b73f096 --- /dev/null +++ b/packages/primitives/src/index.js @@ -0,0 +1,16 @@ +// @flow +import { Text, View, Image } from 'react-primitives' + +import { createStyled } from './styled' + +const assignPrimitives = styled => { + createStyled.Text = createStyled(Text) + createStyled.View = createStyled(View) + createStyled.Image = createStyled(Image) + + return styled +} + +export { css } from './css' + +export default /* #__PURE__ */ assignPrimitives(createStyled) diff --git a/packages/primitives/src/styled.js b/packages/primitives/src/styled.js new file mode 100644 index 000000000..9574b126d --- /dev/null +++ b/packages/primitives/src/styled.js @@ -0,0 +1,120 @@ +// @flow +import * as React from 'react' +import { View, Text, Image } from 'react-primitives' +import { + channel, + contextTypes, + setTheme, + testAlwaysTrue, + pickAssign, + interleave +} from './utils' +import { + testPickPropsOnPrimitiveComponent, + testPickPropsOnOtherComponent +} from './test-props' +import { css } from './css' + +function isPrimitiveComponent(component: React.ElementType) { + switch (component) { + case View: + case Text: + case Image: { + return true + } + } + return false +} + +type State = { + theme: Object +} | null + +/** + * a function that returns a styled component which render styles on multiple targets with same code + */ + +type CreateStyledComponent = (...styles: any) => React.ElementType + +type BaseCreateStyled = (tag: React.ElementType) => CreateStyledComponent + +export type CreateStyled = BaseCreateStyled & { + View: CreateStyledComponent, + Text: CreateStyledComponent, + Image: CreateStyledComponent +} + +// $FlowFixMe +let createStyled: CreateStyled = function createStyled( + component: React.ElementType +) { + let isPrimitive = isPrimitiveComponent(component) + let pickTest = isPrimitive + ? testPickPropsOnPrimitiveComponent + : testPickPropsOnOtherComponent + return function createStyledComponent(...rawStyles: *) { + let styles + + if (rawStyles[0] == null || rawStyles[0].raw === undefined) { + styles = rawStyles + } else { + styles = interleave(rawStyles) + } + class Styled extends React.Component<*, State> { + unsubscribe: number | void + mergedProps: Object + + static withComponent = (newComponent: React.ElementType) => + createStyled(newComponent)(...styles) + + componentWillMount() { + if (this.context[channel] !== undefined) { + this.unsubscribe = this.context[channel].subscribe( + setTheme.bind(this) + ) + } + } + + componentWillUnmount() { + if (this.unsubscribe !== undefined) { + this.context[channel].unsubscribe(this.unsubscribe) + } + } + + render() { + const { props, state } = this + + // Similar to create-emotion-styled component implementation + this.mergedProps = pickAssign(testAlwaysTrue, {}, props, { + theme: (state !== null && state.theme) || props.theme || {} + }) + let stylesWithStyleProp = styles + if (props.style) { + stylesWithStyleProp = styles.concat(props.style) + } + const emotionStyles = css.apply(this, stylesWithStyleProp) + + return React.createElement( + component, + pickAssign(pickTest, {}, props, { + ref: props.innerRef, + style: emotionStyles + }) + ) + } + } + + Styled.contextTypes = contextTypes + + Styled.displayName = `emotion(${getDisplayName(component)})` + + return Styled + } +} + +const getDisplayName = primitive => + typeof primitive === 'string' + ? primitive + : primitive.displayName || primitive.name || 'Styled' + +export { createStyled } diff --git a/packages/primitives/src/test-props.js b/packages/primitives/src/test-props.js new file mode 100644 index 000000000..e2d2eb5e1 --- /dev/null +++ b/packages/primitives/src/test-props.js @@ -0,0 +1,62 @@ +// @flow +import isPropValid from '@emotion/is-prop-valid' + +const forwardableProps = { + // primitive props + abortPrefetch: true, + accessibilityComponentType: true, + accessibilityElementsHidden: true, + accessibilityLabel: true, + accessibilityLiveRegion: true, + accessibilityTraits: true, + accessibilityViewIsModal: true, + accessible: true, + adjustsFontSizeToFit: true, + allowFontScaling: true, + blurRadius: true, + capInsets: true, + collapsable: true, + defaultSource: true, + disabled: true, + ellipsizeMode: true, + fadeDuration: true, + getSize: true, + hitSlop: true, + importantForAccessibility: true, + loadingIndicatorSource: true, + Methods: true, + minimumFontScale: true, + nativeID: true, + needsOffscreenAlphaCompositing: true, + numberOfLines: true, + pointerEvents: true, + prefetch: true, + pressRetentionOffset: true, + queryCache: true, + removeClippedSubviews: true, + renderToHardwareTextureAndroid: true, + resizeMethod: true, + resizeMode: true, + resolveAssetSource: true, + selectable: true, + selectionColor: true, + shouldRasterizeIOS: true, + source: true, + suppressHighlighting: true, + testID: true, + textBreakStrategy: true +} + +export function testPickPropsOnPrimitiveComponent(prop: string) { + return ( + forwardableProps[prop] === true || + // This will allow the standard react props + // and dom props since people could + // be using it on the web + isPropValid(prop) + ) +} + +export function testPickPropsOnOtherComponent(prop: string) { + return prop !== 'theme' && prop !== 'innerRef' +} diff --git a/packages/primitives/src/utils.js b/packages/primitives/src/utils.js new file mode 100644 index 000000000..a3fbc542e --- /dev/null +++ b/packages/primitives/src/utils.js @@ -0,0 +1,45 @@ +// @flow +import PropTypes from 'prop-types' + +export const channel = '__EMOTION_THEMING__' + +export const contextTypes = { + [channel]: PropTypes.object +} + +export function setTheme(theme: Object) { + this.setState({ theme }) +} + +export const testAlwaysTrue = () => true + +export const pickAssign: ( + testFn: (key: string) => boolean, + target: {}, + ...sources: Array<{}> +) => Object = function(testFn, target) { + let i = 2 + let length = arguments.length + for (; i < length; i++) { + let source = arguments[i] + let key + for (key in source) { + if (testFn(key)) { + target[key] = source[key] + } + } + } + return target +} + +export function interleave(vals: Array<*>) { + let strings = vals[0] + let finalArray = [strings[0]] + for (let i = 1, len = vals.length; i < len; i++) { + finalArray.push(vals[i]) + if (strings[i] !== undefined) { + finalArray.push(strings[i]) + } + } + return finalArray +} diff --git a/packages/primitives/test/__snapshots__/emotion-primitives.test.js.snap b/packages/primitives/test/__snapshots__/emotion-primitives.test.js.snap new file mode 100644 index 000000000..fbd33acaf --- /dev/null +++ b/packages/primitives/test/__snapshots__/emotion-primitives.test.js.snap @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Emotion primitives primitive should work with \`withComponent\` 1`] = ` + + Mike + +`; + +exports[`Emotion primitives should pass props in withComponent 1`] = ` + +`; + +exports[`Emotion primitives should pass props in withComponent 2`] = ` + +`; + +exports[`Emotion primitives should render 1`] = ` + +`; + +exports[`Emotion primitives should render primitive with style prop 1`] = ` + + Emotion primitives + +`; + +exports[`Emotion primitives should render the primitive on changing the props 1`] = ` + + Emotion Primitives + +`; + +exports[`Emotion primitives should render the primitive when styles applied using object style notation 1`] = ` + + Emotion Primitives + +`; + +exports[`Emotion primitives should style any other component 1`] = ` + + Hello World + +`; + +exports[`Emotion primitives should unmount with emotion-theming 1`] = ` +
+

+ Hello World +

+
+`; + +exports[`Emotion primitives should work with emotion-theming 1`] = ` + + Hello World + +`; diff --git a/packages/primitives/test/css.test.js b/packages/primitives/test/css.test.js new file mode 100644 index 000000000..430f3f7a4 --- /dev/null +++ b/packages/primitives/test/css.test.js @@ -0,0 +1,114 @@ +// @flow +import { css } from '@emotion/primitives' + +let returnArguments = (...args) => args + +test('basic', () => { + expect(css` + color: hotpink; + ${{ backgroundColor: 'green' }}; + `).toEqual({ color: 'hotpink', backgroundColor: 'green' }) + expect(css({ color: 'green' })).toEqual({ color: 'green' }) + expect(css([{ color: 'green' }, `background-color:yellow;`])).toEqual({ + color: 'green', + backgroundColor: 'yellow' + }) + expect(css([{ color: 'green' }])).toEqual({ color: 'green' }) +}) + +test('order with string and object', () => { + // this test checks the keys instead of the objects + // because we care about the order of the keys + expect( + Object.keys( + css({ color: 'green' }, `background-color:yellow;`, { flex: 2 }) + ) + ).toEqual(['color', 'backgroundColor', 'flex']) + expect( + Object.keys( + css([ + [{ color: 'green' }, `background-color:yellow;`], + { + flex: 2 + } + ]) + ) + ).toEqual(['color', 'backgroundColor', 'flex']) + expect( + Object.keys( + css([ + { color: 'green' }, + [ + `background-color:yellow;`, + { + flex: 2 + } + ] + ]) + ) + ).toEqual(['color', 'backgroundColor', 'flex']) + expect( + Object.keys( + css([ + { color: 'green' }, + [ + { flex: 8 }, + `background-color:yellow;`, + [`flex-grow: 1;`, { flexDirection: 'row' }] + ] + ]) + ) + ).toEqual(['color', 'flex', 'backgroundColor', 'flexGrow', 'flexDirection']) +}) + +it('allows function interpolations when this.mergedProps is defined', () => { + expect( + css.call({ mergedProps: { thing: true } }, props => ({ + color: props.thing && 'hotpink' + })) + ).toEqual({ color: 'hotpink' }) +}) + +it('works with nested functions', () => { + expect( + css.call({ mergedProps: { thing: true } }, props => () => ({ + color: props.thing && 'hotpink' + })) + ).toEqual({ color: 'hotpink' }) +}) + +it('works with functions in tagged template literals', () => { + expect( + css.call( + { mergedProps: {} }, + ...returnArguments` + color: ${() => 'hotpink'}; + ` + ) + ).toEqual({ color: 'hotpink' }) +}) + +test('last arg falsy and string before that', () => { + expect(css('color:hotpink;', false)).toEqual({ color: 'hotpink' }) +}) + +test('falsy value in the middle', () => { + expect( + css` + color: ${false}; + background-color: hotpink; + ` + ).toEqual({ backgroundColor: 'hotpink' }) +}) + +test('composition', () => { + let firstStyle = css` + color: hotpink; + ` + expect( + css` + background-color: green; + ${firstStyle}; + ` + ).toEqual({ backgroundColor: 'green', color: 'hotpink' }) +}) diff --git a/packages/primitives/test/emotion-primitives.test.js b/packages/primitives/test/emotion-primitives.test.js new file mode 100644 index 000000000..6167ac197 --- /dev/null +++ b/packages/primitives/test/emotion-primitives.test.js @@ -0,0 +1,186 @@ +// @flow +import './mock-primitives' +import * as React from 'react' +import renderer from 'react-test-renderer' +import Enzyme from 'enzyme' +import Adapter from 'enzyme-adapter-react-16' +import reactPrimitives from 'react-primitives' +import { ThemeProvider } from 'emotion-theming' +import { render, unmountComponentAtNode } from 'react-dom' + +import styled from '@emotion/primitives' + +Enzyme.configure({ adapter: new Adapter() }) + +const StyleSheet = reactPrimitives.StyleSheet + +const theme = { backgroundColor: 'magenta', display: 'flex' } + +describe('Emotion primitives', () => { + test('should not throw an error when used valid primitive', () => { + expect(() => styled.Text({})).not.toThrow() + }) + + test('should throw an error when used invalid primitive', () => { + expect(() => styled.TEXT({})).toThrow() + }) + + test('should render the primitive when styles applied using object style notation', () => { + const Text = styled.Text` + color: red; + font-size: 20px; + background-color: ${props => props.back}; + ` + const tree = renderer + .create( + + Emotion Primitives + + ) + .toJSON() + expect(tree).toMatchSnapshot() + }) + + it('should work with emotion-theming', () => { + const Text = styled.Text` + color: ${props => props.theme.backgroundColor}; + ` + + const tree = renderer + .create( + + Hello World + + ) + .toJSON() + + expect(tree).toMatchSnapshot() + }) + + it('should unmount with emotion-theming', () => { + const Text = styled('p')` + display: ${props => props.theme.display}; + ` + + let mountNode = document.createElement('div') + + render( + + + Hello World + + , + mountNode + ) + expect(mountNode).toMatchSnapshot() + unmountComponentAtNode(mountNode) + expect(mountNode.querySelector('#something')).toBe(null) + }) + + test('should render the primitive on changing the props', () => { + const Text = styled.Text({ padding: '20px' }, props => ({ + color: props.decor + })) + const tree = renderer + .create(Emotion Primitives) + .toJSON() + expect(tree).toMatchSnapshot() + }) + + test('should render primitive with style prop', () => { + const Title = styled.Text` + color: hotpink; + ` + const tree = renderer + .create(Emotion primitives) + .toJSON() + expect(tree).toMatchSnapshot() + const wrapper = Enzyme.shallow( + Emotion primitives + ) + expect(wrapper.find('Text').prop('style')).toEqual({ + color: 'hotpink', + padding: 10 + }) + }) + + it('should work with StyleSheet.create API', () => { + const styles = StyleSheet.create({ foo: { color: 'red' } }) + const Text = styled.Text` + font-size: 10px; + ` + const wrapper = Enzyme.shallow( + Emotion Primitives + ) + expect(wrapper.find('Text').prop('style')).toEqual({ + color: 'red', + fontSize: 10 + }) + }) + + test('primitive should work with `withComponent`', () => { + const Text = styled.Text` + color: ${props => props.decor}; + ` + const Name = Text.withComponent(reactPrimitives.Text) + const tree = renderer.create(Mike).toJSON() + expect(tree).toMatchSnapshot() + }) + + it('should style any other component', () => { + const Text = styled.Text` + color: hotpink; + ` + const Title = () => Hello World + const StyledTitle = styled(Title)` + font-size: 20px; + font-style: ${props => props.sty}; + ` + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() + }) + + it('innerRef', () => { + const Text = styled('p')` + color: hotpink; + ` + let ref = React.createRef() + const rootNode = document.createElement('div') + + render(, rootNode) + expect(ref.current).toBe(rootNode.querySelector('#something')) + unmountComponentAtNode(rootNode) + }) + + it('should pass props in withComponent', () => { + const ViewOne = styled.View` + background-color: ${props => props.color}; + ` + const treeOne = renderer.create() + const ViewTwo = ViewOne.withComponent(reactPrimitives.Text) + const treeTwo = renderer.create() + + expect(treeOne).toMatchSnapshot() + expect(treeTwo).toMatchSnapshot() + }) + + it('should render ', () => { + const Image = styled.Image` + border: 2px solid hotpink; + ` + const tree = renderer + .create( + + ) + .toJSON() + + expect(tree).toMatchSnapshot() + }) +}) diff --git a/packages/primitives/test/mock-primitives.js b/packages/primitives/test/mock-primitives.js new file mode 100644 index 000000000..1309ca11c --- /dev/null +++ b/packages/primitives/test/mock-primitives.js @@ -0,0 +1,16 @@ +// @flow +/* eslint-env jest */ + +jest.mock('react-primitives', () => { + // $FlowFixMe + let realPrimitives = require.requireActual('react-primitives') + + return { + ...realPrimitives, + // mock the components to strings so that we can see the actual styles rather + // than a bunch of class names that don't mean anything + View: 'View', + Text: 'Text', + Image: 'Image' + } +}) diff --git a/packages/primitives/test/no-babel/.babelrc b/packages/primitives/test/no-babel/.babelrc new file mode 100644 index 000000000..3ef03c14a --- /dev/null +++ b/packages/primitives/test/no-babel/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["flow", "env", "react"], + "plugins": ["codegen"] +} diff --git a/packages/primitives/test/no-babel/__snapshots__/basic.test.js.snap b/packages/primitives/test/no-babel/__snapshots__/basic.test.js.snap new file mode 100644 index 000000000..a35d0c19f --- /dev/null +++ b/packages/primitives/test/no-babel/__snapshots__/basic.test.js.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render the primitive when styles applied using object style notation 1`] = ` + + Emotion Primitives + +`; diff --git a/packages/primitives/test/no-babel/basic.test.js b/packages/primitives/test/no-babel/basic.test.js new file mode 100644 index 000000000..343b9e24b --- /dev/null +++ b/packages/primitives/test/no-babel/basic.test.js @@ -0,0 +1,56 @@ +// @flow +import '../mock-primitives' +import * as React from 'react' +import styled, { css } from '@emotion/primitives' +import renderer from 'react-test-renderer' + +// this isn't intented to test everything +// this is just to make sure things work +// without the babel plugin + +test('basic', () => { + expect(css` + color: hotpink; + ${{ backgroundColor: 'green' }}; + `).toEqual({ color: 'hotpink', backgroundColor: 'green' }) + expect(css({ color: 'green' })).toEqual({ color: 'green' }) + expect(css([{ color: 'green' }, `background-color:yellow;`])).toEqual({ + color: 'green', + backgroundColor: 'yellow' + }) + expect(css([{ color: 'green' }])).toEqual({ color: 'green' }) +}) + +test('falsy value in the middle', () => { + expect( + css` + color: ${false}; + background-color: hotpink; + ` + ).toEqual({ backgroundColor: 'hotpink' }) +}) + +test('should render the primitive when styles applied using object style notation', () => { + const Text = styled.Text` + color: red; + font-size: 20px; + background-color: ${props => props.back}; + ` + const tree = renderer + .create( + + Emotion Primitives + + ) + .toJSON() + expect(tree).toMatchSnapshot() +}) + +// this needs to be here since the babel plugin will remove the whitespace +test('empty string', () => { + // prettier-ignore + let style = css` + + ` + expect(style).toEqual({}) +}) diff --git a/packages/primitives/test/warnings.test.js b/packages/primitives/test/warnings.test.js new file mode 100644 index 000000000..7aa51c2bb --- /dev/null +++ b/packages/primitives/test/warnings.test.js @@ -0,0 +1,20 @@ +// @flow +import { css } from '@emotion/primitives' + +// $FlowFixMe +console.error = jest.fn() + +afterEach(() => { + jest.clearAllMocks() +}) + +it('does warn when functions are passed to cx calls ', () => { + css(() => ({})) + expect(console.error).toBeCalledWith( + 'Interpolating functions in css calls is not allowed.\n' + + 'If you want to have a css call based on props, create a function that returns a css call like this\n' + + 'let dynamicStyle = (props) => css`color: ${props.color}`\n' + + 'It can be called directly with props or interpolated in a styled call like this\n' + + 'let SomeComponent = styled.View`${dynamicStyle}`' + ) +}) diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index 0ce5dbdb2..000000000 --- a/rollup.config.js +++ /dev/null @@ -1,91 +0,0 @@ -import resolve from 'rollup-plugin-node-resolve' -import { uglify } from 'rollup-plugin-uglify' -import replace from 'rollup-plugin-replace' -import babel from 'rollup-plugin-babel' -import alias from 'rollup-plugin-alias' -import cjs from 'rollup-plugin-commonjs' -import path from 'path' -import getLernaPackages from 'get-lerna-packages' -import { rollup as lernaAliases } from 'lerna-alias' - -const flatMap = (iteratee, arr) => [].concat(...arr.map(iteratee)) -const uniq = arr => [...new Set(arr)] - -const pkg = require(path.resolve(process.cwd(), './package.json')) - -const basePlugins = [ - cjs({ exclude: [path.join(__dirname, 'packages', '*/src/**/*')] }), - resolve(), - babel({ - presets: [ - [ - '@babel/env', - { - loose: true, - modules: false, - exclude: ['transform-typeof-symbol'] - } - ], - '@babel/stage-0', - '@babel/react', - '@babel/flow' - ], - plugins: ['codegen', 'closure-elimination'], - babelrc: false - }) -] - -const baseConfig = { - input: './src/index.js' -} - -const baseExternal = ['react', 'prop-types', 'preact'] - -const mainConfig = Object.assign({}, baseConfig, { - external: uniq( - baseExternal.concat( - flatMap(dir => { - const { - dependencies = {}, - peerDependencies = {} - } = require(`${dir}/package.json`) - - return [...Object.keys(dependencies), ...Object.keys(peerDependencies)] - }, getLernaPackages()) - ) - ), - plugins: basePlugins, - output: [ - { - file: pkg.main, - format: 'cjs', - sourcemap: true - }, - { - file: pkg.module, - format: 'es', - sourcemap: true - } - ] -}) - -const umdConfig = Object.assign({}, baseConfig, { - plugins: [alias(lernaAliases())].concat(basePlugins).concat( - replace({ - 'process.env.NODE_ENV': JSON.stringify('production') - }), - uglify() - ), - output: [ - { - file: './dist/emotion.umd.min.js', - format: 'umd', - name: pkg.name, - globals: { react: 'React', 'prop-types': 'PropTypes', preact: 'preact' }, - sourcemap: true - } - ], - external: baseExternal -}) - -export default [mainConfig, umdConfig] diff --git a/site/gatsby-config.js b/site/gatsby-config.js index 63f4a39a0..3c945a736 100644 --- a/site/gatsby-config.js +++ b/site/gatsby-config.js @@ -9,7 +9,11 @@ module.exports = { title: `emotion` }, plugins: packages - .map(pkg => path.resolve(`${__dirname}/../packages/${pkg}/README.md`)) + .map(pkg => + path.resolve( + `${__dirname}/../packages/${pkg.replace('@emotion/', '')}/README.md` + ) + ) .map(file => ({ resolve: 'gatsby-source-filesystem', options: { diff --git a/site/gatsby-node.js b/site/gatsby-node.js index 0b78063f7..de0cb8683 100644 --- a/site/gatsby-node.js +++ b/site/gatsby-node.js @@ -92,14 +92,25 @@ exports.onCreateNode = async ({ node, actions, getNode, loadNodeContent }) => { ) { const fileNode = getNode(node.parent) - const splitAbsolutePath = fileNode.absolutePath.split(path.sep) createNodeField({ node, name: `slug`, value: fileNode.name === 'README' - ? splitAbsolutePath[splitAbsolutePath.length - 2] + ? getNameForPackage(fileNode.absolutePath) : fileNode.name }) } } + +function getNameForPackage(absolutePath) { + try { + return require(`${path.parse(absolutePath).dir}/package.json`).name + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + const splitAbsolutePath = absolutePath.split(path.sep) + return splitAbsolutePath[splitAbsolutePath.length - 2] + } + throw e + } +} diff --git a/site/src/templates/doc.js b/site/src/templates/doc.js index 463598bc7..78138aa5f 100644 --- a/site/src/templates/doc.js +++ b/site/src/templates/doc.js @@ -13,7 +13,6 @@ import { graphql } from 'gatsby' import DocWrapper from '../components/DocWrapper' type Props = { - markdownNodes: *, data: { doc: { htmlAst: HASTRoot, @@ -84,8 +83,7 @@ const createLiveCode = memoize(logoUrl => props => ( export default class DocRoute extends React.Component { render() { - // eslint-disable-next-line no-unused-vars - const { data, markdownNodes } = this.props + const { data } = this.props const { doc, avatar } = data return ( diff --git a/site/src/utils/misc.js b/site/src/utils/misc.js index 3e499a2c0..d5b193dd5 100644 --- a/site/src/utils/misc.js +++ b/site/src/utils/misc.js @@ -13,6 +13,9 @@ export function getDocMap( ) { const docMap: { [string]: string } = {} edges.forEach(({ node }) => { + if (node.fields === null) { + return + } docMap[node.fields.slug] = node.frontmatter.title }) return docMap diff --git a/yarn.lock b/yarn.lock index 03c3a0a40..313b4dbba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1040,10 +1040,20 @@ dependencies: "@emotion/memoize" "^0.6.2" +"@emotion/is-prop-valid@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.6.3.tgz#b04956e2d655027fe88c6234669fd7064fb071cc" + dependencies: + "@emotion/memoize" "^0.6.3" + "@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.2": version "0.6.2" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.2.tgz#138e00b332d519b4e307bded6159e5ba48aba3ae" +"@emotion/memoize@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.3.tgz#64379a1d6af6f8d4fe8bd6efe9d9e824ea4b22d8" + "@emotion/provider@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@emotion/provider/-/provider-0.9.0.tgz#df5b0e7bb4651f87155064fe0cf418b888c15965" @@ -1477,6 +1487,13 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" +animated@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/animated/-/animated-0.1.5.tgz#83df8dc443d57abab7b0bb04818b0b655b31c9b9" + dependencies: + invariant "^2.2.0" + normalize-css-color "^1.0.1" + ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -1630,7 +1647,7 @@ array-filter@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" -array-find-index@^1.0.1: +array-find-index@^1.0.1, array-find-index@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -1691,7 +1708,11 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@~2.0.3: +art@^0.10.1: + version "0.10.2" + resolved "https://registry.yarnpkg.com/art/-/art-0.10.2.tgz#55c3738d82a3a07e0623943f070ebe86297253d9" + +asap@^2.0.5, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -4169,6 +4190,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-class@^15.6.2: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-fetch@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.0.0.tgz#a17475449561e0f325146cea636a8619efb9b382" @@ -4296,6 +4325,14 @@ css-to-react-native@^2.0.3: fbjs "^0.8.5" postcss-value-parser "^3.3.0" +css-to-react-native@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.1.tgz#7f3f4c95de65501b8720c87bf0caf1f39073b88e" + dependencies: + css-color-keywords "^1.0.0" + fbjs "^0.8.5" + postcss-value-parser "^3.3.0" + css-what@1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-1.0.0.tgz#d7cc2df45180666f99d2b14462639469e00f736c" @@ -4468,6 +4505,10 @@ death@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" +debounce@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" + debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4619,6 +4660,12 @@ dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" +deep-assign@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-2.0.0.tgz#ebe06b1f07f08dae597620e3dd1622f371a1c572" + dependencies: + is-obj "^1.0.0" + deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -4719,6 +4766,10 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +deline@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/deline/-/deline-1.0.4.tgz#6c05c87836926e1a1c63e47882f3d2eb2c6f14c9" + depd@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" @@ -5176,7 +5227,7 @@ envinfo@^5.8.1: version "5.10.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df" -enzyme-adapter-react-16@^1.0.4: +enzyme-adapter-react-16@^1.0.4, enzyme-adapter-react-16@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" dependencies: @@ -5202,7 +5253,7 @@ enzyme-to-json@^3.2.1: dependencies: lodash "^4.17.4" -enzyme@^3.1.1: +enzyme@^3.1.1, enzyme@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479" dependencies: @@ -6242,6 +6293,10 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" +flexibility@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flexibility/-/flexibility-2.0.1.tgz#ad323aafc40f469ce624286518fc4d7cd72b7c77" + flow-bin@^0.73.0: version "0.73.0" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.73.0.tgz#da1b90a02b0ef9c439f068c2fc14968db83be425" @@ -7868,6 +7923,13 @@ inline-style-prefixer@^3.0.6: bowser "^1.7.3" css-in-js-utils "^2.0.0" +inline-style-prefixer@^4.0.0, inline-style-prefixer@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911" + dependencies: + bowser "^1.7.3" + css-in-js-utils "^2.0.0" + inquirer@3.3.0, inquirer@^3.0.1, inquirer@^3.0.6, inquirer@^3.2.2: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" @@ -10198,6 +10260,10 @@ nopt@~1.0.10: dependencies: abbrev "1" +normalize-css-color@^1.0.1, normalize-css-color@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/normalize-css-color/-/normalize-css-color-1.0.2.tgz#02991e97cccec6623fe573afbbf0de6a1f3e9f8d" + normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -11596,6 +11662,21 @@ react-addons-perf@^15.6.0-rc.1: fbjs "^0.8.9" object-assign "^4.1.0" +react-art@^16.2.0: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-art/-/react-art-16.4.1.tgz#3544c13038d7ddfe8b1cc1170b02d99492c03b50" + dependencies: + art "^0.10.1" + create-react-class "^15.6.2" + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +react-css-property-operations@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/react-css-property-operations/-/react-css-property-operations-15.4.1.tgz#4c0e305df4cc35f0f5fd2d65a79214c8b012db35" + react-dev-utils@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-4.2.1.tgz#9f2763e7bafa1a1b9c52254d2a479deec280f111" @@ -11691,6 +11772,40 @@ react-live@^1.7.1: prop-types "^15.5.8" unescape "^0.2.0" +react-native-web@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.3.4.tgz#b73a483f3c37a37ed832903e02d9c3c96f3ea109" + dependencies: + array-find-index "^1.0.2" + create-react-class "^15.6.2" + debounce "^1.1.0" + deep-assign "^2.0.0" + fbjs "^0.8.16" + hyphenate-style-name "^1.0.2" + inline-style-prefixer "^4.0.0" + normalize-css-color "^1.0.2" + prop-types "^15.6.0" + react-art "^16.2.0" + react-timer-mixin "^0.13.3" + +react-primitives@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/react-primitives/-/react-primitives-0.5.1.tgz#caa466afd029e7d024f48b5be7c5717fe94fc703" + dependencies: + animated "^0.1.5" + asap "^2.0.5" + create-react-class "^15.6.2" + deline "^1.0.4" + flexibility "^2.0.1" + inline-style-prefixer "^4.0.2" + invariant "^2.2.1" + normalize-css-color "^1.0.1" + prop-types "^15.5.10" + react-css-property-operations "^15.4.1" + react-native-web "^0.3.2" + react-timer-mixin "^0.13.3" + string-hash "^1.1.3" + react-reconciler@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d" @@ -11759,7 +11874,11 @@ react-test-renderer@^16.3.2: prop-types "^15.6.0" react-is "^16.3.2" -react@^16.2.0, react@^16.3.2: +react-timer-mixin@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.3.tgz#0da8b9f807ec07dc3e854d082c737c65605b3d22" + +react@^16.2.0, react@^16.3.2, react@^16.4.0: version "16.3.2" resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" dependencies: @@ -13337,6 +13456,10 @@ strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" +string-hash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"