diff --git a/CHANGELOG.md b/CHANGELOG.md index 424ff81b5..5b492b18f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Adjust media query sort logic #600 - Fixed link to Gatsby Plugin page in `getting-started` page. Issue #602 +- Adds support for combining multiple variants (pass `variants` an array in `sx` or as a prop on components) ## v0.3.0 2019-01-22 diff --git a/packages/components/package.json b/packages/components/package.json index 849616e59..171457a8a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -14,7 +14,8 @@ "@styled-system/color": "^5.1.2", "@styled-system/should-forward-prop": "^5.1.2", "@styled-system/space": "^5.1.2", - "@theme-ui/css": "^0.3.1" + "@theme-ui/css": "^0.3.1", + "deepmerge": "^4.2.2" }, "peerDependencies": { "react": "^16.8.0" diff --git a/packages/components/src/Box.js b/packages/components/src/Box.js index c4c0c0bfa..e75a0b1ad 100644 --- a/packages/components/src/Box.js +++ b/packages/components/src/Box.js @@ -3,6 +3,7 @@ import { css, get } from '@theme-ui/css' import { createShouldForwardProp } from '@styled-system/should-forward-prop' import space from '@styled-system/space' import color from '@styled-system/color' +import deepmerge from 'deepmerge' const shouldForwardProp = createShouldForwardProp([ ...space.propNames, @@ -11,8 +12,17 @@ const shouldForwardProp = createShouldForwardProp([ const sx = props => css(props.sx)(props.theme) const base = props => css(props.__css)(props.theme) + const variant = ({ theme, variant, __themeKey = 'variants' }) => css(get(theme, __themeKey + '.' + variant, get(theme, variant))) +const variants = ({ theme, variants = [], __themeKey = 'variants' }) => { + if (!Array.isArray(variants)) return {} + return css( + deepmerge.all( + variants.map(v => get(theme, __themeKey + '.' + v, get(theme, v))) + ) + ) +} export const Box = styled('div', { shouldForwardProp, @@ -23,6 +33,7 @@ export const Box = styled('div', { minWidth: 0, }, base, + variants, variant, space, color, diff --git a/packages/components/test/__snapshots__/index.js.snap b/packages/components/test/__snapshots__/index.js.snap index 203351cc5..d8463d31e 100644 --- a/packages/components/test/__snapshots__/index.js.snap +++ b/packages/components/test/__snapshots__/index.js.snap @@ -180,6 +180,18 @@ exports[`Box renders 1`] = ` `; +exports[`Box renders with incorrect type of variants prop 1`] = ` +.emotion-0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +
+`; + exports[`Button renders 1`] = ` .emotion-0 { box-sizing: border-box; diff --git a/packages/components/test/index.js b/packages/components/test/index.js index 3d7a9d279..4a86d1607 100644 --- a/packages/components/test/index.js +++ b/packages/components/test/index.js @@ -48,6 +48,10 @@ const theme = { p: 4, bg: 'highlight', }, + boop: { + bg: 'tomato', + color: 'white', + }, }, cards: { primary: { @@ -132,6 +136,38 @@ describe('Box', () => { expect(json).toHaveStyleRule('padding', '32px') }) + test('renders with variants prop', () => { + const json = renderJSON( + + + + ) + expect(json).toHaveStyleRule('padding', '32px') + expect(json).toHaveStyleRule('background-color', 'tomato') + expect(json).toHaveStyleRule('color', 'white') + }) + + test('renders with variant and variants prop', () => { + const json = renderJSON( + + + + ) + expect(json).toHaveStyleRule('font-size', '32px') + expect(json).toHaveStyleRule('padding', '32px') + expect(json).toHaveStyleRule('background-color', 'tomato') + expect(json).toHaveStyleRule('color', 'white') + }) + + test('renders with incorrect type of variants prop', () => { + const json = renderJSON( + + + + ) + expect(json).toMatchSnapshot() + }) + test('renders with base styles', () => { const json = renderJSON( { key = key && key.split ? key.split('.') : [key] for (p = 0; p < key.length; p++) { @@ -191,6 +193,12 @@ export const css = args => (props = {}) => { continue } + if (key === 'variants') { + const variants = css(get(theme, val))(theme) + result = { ...result, ...variants } + continue + } + if (val && typeof val === 'object') { result[key] = css(val)(theme) continue diff --git a/packages/css/test/index.js b/packages/css/test/index.js index e1b7fcf04..b6e106f85 100644 --- a/packages/css/test/index.js +++ b/packages/css/test/index.js @@ -230,6 +230,21 @@ test('handles responsive variants', () => { }) }) +test('returns multiple variants from theme', () => { + const result = css({ + variants: ['text.caps', 'text.title'], + })(theme) + expect(result).toEqual({ + fontSize: 16, + letterSpacing: '-0.01em', + textTransform: 'uppercase', + '@media screen and (min-width: 40em)': { + fontSize: 16, + letterSpacing: '-0.02em', + }, + }) +}) + test('handles negative margins from scale', () => { const result = css({ mt: -3, diff --git a/packages/docs/src/pages/components/variants.mdx b/packages/docs/src/pages/components/variants.mdx index 1a7c0828b..6a3aee37b 100644 --- a/packages/docs/src/pages/components/variants.mdx +++ b/packages/docs/src/pages/components/variants.mdx @@ -64,6 +64,17 @@ For example, a secondary button style can be added to `theme.buttons.secondary` ``` +## Combining Multiple Variants + +If you want to combine multiple variants on the same element, pass an array of variants with the `variants` prop. +If styles conflict, those of the variant passed later will render. + +```jsx live=true + + Title text + +``` + ## Example Theme ```js diff --git a/packages/docs/src/pages/guides/variants.mdx b/packages/docs/src/pages/guides/variants.mdx index 3ca36662f..3bd07a615 100644 --- a/packages/docs/src/pages/guides/variants.mdx +++ b/packages/docs/src/pages/guides/variants.mdx @@ -29,6 +29,9 @@ For example, you can define `primary` and `secondary` variants for buttons and u color: 'white', bg: 'secondary', }, + small: { + fontSize: 1 + } }, } ``` @@ -51,6 +54,15 @@ Variants can use any name you choose, and deeply nested objects can be accessed +## Combining Multiple Variants + +If you want to combine multiple variants on the same element, pass an array to `variants` in the `sx` prop. +If styles conflict, those of the variant passed later will render. + +```jsx +