Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compose styles with arrays in sx prop #704

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ 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 styles = css(props.sx)(theme)
const sx = flattenDeep(props.sx)
const styles = sx.map(s => css(s)(theme))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jxnblk Do you have a preferred method for recursively flattening arrays? I realized after I opened the PR that Emotion recursively flattens arrays of styles and this doesn't account for that scenario.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are merge utilities in this file (see line 66)—is that what you’re looking for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lachlanjc Thanks for the suggestion. Looks like the merge utility didn't have what i was looking for. I update a PR to do the flattening though.

const raw = typeof props.css === 'function' ? props.css(theme) : props.css
return [styles, raw]
return [...styles, raw]
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down
72 changes: 51 additions & 21 deletions packages/core/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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')

})
})

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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'))
Expand Down Expand Up @@ -338,7 +369,7 @@ describe('useThemeUI', () => {
theme={{
colors: {
text: 'tomato',
}
},
}}>
<GetContext />
</ThemeProvider>
Expand All @@ -347,4 +378,3 @@ describe('useThemeUI', () => {
expect(context.theme.colors.text).toBe('tomato')
})
})

30 changes: 30 additions & 0 deletions packages/docs/src/pages/sx-prop.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => (
<div>
<div sx={base}>This will be the primary color</div>
<div sx={[danger, base]}>
This will be also be primary since the base styles
overwrite the danger styles.
</div>
<div sx={[base, danger]}>This will be red</div>
</div>
)
```

## Functional Values

For shorthand CSS properties or ones that are not automatically mapped to values in the theme,
Expand Down