diff --git a/package-lock.json b/package-lock.json index 997d964ea..062807fac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "styled-system", - "version": "4.1.1", + "version": "4.2.0-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eb86bdf94..bb75bdfa7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "styled-system", - "version": "4.1.1", + "version": "4.2.0-0", "description": "Responsive, theme-based style props for building design systems with React", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", @@ -58,6 +58,11 @@ "bench", "examples", "packages/**/*" + ], + "reporter": [ + "lcov", + "text-summary", + "html" ] }, "bundlesize": [ diff --git a/src/index.js b/src/index.js index c5435234f..34bd955ca 100644 --- a/src/index.js +++ b/src/index.js @@ -35,6 +35,30 @@ export const createMediaQuery = n => `@media screen and (min-width: ${px(n)})` const getValue = (n, scale) => get(scale, n) +// loosely based on deepmerge package +export const merge = (a, b) => { + const result = {} + for (const key in a) { + result[key] = a[key] + } + for (const key in b) { + if (!a[key]) { + result[key] = b[key] + } else { + result[key] = merge(a[key], b[key]) + } + } + return result +} + +const mergeAll = (...args) => { + let result = {} + for (let i = 0; i < args.length; i++) { + result = merge(result, args[i]) + } + return result +} + export const style = ({ prop, cssProperty, @@ -83,7 +107,7 @@ export const style = ({ styles.sort() } - return styles + return mergeAll(...styles) } func.propTypes = { @@ -108,7 +132,7 @@ export const style = ({ export const compose = (...funcs) => { const func = props => { const n = funcs.map(fn => fn(props)).filter(Boolean) - return n + return mergeAll(...n) } func.propTypes = {} diff --git a/test/index.js b/test/index.js index b5b6d5cdf..4960c87ae 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ import { variant, cloneFunction, mapProps, + merge, } from '../src' const width = style({ @@ -88,32 +89,32 @@ 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%' }, + }) }) test('parses object values', t => { @@ -123,10 +124,10 @@ test('parses object values', t => { 2: '50%', }, }) - t.deepEqual(style, [ - { width: '100%' }, - { '@media screen and (min-width: 64em)': { width: '50%' } }, - ]) + t.deepEqual(style, { + width: '100%', + '@media screen and (min-width: 64em)': { width: '50%' }, + }) }) test('get returns a value', t => { @@ -191,7 +192,7 @@ 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 => { @@ -265,7 +266,7 @@ test('array values longer than breakpoints does not reset returned style object' const a = width({ width: ['100%', , , , , '50%', '25%'], }) - t.deepEqual(a, [{ width: '100%' }]) + t.deepEqual(a, { width: '100%' }) }) test('mapProps copies propTypes', t => { @@ -273,3 +274,16 @@ test('mapProps copies propTypes', t => { const func = mapProps(props => props)(margin) t.is(typeof func.propTypes, 'object') }) + +test('merge deeply merges', t => { + const result = merge( + { hello: { hi: 'beep' } }, + { hello: { hey: 'boop' } }, + ) + t.deepEqual(result, { + hello: { + hi: 'beep', + hey: 'boop', + } + }) +}) diff --git a/test/styles.js b/test/styles.js index d90129bd7..60fcf6ace 100644 --- a/test/styles.js +++ b/test/styles.js @@ -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,7 +31,7 @@ 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 => { @@ -39,7 +39,7 @@ test('backgroundColor prop overrides bg prop', t => { backgroundColor: 'tomato', bg: 'blue', }) - t.deepEqual(a, [{ backgroundColor: 'tomato' }]) + t.deepEqual(a, { backgroundColor: 'tomato' }) }) test('returns a pixel font-size', t => { @@ -76,22 +76,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: '-8px' }) }) 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 +101,7 @@ test('returns negative theme values', t => { }, m: -2, }) - t.deepEqual(styles, [{ margin: '-8px' }]) + t.deepEqual(styles, { margin: '-8px' }) }) test('returns positive theme values', t => { @@ -111,27 +111,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: '8px' }, + '@media screen and (min-width: 52em)': { margin: '16px' }, + }) }) test('returns aliased values', t => { const styles = space({ px: 2, }) - t.deepEqual(styles, [{ paddingLeft: '8px' }, { paddingRight: '8px' }]) + t.deepEqual(styles, { paddingLeft: '8px', paddingRight: '8px' }) }) test('returns string values from theme', t => { @@ -141,7 +139,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 +149,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,17 +160,17 @@ test('returns values from theme object', t => { margin: 'sm', }) - t.deepEqual(styles, [{ margin: '1px' }]) + t.deepEqual(styles, { margin: '1px' }) }) test('pl prop sets paddingLeft', t => { const styles = space({ pl: 2 }) - t.deepEqual(styles, [{ paddingLeft: '8px' }]) + t.deepEqual(styles, { paddingLeft: '8px' }) }) test('pl prop sets paddingLeft 0', t => { const styles = space({ pl: 0 }) - t.deepEqual(styles, [{ paddingLeft: 0 }]) + t.deepEqual(styles, { paddingLeft: 0 }) }) test('px prop overrides pl prop', t => { @@ -180,7 +178,7 @@ test('px prop overrides pl prop', t => { pl: 1, px: 2, }) - t.deepEqual(styles, [{ paddingLeft: '8px' }, { paddingRight: '8px' }]) + t.deepEqual(styles, { paddingLeft: '8px', paddingRight: '8px' }) }) test('py prop overrides pb prop', t => { @@ -188,7 +186,7 @@ test('py prop overrides pb prop', t => { pb: 1, py: 2, }) - t.deepEqual(styles, [{ paddingTop: '8px' }, { paddingBottom: '8px' }]) + t.deepEqual(styles, { paddingTop: '8px', paddingBottom: '8px' }) }) test('mx prop overrides mr prop', t => { @@ -196,7 +194,7 @@ test('mx prop overrides mr prop', t => { mr: 1, mx: 2, }) - t.deepEqual(styles, [{ marginLeft: '8px' }, { marginRight: '8px' }]) + t.deepEqual(styles, { marginLeft: '8px', marginRight: '8px' }) }) test('my prop overrides mt prop', t => { @@ -204,7 +202,7 @@ test('my prop overrides mt prop', t => { mt: 1, my: 2, }) - t.deepEqual(styles, [{ marginTop: '8px' }, { marginBottom: '8px' }]) + t.deepEqual(styles, { marginTop: '8px', marginBottom: '8px' }) }) test('margin overrides m prop', t => { @@ -212,7 +210,7 @@ test('margin overrides m prop', t => { m: 1, margin: 2, }) - t.deepEqual(styles, [{ margin: '8px' }]) + t.deepEqual(styles, { margin: '8px' }) }) test('space includes propTypes', t => { @@ -225,7 +223,7 @@ 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 @@ -332,10 +330,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', + }) })