From 46d3b6eaf6c77578b1c4df112fe2eea099fe81a2 Mon Sep 17 00:00:00 2001 From: Jonathan Avila Date: Sat, 22 Feb 2020 11:08:51 -0500 Subject: [PATCH 1/4] Accept arrays in sx prop for styles composition --- packages/core/src/index.ts | 5 +++-- packages/docs/src/pages/sx-prop.mdx | 30 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6601f9eff..74761693a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,9 +16,10 @@ export * from './types' const getCSS = props => { if (!props.sx && !props.css) return undefined return theme => { - const styles = css(props.sx)(theme) + const sx = Array.isArray(props.sx) ? props.sx : [props.sx] + const styles = sx.map(s => css(s)(theme)) const raw = typeof props.css === 'function' ? props.css(theme) : props.css - return [styles, raw] + return [...styles, raw] } } diff --git a/packages/docs/src/pages/sx-prop.mdx b/packages/docs/src/pages/sx-prop.mdx index 7feb7878e..f7fb3329b 100644 --- a/packages/docs/src/pages/sx-prop.mdx +++ b/packages/docs/src/pages/sx-prop.mdx @@ -143,6 +143,36 @@ In addition to shorthands for applying margin and padding on the x- and y-axes, /> ``` +## Composition + +Styles can be composed by using an array of style objects in the `sx` prop. +This works the same way as [Emotion](https://emotion.sh/docs/composition). + +```jsx +import React from 'react' +import { css } from 'theme-ui' + +const base { + backgroundColor: 'background', + color: 'primary' +} + +const danger = { + color: 'red' +} + +export default props => ( +
+
This will be the primary color
+
+ This will be also be primary since the base styles + overwrite the danger styles. +
+
This will be red
+
+) +``` + ## Functional Values For shorthand CSS properties or ones that are not automatically mapped to values in the theme, From fba931dd33225c16e91b57a53003eab674bd7b28 Mon Sep 17 00:00:00 2001 From: Jonathan Avila Date: Sat, 22 Feb 2020 13:10:13 -0500 Subject: [PATCH 2/4] add test for styles composition with array in sx prop --- packages/core/test/index.js | 72 ++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/packages/core/test/index.js b/packages/core/test/index.js index 1eac756e8..30b713cf1 100644 --- a/packages/core/test/index.js +++ b/packages/core/test/index.js @@ -4,13 +4,7 @@ import renderer from 'react-test-renderer' import { render, fireEvent, cleanup, act } from '@testing-library/react' import { matchers } from 'jest-emotion' import mockConsole from 'jest-mock-console' -import { - jsx, - Context, - useThemeUI, - merge, - ThemeProvider, -} from '../src' +import { jsx, Context, useThemeUI, merge, ThemeProvider } from '../src' afterEach(cleanup) @@ -104,20 +98,21 @@ describe('ThemeProvider', () => { cards: { default: { border: t => `1px solid ${t.colors.primary}`, - } - } + }, + }, } const json = renderJSON( - jsx(ThemeProvider, { theme }, + jsx( + ThemeProvider, + { theme }, jsx('div', { sx: { variant: 'cards.default', - } + }, }) ) ) expect(json).toHaveStyleRule('border', '1px solid tomato') - }) }) @@ -151,13 +146,15 @@ describe('jsx', () => { test('css prop accepts functions', () => { const json = renderJSON( - jsx(ThemeProvider, { - theme: { - colors: { - primary: 'tomato', - } - } - }, + jsx( + ThemeProvider, + { + theme: { + colors: { + primary: 'tomato', + }, + }, + }, jsx('div', { css: t => ({ color: t.colors.primary, @@ -216,6 +213,40 @@ describe('jsx', () => { expect(json).toHaveStyleRule('color', '#07c') }) + test('accepts array in sx prop', () => { + const json = renderJSON( + jsx( + ThemeProvider, + { + theme: { + colors: { + primary: 'tomato', + blue: '#07c', + }, + }, + }, + jsx('div', { + sx: [ + { + mx: 2, + p: 2, + bg: 'primary', + }, + { + mx: 'auto', + position: 'absolute', + bg: 'blue', + }, + ], + }) + ) + ) + expect(json).toHaveStyleRule('margin-left', 'auto') + expect(json).toHaveStyleRule('padding', '8px') + expect(json).toHaveStyleRule('background-color', '#07c') + expect(json).toHaveStyleRule('position', 'absolute') + }) + test('does not add css prop when not provided', () => { jest.spyOn(global.console, 'warn') const json = renderJSON(jsx(React.Fragment, null, 'hi')) @@ -338,7 +369,7 @@ describe('useThemeUI', () => { theme={{ colors: { text: 'tomato', - } + }, }}> @@ -347,4 +378,3 @@ describe('useThemeUI', () => { expect(context.theme.colors.text).toBe('tomato') }) }) - From 8f9a74abdfc20355e0d1284614e449f767922a3f Mon Sep 17 00:00:00 2001 From: Jonathan Avila Date: Sat, 22 Feb 2020 13:24:18 -0500 Subject: [PATCH 3/4] Update types to accept arrays in sx prop --- packages/core/src/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c881cede0..abbd12f04 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,11 +1,11 @@ import { SystemStyleObject } from '@theme-ui/css' /** - * The `sx` prop accepts a `SxStyleProp` object and properties that are part of - * the `Theme` will be transformed to their corresponding values. Other valid - * CSS properties are also allowed. + * The `sx` prop accepts a `SxStyleProp` object or an array of `SxStyleProp` + * objects and properties that are part of the `Theme` will be transformed to + * their corresponding values. Other valid CSS properties are also allowed. */ -export type SxStyleProp = SystemStyleObject +export type SxStyleProp = SystemStyleObject | SystemStyleObject[] export interface SxProps { /** From 9d85eb35d033f62df1fdc89975dc5b4c91ee3141 Mon Sep 17 00:00:00 2001 From: Jonathan Avila Date: Tue, 3 Mar 2020 22:40:58 -0500 Subject: [PATCH 4/4] Recursively flatten array of styles --- packages/core/src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 74761693a..6a4915ec1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -13,10 +13,15 @@ import './react-jsx' export * from './types' +const flattenDeep = arr => + Array.isArray(arr) + ? arr.reduce((a, b) => a.concat(flattenDeep(b)), []) + : [arr] + const getCSS = props => { if (!props.sx && !props.css) return undefined return theme => { - const sx = Array.isArray(props.sx) ? props.sx : [props.sx] + const sx = flattenDeep(props.sx) const styles = sx.map(s => css(s)(theme)) const raw = typeof props.css === 'function' ? props.css(theme) : props.css return [...styles, raw]