diff --git a/.npmignore b/.npmignore index 35d450777..b62fff18b 100644 --- a/.npmignore +++ b/.npmignore @@ -1,14 +1,3 @@ -.DS_Store -.prettierrc -.nyc_output -.travis.yml -coverage -coverage.lcov -bench -docs -src -examples -babel.config.js -test -CONTRIBUTING.md -CODE_OF_CONDUCT.md +/* +!/dist/**/*.js +!/props.js \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index b98087bf5..ad4605be9 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,29 +1,16 @@ -module.exports = { - env: { - test: { - presets: [ - [ '@babel/env', { loose: true } ], - '@babel/react' - ], - plugins: [ - '@babel/transform-runtime' - ] - }, - cjs: { - presets: [ - [ '@babel/env', { loose: true } ] - ], - plugins: [ - '@babel/transform-runtime' - ] - }, - esm: { - presets: [ - [ '@babel/env', { loose: true, modules: false } ] - ], - plugins: [ - ['@babel/transform-runtime', { useESModules: true }] - ] - }, +module.exports = function getConfig(api) { + const isRollup = api.caller( + caller => caller && caller.name === 'rollup-plugin-babel' + ) + if (!isRollup) + return { + presets: [['@babel/preset-env', { loose: true }], '@babel/react'], + } + return { + presets: [['@babel/preset-env', { loose: true, modules: false }]], + plugins: [ + 'babel-plugin-annotate-pure-calls', + ['@babel/transform-runtime', { useESModules: true }], + ], } } diff --git a/benchmarks/index.js b/benchmarks/index.js new file mode 100644 index 000000000..f46ec7875 --- /dev/null +++ b/benchmarks/index.js @@ -0,0 +1,40 @@ +/* eslint-disable no-console, import/no-unresolved */ +const Benchmark = require('benchmark') + +const libs = [ + { + name: 'actual', + module: require('../dist/styled-system.cjs'), + }, + { + name: 'v4', + module: require('./v4.1.0'), + }, +].map(({ name, module: { compose, fontSize, space } }) => { + const system = compose( + fontSize, + space + ) + const run = () => system({ p: [10, 20], mt: 10, m: '20px', fontSize: 10 }) + return { name, run } +}) + +const suite = new Benchmark.Suite() + +console.log('Initial run...') + +libs.forEach(lib => { + console.log(lib.name, lib.run()) + suite.add(lib.name, lib.run) +}) + +console.log('Run suite...') + +suite + .on('cycle', event => { + console.log(String(event.target)) + }) + .on('complete', function onComplete() { + console.log(`Fastest is ${this.filter('fastest').map('name')}`) + }) + .run({ async: true }) diff --git a/benchmarks/v4.1.0.js b/benchmarks/v4.1.0.js new file mode 100644 index 000000000..114f91cea --- /dev/null +++ b/benchmarks/v4.1.0.js @@ -0,0 +1,688 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +exports.__esModule = true; +exports.colorStyle = exports.textStyle = exports.buttonStyle = exports.left = exports.bottom = exports.right = exports.top = exports.zIndex = exports.position = exports.backgroundRepeat = exports.backgroundPosition = exports.backgroundSize = exports.backgroundImage = exports.background = exports.overflow = exports.opacity = exports.boxShadow = exports.borders = exports.borderRadius = exports.borderLeft = exports.borderBottom = exports.borderRight = exports.borderTop = exports.borderColor = exports.borderStyle = exports.borderWidth = exports.border = exports.gridArea = exports.gridTemplateAreas = exports.gridTemplateRows = exports.gridTemplateColumns = exports.gridAutoRows = exports.gridAutoColumns = exports.gridAutoFlow = exports.gridRow = exports.gridColumn = exports.gridRowGap = exports.gridColumnGap = exports.gridGap = exports.order = exports.alignSelf = exports.justifySelf = exports.flex = exports.flexDirection = exports.flexBasis = exports.flexWrap = exports.justifyContent = exports.justifyItems = exports.alignContent = exports.alignItems = exports.verticalAlign = exports.size = exports.minHeight = exports.maxHeight = exports.height = exports.minWidth = exports.maxWidth = exports.display = exports.letterSpacing = exports.fontStyle = exports.textAlign = exports.lineHeight = exports.fontWeight = exports.fontFamily = exports.fontSize = exports.getPx = exports.width = exports.getWidth = exports.color = exports.backgroundColor = exports.textColor = exports.space = exports.paddingRight = exports.paddingLeft = exports.paddingBottom = exports.paddingTop = exports.padding = exports.marginRight = exports.marginLeft = exports.marginBottom = exports.marginTop = exports.margin = exports.variant = exports.mapProps = exports.compose = exports.style = exports.createMediaQuery = exports.px = exports.num = exports.isObject = exports.is = exports.themeGet = exports.get = exports.cloneFunction = exports.propType = exports.defaultBreakpoints = void 0; + +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); + +var _propTypes = _interopRequireDefault(require("prop-types")); + +var defaultBreakpoints = [40, 52, 64].map(function (n) { + return n + 'em'; +}); +exports.defaultBreakpoints = defaultBreakpoints; + +var propType = _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string, _propTypes.default.array, _propTypes.default.object]); + +exports.propType = propType; + +var cloneFunction = function cloneFunction(fn) { + return function () { + return fn.apply(void 0, arguments); + }; +}; + +exports.cloneFunction = cloneFunction; + +var get = function get(obj) { + for (var _len = arguments.length, paths = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + paths[_key - 1] = arguments[_key]; + } + + var value = paths.reduce(function (a, path) { + if (is(a)) return a; + var keys = typeof path === 'string' ? path.split('.') : [path]; + return keys.reduce(function (a, key) { + return a && is(a[key]) ? a[key] : null; + }, obj); + }, null); + return is(value) ? value : paths[paths.length - 1]; +}; + +exports.get = get; + +var themeGet = function themeGet(path, fallback) { + if (fallback === void 0) { + fallback = null; + } + + return function (props) { + return get(props.theme, path, fallback); + }; +}; + +exports.themeGet = themeGet; + +var is = function is(n) { + return n !== undefined && n !== null; +}; + +exports.is = is; + +var isObject = function isObject(n) { + return typeof n === 'object' && n !== null; +}; + +exports.isObject = isObject; + +var num = function num(n) { + return typeof n === 'number' && !isNaN(n); +}; + +exports.num = num; + +var px = function px(n) { + return num(n) && n !== 0 ? n + 'px' : n; +}; + +exports.px = px; + +var createMediaQuery = function createMediaQuery(n) { + return "@media screen and (min-width: " + px(n) + ")"; +}; + +exports.createMediaQuery = createMediaQuery; + +var getValue = function getValue(n, scale) { + return get(scale, n); +}; + +var style = function style(_ref) { + var _func$propTypes; + + var prop = _ref.prop, + cssProperty = _ref.cssProperty, + alias = _ref.alias, + key = _ref.key, + _ref$transformValue = _ref.transformValue, + transformValue = _ref$transformValue === void 0 ? getValue : _ref$transformValue, + _ref$scale = _ref.scale, + defaultScale = _ref$scale === void 0 ? {} : _ref$scale; + var property = cssProperty || prop; + + var func = function func(props) { + var value = get(props, prop, alias, null); + if (!is(value)) return null; + var scale = get(props.theme, key, defaultScale); + + var createStyle = function createStyle(n) { + var _ref2; + + return is(n) ? (_ref2 = {}, _ref2[property] = transformValue(n, scale), _ref2) : null; + }; + + if (!isObject(value)) return createStyle(value); + var breakpoints = get(props.theme, 'breakpoints', defaultBreakpoints); + var styles = []; + + if (Array.isArray(value)) { + styles.push(createStyle(value[0])); + + for (var i = 1; i < value.slice(0, breakpoints.length + 1).length; i++) { + var rule = createStyle(value[i]); + + if (rule) { + var _styles$push; + + var media = createMediaQuery(breakpoints[i - 1]); + styles.push((_styles$push = {}, _styles$push[media] = rule, _styles$push)); + } + } + } else { + for (var _key2 in value) { + var breakpoint = breakpoints[_key2]; + + var _media = createMediaQuery(breakpoint); + + var _rule = createStyle(value[_key2]); + + if (!breakpoint) { + styles.unshift(_rule); + } else { + var _styles$push2; + + styles.push((_styles$push2 = {}, _styles$push2[_media] = _rule, _styles$push2)); + } + } + + styles.sort(); + } + + return styles; + }; + + func.propTypes = (_func$propTypes = {}, _func$propTypes[prop] = cloneFunction(propType), _func$propTypes); + func.propTypes[prop].meta = { + prop: prop, + themeKey: key + }; + + if (alias) { + func.propTypes[alias] = cloneFunction(propType); + func.propTypes[alias].meta = { + prop: alias, + themeKey: key + }; + } + + return func; +}; + +exports.style = style; + +var compose = function compose() { + for (var _len2 = arguments.length, funcs = new Array(_len2), _key3 = 0; _key3 < _len2; _key3++) { + funcs[_key3] = arguments[_key3]; + } + + var func = function func(props) { + var n = funcs.map(function (fn) { + return fn(props); + }).filter(Boolean); + return n; + }; + + func.propTypes = {}; + funcs.forEach(function (fn) { + func.propTypes = (0, _extends2.default)({}, func.propTypes, fn.propTypes); + }); + return func; +}; + +exports.compose = compose; + +var mapProps = function mapProps(mapper) { + return function (func) { + var next = function next(props) { + return func(mapper(props)); + }; + + for (var key in func) { + next[key] = func[key]; + } + + return next; + }; +}; + +exports.mapProps = mapProps; + +var variant = function variant(_ref3) { + var _fn$propTypes; + + var key = _ref3.key, + _ref3$prop = _ref3.prop, + prop = _ref3$prop === void 0 ? 'variant' : _ref3$prop; + + var fn = function fn(props) { + return get(props.theme, [key, props[prop]].join('.'), null); + }; + + fn.propTypes = (_fn$propTypes = {}, _fn$propTypes[prop] = _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), _fn$propTypes); + return fn; +}; // space + + +exports.variant = variant; +var spaceScale = [0, 4, 8, 16, 32, 64, 128, 256, 512]; + +var getSpace = function getSpace(n, scale) { + if (!num(n)) { + return px(get(scale, n, n)); + } + + var isNegative = n < 0; + var absolute = Math.abs(n); + var value = get(scale, absolute); + + if (!num(value)) { + return isNegative ? '-' + value : value; + } + + return px(value * (isNegative ? -1 : 1)); +}; + +var margin = style({ + prop: 'margin', + alias: 'm', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.margin = margin; +var marginTop = style({ + prop: 'marginTop', + alias: 'mt', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.marginTop = marginTop; +var marginBottom = style({ + prop: 'marginBottom', + alias: 'mb', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.marginBottom = marginBottom; +var marginLeft = style({ + prop: 'marginLeft', + alias: 'ml', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.marginLeft = marginLeft; +var marginRight = style({ + prop: 'marginRight', + alias: 'mr', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.marginRight = marginRight; +var padding = style({ + prop: 'padding', + alias: 'p', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.padding = padding; +var paddingTop = style({ + prop: 'paddingTop', + alias: 'pt', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.paddingTop = paddingTop; +var paddingBottom = style({ + prop: 'paddingBottom', + alias: 'pb', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.paddingBottom = paddingBottom; +var paddingLeft = style({ + prop: 'paddingLeft', + alias: 'pl', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.paddingLeft = paddingLeft; +var paddingRight = style({ + prop: 'paddingRight', + alias: 'pr', + key: 'space', + transformValue: getSpace, + scale: spaceScale +}); +exports.paddingRight = paddingRight; +var space = mapProps(function (props) { + return (0, _extends2.default)({}, props, { + mt: is(props.my) ? props.my : props.mt, + mb: is(props.my) ? props.my : props.mb, + ml: is(props.mx) ? props.mx : props.ml, + mr: is(props.mx) ? props.mx : props.mr, + pt: is(props.py) ? props.py : props.pt, + pb: is(props.py) ? props.py : props.pb, + pl: is(props.px) ? props.px : props.pl, + pr: is(props.px) ? props.px : props.pr + }); +})(compose(margin, marginTop, marginBottom, marginLeft, marginRight, padding, paddingTop, paddingBottom, paddingLeft, paddingRight)); // color + +exports.space = space; +var textColor = style({ + prop: 'color', + key: 'colors' +}); +exports.textColor = textColor; +var backgroundColor = style({ + prop: 'backgroundColor', + alias: 'bg', + key: 'colors' +}); +exports.backgroundColor = backgroundColor; +var color = compose(textColor, backgroundColor); // width + +exports.color = color; + +var getWidth = function getWidth(n, scale) { + return !num(n) || n > 1 ? px(n) : n * 100 + '%'; +}; + +exports.getWidth = getWidth; +var width = style({ + prop: 'width', + key: 'widths', + transformValue: getWidth +}); // typography + +exports.width = width; + +var getPx = function getPx(n, scale) { + return px(get(scale, n)); +}; + +exports.getPx = getPx; +var fontSize = style({ + prop: 'fontSize', + key: 'fontSizes', + transformValue: getPx, + scale: [12, 14, 16, 20, 24, 32, 48, 64, 72] +}); +exports.fontSize = fontSize; +var fontFamily = style({ + prop: 'fontFamily', + key: 'fonts' +}); +exports.fontFamily = fontFamily; +var fontWeight = style({ + prop: 'fontWeight', + key: 'fontWeights' +}); +exports.fontWeight = fontWeight; +var lineHeight = style({ + prop: 'lineHeight', + key: 'lineHeights' +}); +exports.lineHeight = lineHeight; +var textAlign = style({ + prop: 'textAlign' +}); +exports.textAlign = textAlign; +var fontStyle = style({ + prop: 'fontStyle' +}); +exports.fontStyle = fontStyle; +var letterSpacing = style({ + prop: 'letterSpacing', + key: 'letterSpacings', + transformValue: getPx +}); // layout + +exports.letterSpacing = letterSpacing; +var display = style({ + prop: 'display' +}); +exports.display = display; +var maxWidth = style({ + prop: 'maxWidth', + key: 'maxWidths', + transformValue: getPx +}); +exports.maxWidth = maxWidth; +var minWidth = style({ + prop: 'minWidth', + key: 'minWidths', + transformValue: getPx +}); +exports.minWidth = minWidth; +var height = style({ + prop: 'height', + key: 'heights', + transformValue: getPx +}); +exports.height = height; +var maxHeight = style({ + prop: 'maxHeight', + key: 'maxHeights', + transformValue: getPx +}); +exports.maxHeight = maxHeight; +var minHeight = style({ + prop: 'minHeight', + key: 'minHeights', + transformValue: getPx +}); +exports.minHeight = minHeight; +var size = mapProps(function (props) { + return (0, _extends2.default)({}, props, { + width: props.size, + height: props.size + }); +})(compose(width, height)); +exports.size = size; +var verticalAlign = style({ + prop: 'verticalAlign' +}); // flexbox + +exports.verticalAlign = verticalAlign; +var alignItems = style({ + prop: 'alignItems' +}); +exports.alignItems = alignItems; +var alignContent = style({ + prop: 'alignContent' +}); +exports.alignContent = alignContent; +var justifyItems = style({ + prop: 'justifyItems' +}); +exports.justifyItems = justifyItems; +var justifyContent = style({ + prop: 'justifyContent' +}); +exports.justifyContent = justifyContent; +var flexWrap = style({ + prop: 'flexWrap' +}); +exports.flexWrap = flexWrap; +var flexBasis = style({ + prop: 'flexBasis', + transformValue: getWidth +}); +exports.flexBasis = flexBasis; +var flexDirection = style({ + prop: 'flexDirection' +}); +exports.flexDirection = flexDirection; +var flex = style({ + prop: 'flex' +}); +exports.flex = flex; +var justifySelf = style({ + prop: 'justifySelf' +}); +exports.justifySelf = justifySelf; +var alignSelf = style({ + prop: 'alignSelf' +}); +exports.alignSelf = alignSelf; +var order = style({ + prop: 'order' +}); // grid + +exports.order = order; +var gridGap = style({ + prop: 'gridGap', + key: 'space', + transformValue: getPx, + scale: spaceScale +}); +exports.gridGap = gridGap; +var gridColumnGap = style({ + prop: 'gridColumnGap', + key: 'space', + transformValue: getPx, + scale: spaceScale +}); +exports.gridColumnGap = gridColumnGap; +var gridRowGap = style({ + prop: 'gridRowGap', + key: 'space', + transformValue: getPx, + scale: spaceScale +}); +exports.gridRowGap = gridRowGap; +var gridColumn = style({ + prop: 'gridColumn' +}); +exports.gridColumn = gridColumn; +var gridRow = style({ + prop: 'gridRow' +}); +exports.gridRow = gridRow; +var gridAutoFlow = style({ + prop: 'gridAutoFlow' +}); +exports.gridAutoFlow = gridAutoFlow; +var gridAutoColumns = style({ + prop: 'gridAutoColumns' +}); +exports.gridAutoColumns = gridAutoColumns; +var gridAutoRows = style({ + prop: 'gridAutoRows' +}); +exports.gridAutoRows = gridAutoRows; +var gridTemplateColumns = style({ + prop: 'gridTemplateColumns' +}); +exports.gridTemplateColumns = gridTemplateColumns; +var gridTemplateRows = style({ + prop: 'gridTemplateRows' +}); +exports.gridTemplateRows = gridTemplateRows; +var gridTemplateAreas = style({ + prop: 'gridTemplateAreas' +}); +exports.gridTemplateAreas = gridTemplateAreas; +var gridArea = style({ + prop: 'gridArea' +}); // borders + +exports.gridArea = gridArea; +var border = style({ + prop: 'border', + key: 'borders' +}); +exports.border = border; +var borderWidth = style({ + prop: 'borderWidth', + key: 'borderWidths', + transformValue: getPx +}); +exports.borderWidth = borderWidth; +var borderStyle = style({ + prop: 'borderStyle', + key: 'borderStyles' +}); +exports.borderStyle = borderStyle; +var borderColor = style({ + prop: 'borderColor', + key: 'colors' +}); +exports.borderColor = borderColor; +var borderTop = style({ + prop: 'borderTop', + key: 'borders' +}); +exports.borderTop = borderTop; +var borderRight = style({ + prop: 'borderRight', + key: 'borders' +}); +exports.borderRight = borderRight; +var borderBottom = style({ + prop: 'borderBottom', + key: 'borders' +}); +exports.borderBottom = borderBottom; +var borderLeft = style({ + prop: 'borderLeft', + key: 'borders' +}); +exports.borderLeft = borderLeft; +var borderRadius = style({ + prop: 'borderRadius', + key: 'radii', + transformValue: getPx +}); +exports.borderRadius = borderRadius; +var borders = compose(border, borderTop, borderRight, borderBottom, borderLeft, borderWidth, borderStyle, borderColor, borderRadius); +exports.borders = borders; +var boxShadow = style({ + prop: 'boxShadow', + key: 'shadows' +}); +exports.boxShadow = boxShadow; +var opacity = style({ + prop: 'opacity' +}); +exports.opacity = opacity; +var overflow = style({ + prop: 'overflow' +}); // backgrounds + +exports.overflow = overflow; +var background = style({ + prop: 'background' +}); +exports.background = background; +var backgroundImage = style({ + prop: 'backgroundImage' +}); +exports.backgroundImage = backgroundImage; +var backgroundSize = style({ + prop: 'backgroundSize' +}); +exports.backgroundSize = backgroundSize; +var backgroundPosition = style({ + prop: 'backgroundPosition' +}); +exports.backgroundPosition = backgroundPosition; +var backgroundRepeat = style({ + prop: 'backgroundRepeat' +}); // position + +exports.backgroundRepeat = backgroundRepeat; +var position = style({ + prop: 'position' +}); +exports.position = position; +var zIndex = style({ + prop: 'zIndex', + key: 'zIndices' +}); +exports.zIndex = zIndex; +var top = style({ + prop: 'top', + transformValue: getPx +}); +exports.top = top; +var right = style({ + prop: 'right', + transformValue: getPx +}); +exports.right = right; +var bottom = style({ + prop: 'bottom', + transformValue: getPx +}); +exports.bottom = bottom; +var left = style({ + prop: 'left', + transformValue: getPx +}); // variants + +exports.left = left; +var buttonStyle = variant({ + key: 'buttons' +}); +exports.buttonStyle = buttonStyle; +var textStyle = variant({ + key: 'textStyles', + prop: 'textStyle' +}); +exports.textStyle = textStyle; +var colorStyle = variant({ + key: 'colorStyles', + prop: 'colors' +}); +exports.colorStyle = colorStyle; diff --git a/package-lock.json b/package-lock.json index 997d964ea..33c71718f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1565,6 +1565,33 @@ "any-observable": "^0.3.0" } }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/node": { + "version": "11.13.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.8.tgz", + "integrity": "sha512-szA3x/3miL90ZJxUCzx9haNbK5/zmPieGraZEe4WI+3srN0eGLiT22NXeMHmyhNEopn+IrxqMc7wdVwvPl8meg==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -1977,6 +2004,12 @@ } } }, + "babel-plugin-annotate-pure-calls": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-annotate-pure-calls/-/babel-plugin-annotate-pure-calls-0.4.0.tgz", + "integrity": "sha512-oi4M/PWUJOU9ZyRGoPTfPMqdyMp06jbJAomd3RcyYuzUtBOddv98BqLm96Lucpi2QFoQHkdGQt0ACvw7VzVEQA==", + "dev": true + }, "babel-plugin-emotion": { "version": "10.0.9", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.9.tgz", @@ -2099,6 +2132,16 @@ } } }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, "binary-extensions": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", @@ -2715,6 +2758,11 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "deepmerge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz", + "integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow==" + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -2935,6 +2983,12 @@ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", "dev": true }, + "estree-walker": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz", + "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==", + "dev": true + }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -3224,8 +3278,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3246,14 +3299,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3268,20 +3319,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3398,8 +3446,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3411,7 +3458,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3426,7 +3472,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3434,14 +3479,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3460,7 +3503,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3541,8 +3583,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3554,7 +3595,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3640,8 +3680,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3677,7 +3716,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3697,7 +3735,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3741,14 +3778,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -4383,6 +4418,12 @@ "is-path-inside": "^1.0.0" } }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -4550,6 +4591,27 @@ "semver": "^5.5.0" } }, + "jest-worker": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", + "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "dev": true, + "requires": { + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -5048,6 +5110,15 @@ "yallist": "^2.1.2" } }, + "magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", @@ -5125,6 +5196,15 @@ "yargs-parser": "^10.0.0" } }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -6808,6 +6888,12 @@ } } }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true + }, "please-upgrade-node": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", @@ -7213,6 +7299,91 @@ "glob": "^7.1.3" } }, + "rollup": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.10.1.tgz", + "integrity": "sha512-pW353tmBE7QP622ITkGxtqF0d5gSRCVPD9xqM+fcPjudeZfoXMFW2sCzsTe2TU/zU1xamIjiS9xuFCPVT9fESw==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "^11.13.5", + "acorn": "^6.1.1" + } + }, + "rollup-plugin-babel": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.2.tgz", + "integrity": "sha512-KfnizE258L/4enADKX61ozfwGHoqYauvoofghFJBhFnpH9Sb9dNPpWg8QHOaAfVASUYV8w0mCx430i9z0LJoJg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.3.0" + } + }, + "rollup-plugin-commonjs": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.3.4.tgz", + "integrity": "sha512-DTZOvRoiVIHHLFBCL4pFxOaJt8pagxsVldEXBOn6wl3/V21wVaj17HFfyzTsQUuou3sZL3lEJZVWKPFblJfI6w==", + "dev": true, + "requires": { + "estree-walker": "^0.6.0", + "magic-string": "^0.25.2", + "resolve": "^1.10.0", + "rollup-pluginutils": "^2.6.0" + } + }, + "rollup-plugin-node-resolve": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.2.3.tgz", + "integrity": "sha512-r+WaesPzdGEynpLZLALFEDugA4ACa5zn7bc/+LVX4vAXQQ8IgDHv0xfsSvJ8tDXUtprfBtrDtRFg27ifKjcJTg==", + "dev": true, + "requires": { + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.10.0" + }, + "dependencies": { + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + } + } + }, + "rollup-plugin-replace": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz", + "integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==", + "dev": true, + "requires": { + "magic-string": "^0.25.2", + "rollup-pluginutils": "^2.6.0" + } + }, + "rollup-plugin-uglify": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.2.tgz", + "integrity": "sha512-qwz2Tryspn5QGtPUowq5oumKSxANKdrnfz7C0jm4lKxvRDsNe/hSGsB9FntUul7UeC4TsZEWKErVgE1qWSO0gw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "jest-worker": "^24.0.0", + "serialize-javascript": "^1.6.1", + "uglify-js": "^3.4.9" + } + }, + "rollup-pluginutils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.6.0.tgz", + "integrity": "sha512-aGQwspEF8oPKvg37u3p7h0cYNwmJR1sCBMZGZ5b9qy8HGtETknqjzcxrDRrcAnJNXN18lBH4Q9vZYth/p4n8jQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.0", + "micromatch": "^3.1.10" + } + }, "run-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", @@ -7280,6 +7451,12 @@ "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", "dev": true }, + "serialize-javascript": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", + "dev": true + }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -7521,6 +7698,12 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", + "dev": true + }, "spdx-correct": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", @@ -7843,6 +8026,30 @@ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true }, + "uglify-js": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.9.tgz", + "integrity": "sha512-WpT0RqsDtAWPNJK955DEnb6xjymR8Fn0OlK4TT4pS0ASYsVPqr5ELhgwOwLCP5J5vHeJ4xmMmz3DEgdqC10JeQ==", + "dev": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "uid2": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", diff --git a/package.json b/package.json index df79a6e74..cb2c855b4 100644 --- a/package.json +++ b/package.json @@ -2,22 +2,22 @@ "name": "styled-system", "version": "4.1.1", "description": "Responsive, theme-based style props for building design systems with React", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", + "main": "dist/styled-system.cjs.js", + "module": "dist/styled-system.es.js", + "jsnext:main": "dist/styled-system.es.js", "sideEffects": false, "scripts": { - "prepare": "mkdirp dist && npm run build:cjs && npm run build:esm", + "prepare": "rollup -c", "prettier": "prettier \"{src,test}/**/*.js\" --write", "clean": "rm -rf dist", - "build:cjs": "NODE_ENV=cjs babel src -o dist/index.cjs.js", - "build:esm": "NODE_ENV=esm babel src -o dist/index.esm.js", "logo": "npx repng docs/src/logo/index.js -d docs/static -f logo.png -w 512 -h 512", "logo-svg": "npx scrs docs/src/logo/index.js --svg > docs/static/logo.svg", "logo-white": "npx scrs docs/src/logo/white.js --svg > docs/static/logo-white.svg", "size": "npx bundlesize", "cover": "nyc report --reporter=html --reporter=lcov > coverage.lcov", "codecov": "nyc report --reporter=html --reporter=lcov > coverage.lcov && npx codecov", - "test": "nyc ava" + "test": "nyc ava", + "bench": "node benchmarks" }, "keywords": [ "react", @@ -37,12 +37,20 @@ "@emotion/core": "^10.0.9", "@emotion/styled": "^10.0.9", "ava": "^1.3.1", + "babel-plugin-annotate-pure-calls": "^0.4.0", + "benchmark": "^2.1.4", "husky": "^1.3.1", "lint-staged": "^8.1.5", "nyc": "^13.3.0", "prettier": "^1.16.4", "react": "^16.8.5", "react-test-renderer": "^16.8.5", + "rollup": "^1.10.1", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^9.3.4", + "rollup-plugin-node-resolve": "^4.2.3", + "rollup-plugin-replace": "^2.2.0", + "rollup-plugin-uglify": "^6.0.2", "styled-components": "^4.2.0" }, "ava": { @@ -71,6 +79,7 @@ }, "dependencies": { "@babel/runtime": "^7.4.2", + "deepmerge": "^3.2.0", "prop-types": "^15.7.2" }, "husky": { diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 000000000..29dbe11ec --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,90 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import path from 'path' +import resolve from 'rollup-plugin-node-resolve' +import babel from 'rollup-plugin-babel' +import replace from 'rollup-plugin-replace' +import commonjs from 'rollup-plugin-commonjs' +import { uglify } from 'rollup-plugin-uglify' +import pkg from './package.json' + +function getConfig() { + const name = 'styledSystem' + const buildName = 'styled-system' + + const SOURCE_DIR = path.resolve(__dirname, 'src') + const DIST_DIR = path.resolve(__dirname, 'dist') + + const baseConfig = { + input: `${SOURCE_DIR}/index.js`, + plugins: [ + babel({ + runtimeHelpers: true, + exclude: 'node_modules/**', + configFile: path.join(__dirname, 'babel.config.js'), + }), + ], + } + + const esConfig = Object.assign({}, baseConfig, { + output: { + file: `${DIST_DIR}/${buildName}.es.js`, + format: 'es', + }, + external: [ + ...Object.keys(pkg.peerDependencies || {}), + ...Object.keys(pkg.dependencies || {}), + ], + plugins: [...baseConfig.plugins, resolve()], + }) + + const cjsConfig = Object.assign({}, esConfig, { + output: { + file: `${DIST_DIR}/${buildName}.cjs.js`, + format: 'cjs', + }, + }) + + const globals = { + deepmerge: 'deepmerge', + } + + const umdConfig = Object.assign({}, baseConfig, { + output: { + name, + file: `${DIST_DIR}/${buildName}.js`, + format: 'umd', + globals, + exports: 'named', + sourcemap: false, + }, + external: Object.keys(globals), + plugins: [...baseConfig.plugins, resolve({ browser: true }), commonjs()], + }) + + const minConfig = Object.assign({}, umdConfig, { + output: { + ...umdConfig.output, + file: `${DIST_DIR}/${buildName}.min.js`, + }, + plugins: [ + ...umdConfig.plugins, + replace({ 'process.env.NODE_ENV': JSON.stringify('production') }), + uglify({ + compress: { + pure_getters: true, + unsafe: true, + unsafe_comps: true, + warnings: false, + }, + }), + ], + }) + + if (process.env.WATCH_MODE) { + return [esConfig, cjsConfig] + } + + return [esConfig, cjsConfig, umdConfig, minConfig] +} + +export default getConfig() diff --git a/src/index.js b/src/index.js index c5435234f..876d7b920 100644 --- a/src/index.js +++ b/src/index.js @@ -1,508 +1,5 @@ -import PropTypes from 'prop-types' +export * from './style' +export * from './styles/index' -export const defaultBreakpoints = [40, 52, 64].map(n => n + 'em') - -export const propType = PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - PropTypes.array, - PropTypes.object, -]) - -export const cloneFunction = fn => (...args) => fn(...args) - -export const get = (obj, ...paths) => { - const value = paths.reduce((a, path) => { - if (is(a)) return a - const keys = typeof path === 'string' ? path.split('.') : [path] - return keys.reduce((a, key) => (a && is(a[key]) ? a[key] : null), obj) - }, null) - return is(value) ? value : paths[paths.length - 1] -} - -export const themeGet = (path, fallback = null) => props => - get(props.theme, path, fallback) - -export const is = n => n !== undefined && n !== null - -export const isObject = n => typeof n === 'object' && n !== null - -export const num = n => typeof n === 'number' && !isNaN(n) - -export const px = n => (num(n) && n !== 0 ? n + 'px' : n) - -export const createMediaQuery = n => `@media screen and (min-width: ${px(n)})` - -const getValue = (n, scale) => get(scale, n) - -export const style = ({ - prop, - cssProperty, - alias, - key, - transformValue = getValue, - scale: defaultScale = {}, -}) => { - const property = cssProperty || prop - const func = props => { - const value = get(props, prop, alias, null) - if (!is(value)) return null - const scale = get(props.theme, key, defaultScale) - const createStyle = n => - is(n) - ? { - [property]: transformValue(n, scale), - } - : null - - if (!isObject(value)) return createStyle(value) - - const breakpoints = get(props.theme, 'breakpoints', defaultBreakpoints) - - const styles = [] - if (Array.isArray(value)) { - styles.push(createStyle(value[0])) - for (let i = 1; i < value.slice(0, breakpoints.length + 1).length; i++) { - const rule = createStyle(value[i]) - if (rule) { - const media = createMediaQuery(breakpoints[i - 1]) - styles.push({ [media]: rule }) - } - } - } else { - for (let key in value) { - const breakpoint = breakpoints[key] - const media = createMediaQuery(breakpoint) - const rule = createStyle(value[key]) - if (!breakpoint) { - styles.unshift(rule) - } else { - styles.push({ [media]: rule }) - } - } - styles.sort() - } - - return styles - } - - func.propTypes = { - [prop]: cloneFunction(propType), - } - func.propTypes[prop].meta = { - prop, - themeKey: key, - } - - if (alias) { - func.propTypes[alias] = cloneFunction(propType) - func.propTypes[alias].meta = { - prop: alias, - themeKey: key, - } - } - - return func -} - -export const compose = (...funcs) => { - const func = props => { - const n = funcs.map(fn => fn(props)).filter(Boolean) - return n - } - - func.propTypes = {} - funcs.forEach(fn => { - func.propTypes = { - ...func.propTypes, - ...fn.propTypes, - } - }) - - return func -} - -export const mapProps = mapper => func => { - const next = props => func(mapper(props)) - for (const key in func) { - next[key] = func[key] - } - return next -} - -export const variant = ({ key, prop = 'variant' }) => { - const fn = props => get(props.theme, [key, props[prop]].join('.'), null) - fn.propTypes = { - [prop]: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - } - return fn -} - -// space -const spaceScale = [0, 4, 8, 16, 32, 64, 128, 256, 512] - -const getSpace = (n, scale) => { - if (!num(n)) { - return px(get(scale, n, n)) - } - - const isNegative = n < 0 - const absolute = Math.abs(n) - const value = get(scale, absolute) - if (!num(value)) { - return isNegative ? '-' + value : value - } - return px(value * (isNegative ? -1 : 1)) -} - -export const margin = style({ - prop: 'margin', - alias: 'm', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const marginTop = style({ - prop: 'marginTop', - alias: 'mt', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const marginBottom = style({ - prop: 'marginBottom', - alias: 'mb', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const marginLeft = style({ - prop: 'marginLeft', - alias: 'ml', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const marginRight = style({ - prop: 'marginRight', - alias: 'mr', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const padding = style({ - prop: 'padding', - alias: 'p', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const paddingTop = style({ - prop: 'paddingTop', - alias: 'pt', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const paddingBottom = style({ - prop: 'paddingBottom', - alias: 'pb', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const paddingLeft = style({ - prop: 'paddingLeft', - alias: 'pl', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const paddingRight = style({ - prop: 'paddingRight', - alias: 'pr', - key: 'space', - transformValue: getSpace, - scale: spaceScale, -}) - -export const space = mapProps(props => ({ - ...props, - mt: is(props.my) ? props.my : props.mt, - mb: is(props.my) ? props.my : props.mb, - ml: is(props.mx) ? props.mx : props.ml, - mr: is(props.mx) ? props.mx : props.mr, - pt: is(props.py) ? props.py : props.pt, - pb: is(props.py) ? props.py : props.pb, - pl: is(props.px) ? props.px : props.pl, - pr: is(props.px) ? props.px : props.pr, -}))( - compose( - margin, - marginTop, - marginBottom, - marginLeft, - marginRight, - padding, - paddingTop, - paddingBottom, - paddingLeft, - paddingRight - ) -) - -// color -export const textColor = style({ - prop: 'color', - key: 'colors', -}) - -export const backgroundColor = style({ - prop: 'backgroundColor', - alias: 'bg', - key: 'colors', -}) - -export const color = compose( - textColor, - backgroundColor -) - -// width -export const getWidth = (n, scale) => (!num(n) || n > 1 ? px(n) : n * 100 + '%') - -export const width = style({ - prop: 'width', - key: 'widths', - transformValue: getWidth, -}) - -// typography - -export const getPx = (n, scale) => px(get(scale, n)) - -export const fontSize = style({ - prop: 'fontSize', - key: 'fontSizes', - transformValue: getPx, - scale: [12, 14, 16, 20, 24, 32, 48, 64, 72], -}) - -export const fontFamily = style({ - prop: 'fontFamily', - key: 'fonts', -}) - -export const fontWeight = style({ - prop: 'fontWeight', - key: 'fontWeights', -}) - -export const lineHeight = style({ - prop: 'lineHeight', - key: 'lineHeights', -}) - -export const textAlign = style({ - prop: 'textAlign', -}) - -export const fontStyle = style({ - prop: 'fontStyle', -}) - -export const letterSpacing = style({ - prop: 'letterSpacing', - key: 'letterSpacings', - transformValue: getPx, -}) - -// layout -export const display = style({ - prop: 'display', -}) - -export const maxWidth = style({ - prop: 'maxWidth', - key: 'maxWidths', - transformValue: getPx, -}) - -export const minWidth = style({ - prop: 'minWidth', - key: 'minWidths', - transformValue: getPx, -}) - -export const height = style({ - prop: 'height', - key: 'heights', - transformValue: getPx, -}) - -export const maxHeight = style({ - prop: 'maxHeight', - key: 'maxHeights', - transformValue: getPx, -}) - -export const minHeight = style({ - prop: 'minHeight', - key: 'minHeights', - transformValue: getPx, -}) - -export const size = mapProps(props => ({ - ...props, - width: props.size, - height: props.size, -}))( - compose( - width, - height - ) -) - -export const verticalAlign = style({ prop: 'verticalAlign' }) - -// flexbox -export const alignItems = style({ prop: 'alignItems' }) -export const alignContent = style({ prop: 'alignContent' }) -export const justifyItems = style({ prop: 'justifyItems' }) -export const justifyContent = style({ prop: 'justifyContent' }) -export const flexWrap = style({ prop: 'flexWrap' }) -export const flexBasis = style({ prop: 'flexBasis', transformValue: getWidth }) -export const flexDirection = style({ prop: 'flexDirection' }) -export const flex = style({ prop: 'flex' }) -export const justifySelf = style({ prop: 'justifySelf' }) -export const alignSelf = style({ prop: 'alignSelf' }) -export const order = style({ prop: 'order' }) - -// grid -export const gridGap = style({ - prop: 'gridGap', - key: 'space', - transformValue: getPx, - scale: spaceScale, -}) - -export const gridColumnGap = style({ - prop: 'gridColumnGap', - key: 'space', - transformValue: getPx, - scale: spaceScale, -}) - -export const gridRowGap = style({ - prop: 'gridRowGap', - key: 'space', - transformValue: getPx, - scale: spaceScale, -}) - -export const gridColumn = style({ prop: 'gridColumn' }) -export const gridRow = style({ prop: 'gridRow' }) -export const gridAutoFlow = style({ prop: 'gridAutoFlow' }) -export const gridAutoColumns = style({ prop: 'gridAutoColumns' }) -export const gridAutoRows = style({ prop: 'gridAutoRows' }) -export const gridTemplateColumns = style({ prop: 'gridTemplateColumns' }) -export const gridTemplateRows = style({ prop: 'gridTemplateRows' }) -export const gridTemplateAreas = style({ prop: 'gridTemplateAreas' }) -export const gridArea = style({ prop: 'gridArea' }) - -// borders -export const border = style({ - prop: 'border', - key: 'borders', -}) - -export const borderWidth = style({ - prop: 'borderWidth', - key: 'borderWidths', - transformValue: getPx, -}) - -export const borderStyle = style({ - prop: 'borderStyle', - key: 'borderStyles', -}) - -export const borderColor = style({ - prop: 'borderColor', - key: 'colors', -}) - -export const borderTop = style({ - prop: 'borderTop', - key: 'borders', -}) - -export const borderRight = style({ - prop: 'borderRight', - key: 'borders', -}) - -export const borderBottom = style({ - prop: 'borderBottom', - key: 'borders', -}) - -export const borderLeft = style({ - prop: 'borderLeft', - key: 'borders', -}) - -export const borderRadius = style({ - prop: 'borderRadius', - key: 'radii', - transformValue: getPx, -}) - -export const borders = compose( - border, - borderTop, - borderRight, - borderBottom, - borderLeft, - borderWidth, - borderStyle, - borderColor, - borderRadius -) - -export const boxShadow = style({ - prop: 'boxShadow', - key: 'shadows', -}) - -export const opacity = style({ prop: 'opacity' }) -export const overflow = style({ prop: 'overflow' }) - -// backgrounds -export const background = style({ prop: 'background' }) -export const backgroundImage = style({ prop: 'backgroundImage' }) -export const backgroundSize = style({ prop: 'backgroundSize' }) -export const backgroundPosition = style({ prop: 'backgroundPosition' }) -export const backgroundRepeat = style({ prop: 'backgroundRepeat' }) - -// position -export const position = style({ prop: 'position' }) -export const zIndex = style({ prop: 'zIndex', key: 'zIndices' }) -export const top = style({ prop: 'top', transformValue: getPx }) -export const right = style({ prop: 'right', transformValue: getPx }) -export const bottom = style({ prop: 'bottom', transformValue: getPx }) -export const left = style({ prop: 'left', transformValue: getPx }) - -// variants -export const buttonStyle = variant({ key: 'buttons' }) -export const textStyle = variant({ key: 'textStyles', prop: 'textStyle' }) -export const colorStyle = variant({ key: 'colorStyles', prop: 'colors' }) +export * from './variant' +export * from './variants' diff --git a/src/media.js b/src/media.js new file mode 100644 index 000000000..13bec68b2 --- /dev/null +++ b/src/media.js @@ -0,0 +1,8 @@ +export const minBreakpoint = breakpoint => + breakpoint !== 0 ? breakpoint : null + +export const minWidth = value => `@media screen and (min-width: ${value})` + +export const DEFAULT_BREAKPOINTS = [0, 40, 52, 64].map(n => + n > 0 ? n + 'em' : n +) diff --git a/src/style.js b/src/style.js new file mode 100644 index 000000000..d5bbd6ce6 --- /dev/null +++ b/src/style.js @@ -0,0 +1,195 @@ +import { minBreakpoint, minWidth, DEFAULT_BREAKPOINTS } from './media' +import { is, num, string, obj, merge, get, func } from './util' + +function callOrReturn(fn, arg) { + if (!func(fn)) return fn + const next = fn(arg) + return callOrReturn(next, arg) +} + +function getThemeValue(theme, path, initial) { + if (!theme) return undefined + return callOrReturn(get(initial || theme, path), { theme }) +} + +function getValue(value, variants, theme) { + if (is(variants)) { + const valueFromVariants = getThemeValue(theme, value, variants) + if (is(valueFromVariants)) { + return valueFromVariants + } + } + return value +} + +function styleFromValue(cssProperties, value, theme, key, transformValue) { + const variants = getThemeValue(theme, key) + const computedValue = getValue(value, variants, theme) + if (string(computedValue) || num(computedValue)) { + const style = {} + for (let i = 0; i < cssProperties.length; i++) { + style[cssProperties[i]] = transformValue + ? transformValue(computedValue, { + rawValue: value, + variants, + }) + : computedValue + } + return style + } + return null +} + +function getBreakpoints(theme) { + const themeBreakpoints = getThemeValue(theme, 'breakpoints') + if (is(themeBreakpoints)) { + return themeBreakpoints + } + return DEFAULT_BREAKPOINTS +} + +function createStyleGenerator(getStyle, props, generators) { + const getStyles = props => { + const theme = props.theme || null + return getStyle(props, theme) + } + + getStyles.meta = { + props, + getStyle, + generators, + } + + return getStyles +} + +function styleFromBreakPoint(cssProperties, value, theme, key, transformValue) { + const breakpoints = getBreakpoints(theme) + const keys = Object.keys(value) + let allStyle = {} + for (let i = 0; i < keys.length; i++) { + const breakpoint = keys[i] + const style = styleFromValue( + cssProperties, + value[breakpoint], + theme, + key, + transformValue + ) + + if (style !== null) { + const breakpointValue = minBreakpoint(breakpoints[breakpoint]) + + if (breakpointValue === null) { + allStyle = merge(allStyle, style) + } else if (breakpointValue) { + allStyle = merge(allStyle, { + [minWidth(breakpointValue)]: style, + }) + } + } + } + return allStyle +} + +function getStyleFactory(prop, cssProperties, key, transformValue) { + return function getStyle(attrs, theme) { + const value = attrs[prop] + if (!is(value)) return null + + const style = styleFromValue( + cssProperties, + value, + theme, + key, + transformValue + ) + + if (style !== null) { + return style + } + + if (obj(value)) { + return styleFromBreakPoint( + cssProperties, + value, + theme, + key, + transformValue + ) + } + + return null + } +} + +export function style({ + prop, + cssProperties, + cssProperty, + alias, + key = null, + transformValue = null, +}) { + cssProperties = cssProperties || (cssProperty ? [cssProperty] : [prop]) + const getStyle = getStyleFactory(prop, cssProperties, key, transformValue) + const generator = createStyleGenerator(getStyle, [prop]) + + if (!alias) { + return generator + } + + return compose( + generator, + style({ prop: alias, cssProperties, key, transformValue }) + ) +} + +function indexGeneratorsByProp(styles) { + const index = {} + for (let i = 0; i < styles.length; i++) { + const style = styles[i] + if (style && style.meta) { + const propsKeys = Object.keys(style.meta.props) + for (let j = 0; j < propsKeys.length; j++) { + const prop = style.meta.props[propsKeys[j]] + index[prop] = style + } + } + } + return index +} + +export function compose(...generators) { + let flatGenerators = [] + generators.forEach(gen => { + if (gen.meta.generators) { + flatGenerators = [...flatGenerators, ...gen.meta.generators] + } else { + flatGenerators.push(gen) + } + }) + + const generatorsByProp = indexGeneratorsByProp(flatGenerators) + + function getStyle(attrs, theme) { + const propKeys = Object.keys(attrs) + const propCount = propKeys.length + let allStyle = {} + for (let i = 0; i < propCount; i++) { + const propKey = propKeys[i] + const generator = generatorsByProp[propKey] + if (generator) { + allStyle = merge(allStyle, generator.meta.getStyle(attrs, theme)) + } + } + return allStyle + } + + const props = flatGenerators.reduce( + (keys, generator) => [...keys, ...generator.meta.props], + [] + ) + + return createStyleGenerator(getStyle, props, generators) +} diff --git a/src/styles/backgrounds.js b/src/styles/backgrounds.js new file mode 100644 index 000000000..940b5a541 --- /dev/null +++ b/src/styles/backgrounds.js @@ -0,0 +1,36 @@ +import { style, compose } from '../style' + +export const background = style({ + prop: 'background', +}) + +export const backgroundColor = style({ + prop: 'backgroundColor', + alias: 'bg', + key: 'colors', +}) + +export const backgroundImage = style({ + prop: 'backgroundImage', +}) + +export const backgroundSize = style({ + prop: 'backgroundSize', +}) + +export const backgroundPosition = style({ + prop: 'backgroundPosition', +}) + +export const backgroundRepeat = style({ + prop: 'backgroundRepeat', +}) + +export const backgrounds = compose( + background, + backgroundColor, + backgroundImage, + backgroundSize, + backgroundPosition, + backgroundRepeat +) diff --git a/src/styles/basics.js b/src/styles/basics.js new file mode 100644 index 000000000..07d2f125c --- /dev/null +++ b/src/styles/basics.js @@ -0,0 +1,14 @@ +import { style, compose } from '../style' + +export const opacity = style({ + prop: 'opacity', +}) + +export const overflow = style({ + prop: 'overflow', +}) + +export const basics = compose( + opacity, + overflow, +) diff --git a/src/styles/borders.js b/src/styles/borders.js new file mode 100644 index 000000000..660aced02 --- /dev/null +++ b/src/styles/borders.js @@ -0,0 +1,99 @@ +import { style, compose } from '../style' +import { num } from '../util' +import { px } from '../unit' + +const getBorder = n => (num(n) && n > 0 ? `${n}px solid` : n) + +export const border = style({ + prop: 'border', + key: 'borders', + transformValue: getBorder, +}) + +export const borderWidth = style({ + prop: 'borderWidth', + key: 'borderWidths', + transformValue: px, +}) + +export const borderStyle = style({ + prop: 'borderStyle', + key: 'borderStyles', +}) + +export const borderColor = style({ + prop: 'borderColor', + key: 'colors', +}) + +export const borderRadius = style({ + prop: 'borderRadius', + key: 'radii', + transformValue: px, +}) + +export const borderTop = style({ + prop: 'borderTop', + key: 'borders', + transformValue: getBorder, +}) + +export const borderTopColor = style({ + prop: 'borderTopColor', + key: 'colors', +}) + +export const borderRight = style({ + prop: 'borderRight', + key: 'borders', + transformValue: getBorder, +}) + +export const borderRightColor = style({ + prop: 'borderRightColor', + key: 'colors', +}) + +export const borderBottom = style({ + prop: 'borderBottom', + key: 'borders', + transformValue: getBorder, +}) + +export const borderBottomColor = style({ + prop: 'borderBottomColor', + key: 'colors', +}) + +export const borderLeft = style({ + prop: 'borderLeft', + key: 'borders', + transformValue: getBorder, +}) + +export const borderLeftColor = style({ + prop: 'borderLeftColor', + key: 'colors', +}) + +export const boxShadow = style({ + prop: 'boxShadow', + key: 'shadows', +}) + +export const borders = compose( + border, + borderWidth, + borderStyle, + borderColor, + borderTop, + borderTopColor, + borderRight, + borderRightColor, + borderBottom, + borderBottomColor, + borderLeft, + borderLeftColor, + borderRadius, + boxShadow +) diff --git a/src/styles/colors.js b/src/styles/colors.js new file mode 100644 index 000000000..72ada39bd --- /dev/null +++ b/src/styles/colors.js @@ -0,0 +1,9 @@ +import { compose } from '../style' + +import { backgroundColor } from './backgrounds' +import { textColor } from './typography' + +export const color = compose( + backgroundColor, + textColor +) diff --git a/src/styles/dimensions.js b/src/styles/dimensions.js new file mode 100644 index 000000000..0ee39d7be --- /dev/null +++ b/src/styles/dimensions.js @@ -0,0 +1,54 @@ +import { style, compose } from '../style' +import { percent } from '../unit' + +export const width = style({ + prop: 'width', + transformValue: percent, + key: 'widths', +}) + +export const height = style({ + prop: 'height', + transformValue: percent, + key: 'heights', +}) + +export const size = style({ + prop: 'size', + cssProperties: ['width', 'height'], + transformValue: percent, + key: 'sizes', +}) + +export const maxWidth = style({ + prop: 'maxWidth', + transformValue: percent, + key: 'widths', +}) + +export const maxHeight = style({ + prop: 'maxHeight', + transformValue: percent, + key: 'heights', +}) + +export const minWidth = style({ + prop: 'minWidth', + transformValue: percent, + key: 'widths', +}) + +export const minHeight = style({ + prop: 'minHeight', + transformValue: percent, + key: 'heights', +}) + +export const dimensions = compose( + width, + height, + maxWidth, + maxHeight, + minWidth, + minHeight +) diff --git a/src/styles/flexboxes.js b/src/styles/flexboxes.js new file mode 100644 index 000000000..16f0f622b --- /dev/null +++ b/src/styles/flexboxes.js @@ -0,0 +1,65 @@ +import { style, compose } from '../style' +import { percent } from '../unit' + +export const display = style({ + prop: 'display', +}) + +export const alignItems = style({ + prop: 'alignItems', +}) + +export const alignContent = style({ + prop: 'alignContent', +}) + +export const justifyItems = style({ + prop: 'justifyItems', +}) + +export const justifyContent = style({ + prop: 'justifyContent', +}) + +export const flexWrap = style({ + prop: 'flexWrap', +}) + +export const flexBasis = style({ + prop: 'flexBasis', + transformValue: percent, +}) + +export const flexDirection = style({ + prop: 'flexDirection', +}) + +export const flex = style({ + prop: 'flex', +}) + +export const justifySelf = style({ + prop: 'justifySelf', +}) + +export const alignSelf = style({ + prop: 'alignSelf', +}) + +export const order = style({ + prop: 'order', +}) + +export const flexboxes = compose( + display, + alignItems, + alignContent, + justifyContent, + flexWrap, + flexBasis, + flexDirection, + flex, + justifySelf, + alignSelf, + order +) diff --git a/src/styles/grids.js b/src/styles/grids.js new file mode 100644 index 000000000..ffbc13522 --- /dev/null +++ b/src/styles/grids.js @@ -0,0 +1,45 @@ +import { style, compose } from '../style' +import { scale } from '../unit' + +export const gridGap = style({ + prop: 'gridGap', + key: 'space', + transformValue: scale(), +}) + +export const gridColumnGap = style({ + prop: 'gridColumnGap', + key: 'space', + transformValue: scale(), +}) + +export const gridRowGap = style({ + prop: 'gridRowGap', + key: 'space', + transformValue: scale(), +}) + +export const gridColumn = style({ prop: 'gridColumn' }) +export const gridRow = style({ prop: 'gridRow' }) +export const gridAutoFlow = style({ prop: 'gridAutoFlow' }) +export const gridAutoColumns = style({ prop: 'gridAutoColumns' }) +export const gridAutoRows = style({ prop: 'gridAutoRows' }) +export const gridTemplateColumns = style({ prop: 'gridTemplateColumns' }) +export const gridTemplateRows = style({ prop: 'gridTemplateRows' }) +export const gridTemplateAreas = style({ prop: 'gridTemplateAreas' }) +export const gridArea = style({ prop: 'gridArea' }) + +export const grids = compose( + gridGap, + gridColumnGap, + gridRowGap, + gridColumn, + gridRow, + gridAutoFlow, + gridAutoColumns, + gridAutoRows, + gridTemplateColumns, + gridTemplateRows, + gridTemplateAreas, + gridArea +) diff --git a/src/styles/index.js b/src/styles/index.js new file mode 100644 index 000000000..08e64fd66 --- /dev/null +++ b/src/styles/index.js @@ -0,0 +1,33 @@ +import { compose } from '../style' +import { backgrounds } from './backgrounds' +import { basics } from './basics' +import { borders } from './borders' +import { dimensions } from './dimensions' +import { flexboxes } from './flexboxes' +import { grids } from './grids' +import { positions } from './positions' +import { space } from './space' +import { typography } from './typography' + +export * from './backgrounds' +export * from './basics' +export * from './borders' +export * from './colors' +export * from './dimensions' +export * from './flexboxes' +export * from './grids' +export * from './positions' +export * from './space' +export * from './typography' + +export const system = compose( + backgrounds, + basics, + borders, + dimensions, + flexboxes, + grids, + positions, + space, + typography +) diff --git a/src/styles/index.test.js-nop b/src/styles/index.test.js-nop new file mode 100644 index 000000000..7f3bbad66 --- /dev/null +++ b/src/styles/index.test.js-nop @@ -0,0 +1,432 @@ +import React from 'react' +import { mount } from 'enzyme' +import styled from 'styled-components' +import * as styles from './index' + +describe('styles', () => { + describe.each([ + [ + 'fontFamily', + { + styleRule: 'font-family', + theme: { + fonts: { + primary: 'serif', + secondary: 'sans-serif', + }, + }, + expectations: [['arial', 'arial'], ['primary', 'serif']], + }, + ], + [ + 'fontSize', + { + styleRule: 'font-size', + theme: { + fontSizes: [10, 15, 40], + }, + expectations: [[0, '10px'], [1, '15px'], [20, '20px'], ['3em', '3em']], + }, + ], + [ + 'lineHeight', + { + styleRule: 'line-height', + theme: { + lineHeights: [1.2, 1.5, 2], + }, + expectations: [[0, 1.2], [1, 1.5], [3, 3], ['3em', '3em']], + }, + ], + [ + 'fontWeight', + { + styleRule: 'font-weight', + theme: { + fontWeights: [400, 500, 800], + }, + expectations: [[0, 400], [1, 500], [800, 800], ['medium', 'medium']], + }, + ], + [ + 'textAlign', + { + styleRule: 'text-align', + expectations: [['center', 'center'], ['justify', 'justify']], + }, + ], + [ + 'letterSpacing', + { + styleRule: 'letter-spacing', + theme: { + letterSpacings: [1.2, 2], + }, + expectations: [ + [0, '1.2px'], + [1, '2px'], + [1.1, '1.1px'], + ['2rem', '2rem'], + ], + }, + ], + [ + 'color', + { + styleRule: 'color', + theme: { + colors: { + primary: 'red', + }, + }, + expectations: [ + ['primary', 'red'], + ['#fff', '#fff'], + ['rgba(0,0,0,0)', 'rgba(0,0,0,0)'], + ], + }, + ], + [ + 'width', + { + styleRule: 'width', + theme: { + widths: { + large: 400, + }, + }, + expectations: [[0.5, '50%'], ['large', '400px'], [50, '50px']], + }, + ], + [ + 'height', + { + styleRule: 'height', + theme: { + heights: { + large: 400, + }, + }, + expectations: [[0.5, '50%'], ['large', '400px'], [50, '50px']], + }, + ], + [ + 'maxWidth', + { + styleRule: 'max-width', + theme: { + widths: { + large: 400, + }, + }, + expectations: [[0.5, '50%'], ['large', '400px'], [50, '50px']], + }, + ], + [ + 'maxHeight', + { + styleRule: 'max-height', + theme: { + heights: { + large: 400, + }, + }, + expectations: [[0.5, '50%'], ['large', '400px'], [50, '50px']], + }, + ], + [ + 'minWidth', + { + styleRule: 'min-width', + theme: { + widths: { + large: 400, + }, + }, + expectations: [[0.5, '50%'], ['large', '400px'], [50, '50px']], + }, + ], + [ + 'minHeight', + { + styleRule: 'min-height', + theme: { + heights: { + large: 400, + }, + }, + expectations: [[0.5, '50%'], ['large', '400px'], [50, '50px']], + }, + ], + [ + 'display', + { + styleRule: 'display', + expectations: [['flex', 'flex'], ['block', 'block']], + }, + ], + [ + 'alignItems', + { + styleRule: 'align-items', + expectations: [['flex-start', 'flex-start'], ['center', 'center']], + }, + ], + [ + 'alignContent', + { + styleRule: 'align-content', + expectations: [['flex-start', 'flex-start'], ['center', 'center']], + }, + ], + [ + 'justifyContent', + { + styleRule: 'justify-content', + expectations: [['flex-start', 'flex-start'], ['center', 'center']], + }, + ], + [ + 'flexWrap', + { + styleRule: 'flex-wrap', + expectations: [['wrap', 'wrap'], ['nowrap', 'nowrap']], + }, + ], + [ + 'flexBasis', + { + styleRule: 'flex-basis', + expectations: [[0.5, '50%'], [50, '50px']], + }, + ], + [ + 'flexDirection', + { + styleRule: 'flex-direction', + expectations: [['row', 'row'], ['column', 'column']], + }, + ], + [ + 'flex', + { + styleRule: 'flex', + expectations: [[1, '1'], ['1 0 auto', '1 0 auto']], + }, + ], + [ + 'justifySelf', + { + styleRule: 'justify-self', + expectations: [['flex-start', 'flex-start'], ['center', 'center']], + }, + ], + [ + 'alignSelf', + { + styleRule: 'align-self', + expectations: [['flex-start', 'flex-start'], ['center', 'center']], + }, + ], + [ + 'order', + { + styleRule: 'order', + expectations: [[1, '1'], [10, '10']], + }, + ], + [ + 'background', + { + styleRule: 'background', + expectations: [['red', 'red'], ['blue', 'blue']], + }, + ], + [ + 'backgroundColor', + { + theme: { + colors: { + primary: 'red', + }, + }, + styleRule: 'background-color', + expectations: [['primary', 'red'], ['blue', 'blue']], + }, + ], + [ + 'backgroundImage', + { + styleRule: 'background-image', + expectations: [['url(x.gif)', 'url(x.gif)']], + }, + ], + [ + 'backgroundSize', + { + styleRule: 'background-size', + expectations: [['cover', 'cover'], ['50%', '50%']], + }, + ], + [ + 'backgroundRepeat', + { + styleRule: 'background-repeat', + expectations: [['no-repeat', 'no-repeat'], ['repeat-y', 'repeat-y']], + }, + ], + [ + 'position', + { + styleRule: 'position', + expectations: [['absolute', 'absolute'], ['relative', 'relative']], + }, + ], + [ + 'zIndex', + { + theme: { + zIndexes: { + alert: 100, + }, + }, + styleRule: 'z-index', + expectations: [['alert', '100'], [20, '20']], + }, + ], + [ + 'top', + { + styleRule: 'top', + expectations: [[10, '10px'], ['10px', '10px'], ['4%', '4%']], + }, + ], + [ + 'right', + { + styleRule: 'right', + expectations: [[10, '10px'], ['10px', '10px'], ['4%', '4%']], + }, + ], + [ + 'bottom', + { + styleRule: 'bottom', + expectations: [[10, '10px'], ['10px', '10px'], ['4%', '4%']], + }, + ], + [ + 'left', + { + styleRule: 'left', + expectations: [[10, '10px'], ['10px', '10px'], ['4%', '4%']], + }, + ], + [ + 'border', + { + styleRule: 'border', + expectations: [[1, '1px solid'], ['1px solid red', '1px solid red']], + }, + ], + [ + 'borderTop', + { + styleRule: 'border-top', + expectations: [[1, '1px solid'], ['1px solid red', '1px solid red']], + }, + ], + [ + 'borderRight', + { + styleRule: 'border-right', + expectations: [[1, '1px solid'], ['1px solid red', '1px solid red']], + }, + ], + [ + 'borderBottom', + { + styleRule: 'border-bottom', + expectations: [[1, '1px solid'], ['1px solid red', '1px solid red']], + }, + ], + [ + 'borderLeft', + { + styleRule: 'border-left', + expectations: [[1, '1px solid'], ['1px solid red', '1px solid red']], + }, + ], + [ + 'borderColor', + { + theme: { + colors: { + primary: 'red', + }, + }, + styleRule: 'border-color', + expectations: [['primary', 'red'], ['blue', 'blue']], + }, + ], + [ + 'borderRadius', + { + theme: { + radii: { + round: '50%', + }, + }, + styleRule: 'border-radius', + expectations: [['round', '50%'], [10, '10px']], + }, + ], + [ + 'boxShadow', + { + theme: { + shadows: { + red: '10px 5px 5px red', + }, + }, + styleRule: 'box-shadow', + expectations: [ + ['red', '10px 5px 5px red'], + [ + '12px 12px 2px 1px rgba(0, 0, 255, .2)', + '12px 12px 2px 1px rgba(0,0,255,.2)', + ], + ], + }, + ], + [ + 'opacity', + { + styleRule: 'opacity', + expectations: [[1, '1'], [0.2, '0.2']], + }, + ], + ])('#%s', (name, config) => { + const Dummy = styled.div` + ${styles[name]}; + ` + + it('should support basic value', () => { + config.expectations.forEach(([value, expected]) => { + const props = { [name]: value } + const wrapper = mount() + expect(wrapper).toHaveStyleRule(config.styleRule, String(expected)) + }) + }) + + it('should support breakpoints value', () => { + config.expectations.forEach(([value, expected]) => { + const props = { [name]: { md: value } } + const wrapper = mount() + expect(wrapper).toHaveStyleRule(config.styleRule, String(expected), { + media: '(min-width:768px)', + }) + }) + }) + }) +}) diff --git a/src/styles/positions.js b/src/styles/positions.js new file mode 100644 index 000000000..7806fa01a --- /dev/null +++ b/src/styles/positions.js @@ -0,0 +1,40 @@ +import { style, compose } from '../style' +import { px } from '../unit' + +export const position = style({ + prop: 'position', +}) + +export const zIndex = style({ + prop: 'zIndex', + key: 'zIndices', +}) + +export const top = style({ + prop: 'top', + transformValue: px, +}) + +export const right = style({ + prop: 'right', + transformValue: px, +}) + +export const bottom = style({ + prop: 'bottom', + transformValue: px, +}) + +export const left = style({ + prop: 'left', + transformValue: px, +}) + +export const positions = compose( + position, + zIndex, + top, + right, + bottom, + left +) diff --git a/src/styles/space.js b/src/styles/space.js new file mode 100644 index 000000000..4654cf3ad --- /dev/null +++ b/src/styles/space.js @@ -0,0 +1,129 @@ +import { style, compose } from '../style' +import { scale } from '../unit' + +const key = 'space' + +export const margin = style({ + prop: 'margin', + alias: 'm', + cssProperties: ['margin'], + key, + transformValue: scale(), +}) + +export const marginTop = style({ + prop: 'marginTop', + alias: 'mt', + cssProperties: ['marginTop'], + key, + transformValue: scale(), +}) + +export const marginRight = style({ + prop: 'marginRight', + alias: 'mr', + cssProperties: ['marginRight'], + key, + transformValue: scale(), +}) + +export const marginBottom = style({ + prop: 'marginBottom', + alias: 'mb', + cssProperties: ['marginBottom'], + key, + transformValue: scale(), +}) + +export const marginLeft = style({ + prop: 'marginLeft', + alias: 'ml', + cssProperties: ['marginLeft'], + key, + transformValue: scale(), +}) + +export const mx = style({ + prop: 'mx', + cssProperties: ['marginRight', 'marginLeft'], + key, + transformValue: scale(), +}) + +export const my = style({ + prop: 'my', + cssProperties: ['marginTop', 'marginBottom'], + key, + transformValue: scale(), +}) + +export const padding = style({ + prop: 'padding', + alias: 'p', + cssProperties: ['padding'], + key, + transformValue: scale(), +}) + +export const paddingTop = style({ + prop: 'paddingTop', + alias: 'pt', + cssProperties: ['paddingTop'], + key, + transformValue: scale(), +}) + +export const paddingRight = style({ + prop: 'paddingRight', + alias: 'pr', + cssProperties: ['paddingRight'], + key, + transformValue: scale(), +}) + +export const paddingBottom = style({ + prop: 'paddingBottom', + alias: 'pb', + cssProperties: ['paddingBottom'], + key, + transformValue: scale(), +}) + +export const paddingLeft = style({ + prop: 'paddingLeft', + alias: 'pl', + cssProperties: ['paddingLeft'], + key, + transformValue: scale(), +}) + +export const px = style({ + prop: 'px', + cssProperties: ['paddingRight', 'paddingLeft'], + key, + transformValue: scale(), +}) + +export const py = style({ + prop: 'py', + cssProperties: ['paddingTop', 'paddingBottom'], + key, + transformValue: scale(), +}) + +export const space = compose( + margin, + marginTop, + marginRight, + marginBottom, + marginLeft, + mx, + my, + padding, + paddingTop, + paddingRight, + paddingBottom, + paddingLeft, + px, + py +) diff --git a/src/styles/space.test.js-nop b/src/styles/space.test.js-nop new file mode 100644 index 000000000..2f79ab728 --- /dev/null +++ b/src/styles/space.test.js-nop @@ -0,0 +1,108 @@ +import { space } from './space' + +describe('space', () => { + it('should support m', () => { + expect(space({ m: 1 })).toEqual({ margin: 8 }) + expect(space({ m: 2 })).toEqual({ margin: 16 }) + expect(space({ m: -2 })).toEqual({ margin: -16 }) + expect(space({ m: 10 })).toEqual({ margin: 10 }) + expect(space({ m: -10 })).toEqual({ margin: -10 }) + expect(space({ m: '50%' })).toEqual({ margin: '50%' }) + expect(space({ m: { md: '50%' } })).toEqual({ + '@media (min-width: 768px)': { margin: '50%' }, + }) + }) + + it('should support mx', () => { + expect(space({ mx: 10 })).toEqual({ + marginLeft: 10, + marginRight: 10, + }) + expect(space({ mx: '50%' })).toEqual({ + marginLeft: '50%', + marginRight: '50%', + }) + expect(space({ mx: { md: '50%' } })).toEqual({ + '@media (min-width: 768px)': { marginLeft: '50%', marginRight: '50%' }, + }) + }) + + it('should support mb, mt, ml, mr', () => { + expect(space({ mb: 10 })).toEqual({ marginBottom: 10 }) + expect(space({ mt: 10 })).toEqual({ marginTop: 10 }) + expect(space({ ml: 10 })).toEqual({ marginLeft: 10 }) + expect(space({ mr: 10 })).toEqual({ marginRight: 10 }) + }) + + it('should support p', () => { + expect(space({ p: 10 })).toEqual({ padding: 10 }) + expect(space({ p: '50%' })).toEqual({ padding: '50%' }) + expect(space({ p: { md: '50%' } })).toEqual({ + '@media (min-width: 768px)': { padding: '50%' }, + }) + }) + + it('should support px, py', () => { + expect(space({ px: 10 })).toEqual({ + paddingLeft: 10, + paddingRight: 10, + }) + expect(space({ px: '50%' })).toEqual({ + paddingLeft: '50%', + paddingRight: '50%', + }) + expect(space({ px: { md: '50%' } })).toEqual({ + '@media (min-width: 768px)': { paddingLeft: '50%', paddingRight: '50%' }, + }) + }) + + it('should support pb, pt, pl, pr', () => { + expect(space({ pb: 10 })).toEqual({ paddingBottom: 10 }) + expect(space({ pt: 10 })).toEqual({ paddingTop: 10 }) + expect(space({ pl: 10 })).toEqual({ paddingLeft: 10 }) + expect(space({ pr: 10 })).toEqual({ paddingRight: 10 }) + }) + + it('should merge everything', () => { + expect(space({ px: { md: '50%' }, mx: { md: 10 } })).toEqual({ + '@media (min-width: 768px)': { + paddingLeft: '50%', + paddingRight: '50%', + marginLeft: 10, + marginRight: 10, + }, + }) + }) + + it('should support variants spaces', () => { + expect( + space({ + m: 1, + p: 0, + theme: { spaces: [0, 8, 16] }, + }) + ).toEqual({ + margin: 8, + padding: 0, + }) + }) + + it('should expose meta', () => { + expect(space.meta).toEqual([ + 'm', + 'mt', + 'mr', + 'mb', + 'ml', + 'mx', + 'my', + 'p', + 'pt', + 'pr', + 'pb', + 'pl', + 'px', + 'py', + ]) + }) +}) diff --git a/src/styles/typography.js b/src/styles/typography.js new file mode 100644 index 000000000..a4a1ba69c --- /dev/null +++ b/src/styles/typography.js @@ -0,0 +1,54 @@ +import { px } from '../unit' +import { style, compose } from '../style' +import { scale } from '../unit' + +export const fontFamily = style({ + prop: 'fontFamily', + key: 'fonts', +}) + +export const fontSize = style({ + prop: 'fontSize', + key: 'fontSizes', + transformValue: scale([12, 14, 16, 20, 24, 32, 48, 64, 72]), +}) + +export const lineHeight = style({ + prop: 'lineHeight', + key: 'lineHeights', +}) + +export const fontWeight = style({ + prop: 'fontWeight', + key: 'fontWeights', +}) + +export const textAlign = style({ + prop: 'textAlign', +}) + +export const letterSpacing = style({ + prop: 'letterSpacing', + key: 'letterSpacings', + transformValue: px, +}) + +export const textColor = style({ + prop: 'color', + key: 'colors', +}) + +export const textTransform = style({ + prop: 'textTransform', +}) + +export const typography = compose( + fontFamily, + fontSize, + lineHeight, + fontWeight, + textAlign, + letterSpacing, + textColor, + textTransform +) diff --git a/src/unit.js b/src/unit.js new file mode 100644 index 000000000..9120bdcd8 --- /dev/null +++ b/src/unit.js @@ -0,0 +1,23 @@ +import { num, negative } from './util' + +const DEFAULT_SPACE = [0, 4, 8, 16, 32, 64, 128, 256, 512] + +export const unit = unit => value => (num(value) ? `${value}${unit}` : value) +export const px = unit('px') +export const percent = n => (!num(n) || n > 1 ? px(n) : `${n * 100}%`) + +export const scale = (defaultVariants = DEFAULT_SPACE) => ( + transformedValue, + { rawValue, variants = defaultVariants } +) => { + if (!num(rawValue)) { + return variants[rawValue] || rawValue + } + const abs = Math.abs(rawValue) + const neg = negative(rawValue) + const value = variants[abs] || abs + if (!num(value)) { + return neg ? `-${value}` : value + } + return value * (neg ? -1 : 1) +} diff --git a/src/util.js b/src/util.js new file mode 100644 index 000000000..2c2d90d59 --- /dev/null +++ b/src/util.js @@ -0,0 +1,33 @@ +import deepmerge from 'deepmerge' // < 1kb payload overhead when lodash/merge is > 3kb. + +export const is = n => n !== undefined && n !== null +export const num = n => typeof n === 'number' && !Number.isNaN(n) +export const string = n => typeof n === 'string' && n !== '' +export const obj = n => typeof n === 'object' && n !== null +export const func = n => typeof n === 'function' +export const negative = n => n < 0 + +export const get = (obj, path) => + String(path) + .split('.') + .reduce((a, b) => (a && is(a[b]) ? a[b] : undefined), obj) + +export function merge(acc, item) { + if (!item) { + return acc + } + + return deepmerge(acc, item, { + clone: false, // No need to clone deep, it's way faster. + }) +} + +export const assign = (target, source) => { + const keys = Object.keys(source || {}) + const totalKeys = keys.length + for (let i = 0; i < totalKeys; i += 1) { + const key = keys[i] + target[key] = source[key] + } + return target +} diff --git a/src/variant.js b/src/variant.js new file mode 100644 index 000000000..3371a05b7 --- /dev/null +++ b/src/variant.js @@ -0,0 +1,4 @@ +import { get } from './util' + +export const variant = ({ key, prop = 'variant' }) => props => + get(props.theme, [key, props[prop]].join('.')) || null diff --git a/src/variants.js b/src/variants.js new file mode 100644 index 000000000..02fd1abc1 --- /dev/null +++ b/src/variants.js @@ -0,0 +1,5 @@ +import { variant } from './variant' + +export const buttonStyle = variant({ key: 'buttons' }) +export const textStyle = variant({ key: 'textStyles', prop: 'textStyle' }) +export const colorStyle = variant({ key: 'colorStyles', prop: 'colors' }) diff --git a/test/index.js b/test/index.js index b1e48c53c..7790321db 100644 --- a/test/index.js +++ b/test/index.js @@ -1,16 +1,5 @@ import test from 'ava' -import { - style, - get, - themeGet, - is, - num, - px, - compose, - variant, - cloneFunction, - mapProps, -} from '../src' +import { style, compose, variant } from '../src/index2' const width = style({ prop: 'width', @@ -63,16 +52,18 @@ test('handles aliased props', t => { }) }) -test('long form prop trumps aliased props', t => { - const style = backgroundColor({ - theme, - backgroundColor: 'black', - bg: 'blue', - }) - t.deepEqual(style, { - backgroundColor: '#111', - }) -}) +// Impossible to ensure, due to perf issues + +// test('long form prop trumps aliased props', t => { +// const style = backgroundColor({ +// theme, +// backgroundColor: 'black', +// bg: 'blue', +// }) +// t.deepEqual(style, { +// backgroundColor: '#111', +// }) +// }) test('returns null', t => { const style = color({}) @@ -88,98 +79,116 @@ test('returns an array of responsive style objects', t => { const style = width({ width: ['100%', '50%'], }) - t.deepEqual(style, [ - { width: '100%' }, - { '@media screen and (min-width: 40em)': { width: '50%' } }, - ]) + t.deepEqual(style, { + width: '100%', + '@media screen and (min-width: 40em)': { width: '50%' }, + }) }) test('returns an array of responsive style objects for all breakpoints', t => { const style = width({ width: ['100%', '75%', '50%', '33%', '25%'], }) - t.deepEqual(style, [ - { width: '100%' }, - { '@media screen and (min-width: 40em)': { width: '75%' } }, - { '@media screen and (min-width: 52em)': { width: '50%' } }, - { '@media screen and (min-width: 64em)': { width: '33%' } }, - ]) + t.deepEqual(style, { + width: '100%', + '@media screen and (min-width: 40em)': { width: '75%' }, + '@media screen and (min-width: 52em)': { width: '50%' }, + '@media screen and (min-width: 64em)': { width: '33%' }, + }) }) test('skips undefined responsive values', t => { const style = width({ width: ['100%', , '50%'], }) - t.deepEqual(style, [ - { width: '100%' }, - { '@media screen and (min-width: 52em)': { width: '50%' } }, - ]) + t.deepEqual(style, { + width: '100%', + '@media screen and (min-width: 52em)': { width: '50%' }, + }) }) +// It is more strict now, see next test + +// test('parses object values', t => { +// const style = width({ +// width: { +// _: '100%', +// 2: '50%', +// }, +// }) +// t.deepEqual(style, { +// width: '100%', +// '@media screen and (min-width: 64em)': { width: '50%' }, +// }) +// }) + test('parses object values', t => { const style = width({ width: { _: '100%', - 2: '50%', + 1: '50%', }, }) - t.deepEqual(style, [ - { width: '100%' }, - { '@media screen and (min-width: 64em)': { width: '50%' } }, - ]) -}) - -test('get returns a value', t => { - const a = get({ blue: '#0cf' }, 'blue') - t.is(a, '#0cf') -}) - -test('get returns the last argument if no value is found', t => { - const a = get( - { - blue: '#0cf', - }, - 'green', - '#0f0' - ) - t.is(a, '#0f0') -}) - -test('get returns 0', t => { - const a = get({}, 0) - const b = get({ space: [0, 4] }, 0) - t.is(a, 0) - t.is(b, 0) -}) - -test('get returns deeply nested values', t => { - const a = get( - { - hi: { - hello: { - beep: 'boop', - }, - }, - }, - 'hi.hello.beep' - ) - t.is(a, 'boop') -}) - -test('themeGet returns values from the theme', t => { - const a = themeGet('colors.blue')({ theme }) - t.is(a, '#07c') + t.deepEqual(style, { + '@media screen and (min-width: 40em)': { width: '50%' }, + }) }) -test('themeGet does not throw when value doesnt exist', t => { - const a = themeGet('colors.blue.5')({ theme }) - t.is(a, null) -}) - -test('themeGet accepts a fallback', t => { - const a = themeGet('colors.lightblue', '#0cf')({ theme }) - t.is(a, '#0cf') -}) +// We will not export "get" anymore + +// test('get returns a value', t => { +// const a = get({ blue: '#0cf' }, 'blue') +// t.is(a, '#0cf') +// }) + +// test('get returns the last argument if no value is found', t => { +// const a = get( +// { +// blue: '#0cf', +// }, +// 'green', +// '#0f0' +// ) +// t.is(a, '#0f0') +// }) + +// test('get returns 0', t => { +// const a = get({}, 0) +// const b = get({ space: [0, 4] }, 0) +// t.is(a, 0) +// t.is(b, 0) +// }) + +// test('get returns deeply nested values', t => { +// const a = get( +// { +// hi: { +// hello: { +// beep: 'boop', +// }, +// }, +// }, +// 'hi.hello.beep' +// ) +// t.is(a, 'boop') +// }) + +// We will not export "themeGet" anymore + +// test('themeGet returns values from the theme', t => { +// const a = themeGet('colors.blue')({ theme }) +// t.is(a, '#07c') +// }) + +// test('themeGet does not throw when value doesnt exist', t => { +// const a = themeGet('colors.blue.5')({ theme }) +// t.is(a, null) +// }) + +// test('themeGet accepts a fallback', t => { +// const a = themeGet('colors.lightblue', '#0cf')({ theme }) +// t.is(a, '#0cf') +// }) test('compose combines style functions', t => { const colors = compose( @@ -191,37 +200,43 @@ test('compose combines style functions', t => { bg: 'black', }) t.is(typeof colors, 'function') - t.deepEqual(styles, [{ color: 'tomato' }, { backgroundColor: 'black' }]) + t.deepEqual(styles, { color: 'tomato', backgroundColor: 'black' }) }) -test('num returns true for numbers', t => { - const isNumber = num(0) - t.true(isNumber) -}) +// We will not export "num" anymore -test('num returns false for non-numbers', t => { - const isNumber = num(null) - t.false(isNumber) -}) +// test('num returns true for numbers', t => { +// const isNumber = num(0) +// t.true(isNumber) +// }) -test('is returns true for truthy values', t => { - const isValue = is(0) - t.true(isValue) -}) +// test('num returns false for non-numbers', t => { +// const isNumber = num(null) +// t.false(isNumber) +// }) -test('is returns false for falsey values', t => { - const a = is(null) - const b = is(undefined) - t.false(a) - t.false(b) -}) +// We will not export "is" anymore -test('cloneFunction creates a new function', t => { - const func = () => 'hi' - const b = cloneFunction(func) - t.false(func === b) - t.is(b(), 'hi') -}) +// test('is returns true for truthy values', t => { +// const isValue = is(0) +// t.true(isValue) +// }) + +// test('is returns false for falsey values', t => { +// const a = is(null) +// const b = is(undefined) +// t.false(a) +// t.false(b) +// }) + +// We will not export "cloneFunction" anymore + +// test('cloneFunction creates a new function', t => { +// const func = () => 'hi' +// const b = cloneFunction(func) +// t.false(func === b) +// t.is(b(), 'hi') +// }) test('variant returns style objects from theme', t => { const buttons = variant({ key: 'buttons' }) @@ -263,17 +278,15 @@ test('variant prop can be customized', t => { test('array values longer than breakpoints does not reset returned style object', t => { const a = width({ - width: [ - '100%',,,,,'50%', '25%', - ] + width: ['100%', , , , , '50%', '25%'], }) - t.deepEqual(a, [ - { width: '100%' }, - ]) + t.deepEqual(a, { width: '100%' }) }) -test('mapProps copies propTypes', t => { - const margin = style({ prop: 'margin' }) - const func = mapProps(props => props)(margin) - t.is(typeof func.propTypes, 'object') -}) +// No longer relevant + +// test('mapProps copies propTypes', t => { +// const margin = style({ prop: 'margin' }) +// const func = mapProps(props => props)(margin) +// t.is(typeof func.propTypes, 'object') +// }) diff --git a/test/styles.js b/test/styles.js index d90129bd7..6e39397c0 100644 --- a/test/styles.js +++ b/test/styles.js @@ -11,7 +11,7 @@ import { textStyle, colorStyle, borders, -} from '../src' +} from '../src/index2' const theme = { colors: { @@ -22,7 +22,7 @@ const theme = { test('returns color values from theme', t => { const a = color({ theme, color: 'blue', bg: 'black' }) - t.deepEqual(a, [{ color: '#07c' }, { backgroundColor: '#111' }]) + t.deepEqual(a, { color: '#07c', backgroundColor: '#111' }) }) test('returns raw color values', t => { @@ -31,25 +31,29 @@ test('returns raw color values', t => { color: 'inherit', bg: 'tomato', }) - t.deepEqual(a, [{ color: 'inherit' }, { backgroundColor: 'tomato' }]) + t.deepEqual(a, { color: 'inherit', backgroundColor: 'tomato' }) }) -test('backgroundColor prop overrides bg prop', t => { - const a = color({ - backgroundColor: 'tomato', - bg: 'blue', - }) - t.deepEqual(a, [{ backgroundColor: 'tomato' }]) -}) +// Impossible to ensure, due to perf issues -test('returns a pixel font-size', t => { - const a = fontSize({ fontSize: 48 }) - t.deepEqual(a, { fontSize: '48px' }) -}) +// test('backgroundColor prop overrides bg prop', t => { +// const a = color({ +// backgroundColor: 'tomato', +// bg: 'blue', +// }) +// t.deepEqual(a, [{ backgroundColor: 'tomato' }]) +// }) + +// Useless, font-size default to pixels + +// test('returns a pixel font-size', t => { +// const a = fontSize({ fontSize: 48 }) +// t.deepEqual(a, { fontSize: '48px' }) +// }) test('uses a default font-size scale', t => { const a = fontSize({ fontSize: 2 }) - t.deepEqual(a, { fontSize: '16px' }) + t.deepEqual(a, { fontSize: 16 }) }) test('returns a string font-size', t => { @@ -76,22 +80,22 @@ test('returns an array of style objects', t => { const styles = space({ m: '4px', }) - t.deepEqual(styles, [{ margin: '4px' }]) + t.deepEqual(styles, { margin: '4px' }) }) test('returns 0 values', t => { const styles = space({ m: 0 }) - t.deepEqual(styles, [{ margin: 0 }]) + t.deepEqual(styles, { margin: 0 }) }) test('returns negative pixel values', t => { const styles = space({ m: -2 }) - t.deepEqual(styles, [{ margin: '-8px' }]) + t.deepEqual(styles, { margin: -8 }) }) test('returns negative em values', t => { const styles = space({ m: '-16em' }) - t.deepEqual(styles, [{ margin: '-16em' }]) + t.deepEqual(styles, { margin: '-16em' }) }) test('returns negative theme values', t => { @@ -101,7 +105,7 @@ test('returns negative theme values', t => { }, m: -2, }) - t.deepEqual(styles, [{ margin: '-8px' }]) + t.deepEqual(styles, { margin: -8 }) }) test('returns positive theme values', t => { @@ -111,27 +115,25 @@ test('returns positive theme values', t => { }, m: 2, }) - t.deepEqual(styles, [{ margin: '2em' }]) + t.deepEqual(styles, { margin: '2em' }) }) test('returns responsive values', t => { const styles = space({ m: [0, 2, 3], }) - t.deepEqual(styles, [ - [ - { margin: 0 }, - { '@media screen and (min-width: 40em)': { margin: '8px' } }, - { '@media screen and (min-width: 52em)': { margin: '16px' } }, - ], - ]) + t.deepEqual(styles, { + margin: 0, + '@media screen and (min-width: 40em)': { margin: 8 }, + '@media screen and (min-width: 52em)': { margin: 16 }, + }) }) test('returns aliased values', t => { const styles = space({ px: 2, }) - t.deepEqual(styles, [{ paddingLeft: '8px' }, { paddingRight: '8px' }]) + t.deepEqual(styles, { paddingLeft: 8, paddingRight: 8 }) }) test('returns string values from theme', t => { @@ -141,7 +143,7 @@ test('returns string values from theme', t => { }, padding: 1, }) - t.deepEqual(styles, [{ padding: '1em' }]) + t.deepEqual(styles, { padding: '1em' }) }) test('returns negative string values from theme', t => { @@ -151,7 +153,7 @@ test('returns negative string values from theme', t => { }, margin: -1, }) - t.deepEqual(styles, [{ margin: '-1em' }]) + t.deepEqual(styles, { margin: '-1em' }) }) test('returns values from theme object', t => { @@ -162,73 +164,79 @@ test('returns values from theme object', t => { margin: 'sm', }) - t.deepEqual(styles, [{ margin: '1px' }]) + t.deepEqual(styles, { margin: 1 }) }) test('pl prop sets paddingLeft', t => { const styles = space({ pl: 2 }) - t.deepEqual(styles, [{ paddingLeft: '8px' }]) + t.deepEqual(styles, { paddingLeft: 8 }) }) test('pl prop sets paddingLeft 0', t => { const styles = space({ pl: 0 }) - t.deepEqual(styles, [{ paddingLeft: 0 }]) + t.deepEqual(styles, { paddingLeft: 0 }) }) +// The order of props matter test('px prop overrides pl prop', t => { const styles = space({ pl: 1, px: 2, }) - t.deepEqual(styles, [{ paddingLeft: '8px' }, { paddingRight: '8px' }]) + t.deepEqual(styles, { paddingLeft: 8, paddingRight: 8 }) }) +// The order of props matter test('py prop overrides pb prop', t => { const styles = space({ pb: 1, py: 2, }) - t.deepEqual(styles, [{ paddingTop: '8px' }, { paddingBottom: '8px' }]) + t.deepEqual(styles, { paddingTop: 8, paddingBottom: 8 }) }) +// The order of props matter test('mx prop overrides mr prop', t => { const styles = space({ mr: 1, mx: 2, }) - t.deepEqual(styles, [{ marginLeft: '8px' }, { marginRight: '8px' }]) + t.deepEqual(styles, { marginLeft: 8, marginRight: 8 }) }) +// The order of props matter test('my prop overrides mt prop', t => { const styles = space({ mt: 1, my: 2, }) - t.deepEqual(styles, [{ marginTop: '8px' }, { marginBottom: '8px' }]) + t.deepEqual(styles, { marginTop: 8, marginBottom: 8 }) }) +// The order of props matter test('margin overrides m prop', t => { const styles = space({ m: 1, margin: 2, }) - t.deepEqual(styles, [{ margin: '8px' }]) + t.deepEqual(styles, { margin: 8 }) }) -test('space includes propTypes', t => { - const { propTypes } = space - t.is(typeof propTypes, 'object') - t.is(typeof propTypes.m, 'function') -}) +// PropTypes are no longer included +// test('space includes propTypes', t => { +// const { propTypes } = space +// t.is(typeof propTypes, 'object') +// t.is(typeof propTypes.m, 'function') +// }) test('size returns width and height', t => { const styles = size({ size: 4, }) - t.deepEqual(styles, [{ width: '4px' }, { height: '4px' }]) + t.deepEqual(styles, { width: '4px', height: '4px' }) }) -// grid +// // grid test('gridGap returns a scalar style', t => { const a = gridGap({ theme: { @@ -237,7 +245,7 @@ test('gridGap returns a scalar style', t => { gridGap: 3, }) - t.deepEqual(a, { gridGap: '8px' }) + t.deepEqual(a, { gridGap: 8 }) }) test('gridGap uses the default scale', t => { @@ -246,7 +254,7 @@ test('gridGap uses the default scale', t => { gridGap: 2, }) - t.deepEqual(a, { gridGap: '8px' }) + t.deepEqual(a, { gridGap: 8 }) }) test('gridRowGap returns a scalar style', t => { @@ -257,7 +265,7 @@ test('gridRowGap returns a scalar style', t => { gridRowGap: 3, }) - t.deepEqual(a, { gridRowGap: '8px' }) + t.deepEqual(a, { gridRowGap: 8 }) }) test('gridRowGap uses the default scale', t => { @@ -266,7 +274,7 @@ test('gridRowGap uses the default scale', t => { gridRowGap: 2, }) - t.deepEqual(a, { gridRowGap: '8px' }) + t.deepEqual(a, { gridRowGap: 8 }) }) test('gridColumnGap returns a scalar style', t => { @@ -277,7 +285,7 @@ test('gridColumnGap returns a scalar style', t => { gridColumnGap: 3, }) - t.deepEqual(a, { gridColumnGap: '8px' }) + t.deepEqual(a, { gridColumnGap: 8 }) }) test('gridColumnGap uses the default scale', t => { @@ -286,7 +294,7 @@ test('gridColumnGap uses the default scale', t => { gridColumnGap: 2, }) - t.deepEqual(a, { gridColumnGap: '8px' }) + t.deepEqual(a, { gridColumnGap: 8 }) }) test('textStyle prop returns theme.textStyles object', t => { @@ -332,10 +340,10 @@ test('borders prop returns correct sequence', t => { borderStyle: 'dashed', borderColor: 'red', }) - t.deepEqual(a, [ - { borderBottom: '1px solid' }, - { borderWidth: '2px' }, - { borderStyle: 'dashed' }, - { borderColor: 'red' }, - ]) + t.deepEqual(a, { + borderBottom: '1px solid', + borderWidth: '2px', + borderStyle: 'dashed', + borderColor: 'red', + }) })