Skip to content

Commit c400c70

Browse files
agriffisgregberge
authored andcommitted
fix: styled.box with rule ordering instead of specificity
1 parent 4754efa commit c400c70

File tree

6 files changed

+106
-45
lines changed

6 files changed

+106
-45
lines changed

packages/emotion/src/createX.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
22
import * as React from 'react'
33
import { Theme } from '@emotion/react'
4-
import styled, { StyledComponent } from '@emotion/styled'
4+
import emStyled, { StyledComponent } from '@emotion/styled'
55
import { compose, StyleGenerator } from '@xstyled/system'
66
import { createShouldForwardProp } from './createShouldForwardProp'
7+
import { styledWithGenerator } from './styled'
78

89
type JSXElementKeys = keyof JSX.IntrinsicElements
910

@@ -20,8 +21,6 @@ export interface X<TProps extends object> extends JSXElements<TProps> {
2021
extend: CreateX
2122
}
2223

23-
const tags = Object.keys(styled) as JSXElementKeys[]
24-
2524
export const createX: CreateX = <TProps extends object>(
2625
generator: StyleGenerator,
2726
) => {
@@ -32,11 +31,9 @@ export const createX: CreateX = <TProps extends object>(
3231

3332
const shouldForwardProp = createShouldForwardProp(generator)
3433

35-
tags.forEach((tag) => {
34+
Object.keys(emStyled).forEach((tag) => {
3635
// @ts-ignore
37-
x[tag] = styled(tag, {
38-
shouldForwardProp,
39-
})<TProps>(() => [`&&{`, generator, `}`])
36+
x[tag] = styledWithGenerator(tag, { shouldForwardProp }, generator)``
4037
})
4138

4239
return x

packages/emotion/src/styled.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,20 @@ describe('#styled.xxxBox', () => {
198198
expect(container.firstChild).toHaveStyle('margin: 4px;')
199199
})
200200

201+
it('overrides styles with props', () => {
202+
const Dummy = styled.box`
203+
margin: 2px;
204+
`
205+
const { container } = render(
206+
<SpaceTheme>
207+
<Dummy m={1} />
208+
</SpaceTheme>,
209+
)
210+
expect(container.firstChild!.nodeName).toBe('DIV')
211+
expect(container.firstChild).toHaveStyle('margin: 4px;')
212+
expect(container.firstChild).not.toHaveStyle('margin: 2px;')
213+
})
214+
201215
it("doesn't forward attributes", () => {
202216
const Dummy = styled.box``
203217
const { container } = render(

packages/emotion/src/styled.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,42 @@
22
import * as React from 'react'
33
import emStyled, { CreateStyledComponent, CreateStyled } from '@emotion/styled'
44
import { Theme } from '@emotion/react'
5-
import { SystemProps } from '@xstyled/system'
5+
import { StyleGenerator, SystemProps, system } from '@xstyled/system'
66
import { BoxElements } from '@xstyled/core'
7+
import { createShouldForwardProp } from './createShouldForwardProp'
78
import { css } from './css'
8-
import { x } from './x'
99

1010
function flattenArgs(arg: any, props: any): any {
1111
if (typeof arg === 'function') return flattenArgs(arg(props), props)
1212
if (Array.isArray(arg)) return arg.map((arg) => flattenArgs(arg, props))
1313
return arg
1414
}
1515

16-
function getCreateStyle(baseCreateStyle: any) {
16+
function getCreateStyle(baseCreateStyle: any, ...generators: StyleGenerator[]) {
1717
return (strings: any, ...args: any) =>
1818
baseCreateStyle((props: any) => {
19-
const flattenedArgs = flattenArgs(args, props)
20-
// @ts-ignore
21-
const result = css(strings, ...flattenedArgs)(props)
22-
return result
19+
let flattenedArgs = flattenArgs(args, props)
20+
21+
// Emotion's css function can receive: template literal (array of
22+
// strings followed by interpolations), style object, or array of style
23+
// objects. Additional generators supplied to getCreateStyle need to be
24+
// interpolated differently depending on which form is called.
25+
if (generators.length) {
26+
if (Array.isArray(strings) && typeof strings[0] === 'string') {
27+
// The tagged template literal should receive an equal number of
28+
// additional separators.
29+
strings = strings.concat(generators.map(() => '\n'))
30+
flattenedArgs = flattenedArgs.concat(flattenArgs(generators, props))
31+
} else if (Array.isArray(strings)) {
32+
// Resolve generators to objects and append to existing array.
33+
strings = strings.concat(flattenArgs(generators, props))
34+
} else {
35+
// Convert single object to array.
36+
strings = [strings].concat(flattenArgs(generators, props))
37+
}
38+
}
39+
40+
return css(strings, ...flattenedArgs)(props)
2341
})
2442
}
2543

@@ -33,15 +51,25 @@ type BoxStyledTags = {
3351
interface CreateXStyled extends CreateStyled, BoxStyledTags {}
3452

3553
// @ts-ignore
36-
export const styled: CreateXStyled = (component: any, options: any) => {
37-
return getCreateStyle(emStyled(component, options))
38-
}
54+
export const styled: CreateXStyled = (component: any, options: any) =>
55+
getCreateStyle(emStyled(component, options))
3956

40-
styled.box = styled(x.div)
57+
// exported for x.* but not for xstyled API
58+
// @ts-ignore
59+
export const styledWithGenerator: CreateXStyled = (
60+
component: any,
61+
options: any,
62+
generator: StyleGenerator,
63+
) => getCreateStyle(emStyled(component, options), generator)
64+
65+
const shouldForwardProp = createShouldForwardProp(system)
66+
67+
// @ts-ignore
68+
styled.box = styledWithGenerator('div', { shouldForwardProp }, system)
4169

4270
Object.keys(emStyled).forEach((key) => {
4371
// @ts-ignore
4472
styled[key] = styled(key)
4573
// @ts-ignore
46-
styled[`${key}Box`] = styled(x[key])
74+
styled[`${key}Box`] = styledWithGenerator(key, { shouldForwardProp }, system)
4775
})

packages/styled-components/src/createX.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
2-
import styled, { StyledComponent, DefaultTheme } from 'styled-components'
2+
import scStyled, { StyledComponent, DefaultTheme } from 'styled-components'
33
import { compose, StyleGenerator } from '@xstyled/system'
44
import { createShouldForwardProp } from './createShouldForwardProp'
5+
import { styledWithGenerator } from './styled'
56

67
type JSXElementKeys = keyof JSX.IntrinsicElements
78

8-
const tags = Object.keys(styled)
9-
109
type SafeIntrinsicComponent<T extends keyof JSX.IntrinsicElements> = (
1110
props: Omit<JSX.IntrinsicElements[T], 'color'>,
1211
) => React.ReactElement<any, T>
@@ -32,13 +31,12 @@ export const createX = <TProps extends object>(generator: StyleGenerator) => {
3231

3332
const shouldForwardProp = createShouldForwardProp(generator)
3433

35-
tags.forEach((tag) => {
34+
Object.keys(scStyled).forEach((tag) => {
3635
// @ts-ignore
37-
x[tag] = styled(tag).withConfig({
36+
x[tag] = styledWithGenerator(tag, generator).withConfig({
3837
// @ts-ignore
3938
shouldForwardProp,
40-
// @ts-ignore
41-
})<TProps>(() => [`&&{`, generator, `}`])
39+
})``
4240
})
4341

4442
return x

packages/styled-components/src/styled.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ describe('#styled.xxxBox', () => {
174174
expect(container.firstChild).toHaveStyle('margin: 1px;')
175175
})
176176

177+
it('overrides styles with props', () => {
178+
const Dummy = styled.box`
179+
margin: 2px;
180+
`
181+
const { container } = render(<Dummy m={1} />)
182+
expect(container.firstChild!.nodeName).toBe('DIV')
183+
expect(container.firstChild).toHaveStyle('margin: 1px;')
184+
expect(container.firstChild).not.toHaveStyle('margin: 2px;')
185+
})
186+
177187
it("doesn't forward attributes", () => {
178188
const Dummy = styled.box``
179189
const { container } = render(<Dummy margin={1} />)
Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
2+
import { BoxElements } from '@xstyled/core'
3+
import { StyleGenerator, system, SystemProps } from '@xstyled/system'
24
import scStyled, {
3-
ThemedStyledFunction,
5+
DefaultTheme,
46
StyledConfig,
57
ThemedBaseStyledInterface,
6-
DefaultTheme,
8+
ThemedStyledFunction,
79
} from 'styled-components'
8-
import { SystemProps } from '@xstyled/system'
9-
import { BoxElements } from '@xstyled/core'
10-
import { x } from './x'
10+
11+
import { createShouldForwardProp } from './createShouldForwardProp'
1112
import { css } from './css'
1213

13-
function getCreateStyle(baseCreateStyle: ThemedStyledFunction<any, any>) {
14-
// @ts-ignore
15-
const createStyle = (...args: any) => baseCreateStyle`${css(...args)}`
16-
createStyle.attrs = (attrs: any) => {
17-
const nextCreateStyle = baseCreateStyle.attrs(attrs)
18-
return getCreateStyle(nextCreateStyle)
19-
}
20-
createStyle.withConfig = (config: StyledConfig<any>) => {
21-
const nextCreateStyle = baseCreateStyle.withConfig(config)
22-
return getCreateStyle(nextCreateStyle)
23-
}
14+
function getCreateStyle(
15+
baseCreateStyle: ThemedStyledFunction<any, any>,
16+
...generators: StyleGenerator[]
17+
) {
18+
const createStyle = (...args: Parameters<typeof css>) =>
19+
// @ts-ignore
20+
baseCreateStyle`${css(...args, ...generators)}`
21+
createStyle.attrs = (attrs: Parameters<typeof baseCreateStyle.attrs>[0]) =>
22+
getCreateStyle(baseCreateStyle.attrs(attrs), ...generators)
23+
createStyle.withConfig = (config: StyledConfig<any>) =>
24+
getCreateStyle(baseCreateStyle.withConfig(config), ...generators)
2425
return createStyle
2526
}
2627

@@ -39,15 +40,28 @@ interface ThemeBaseXStyledInterface<T extends object>
3940
type XStyledInterface = ThemeBaseXStyledInterface<DefaultTheme>
4041

4142
export const styled = <XStyledInterface>(
42-
((component: any) => getCreateStyle(scStyled(component)))
43+
((component: Parameters<typeof scStyled>[0]) =>
44+
getCreateStyle(scStyled(component)))
45+
)
46+
47+
// exported for x.* but not for xstyled API
48+
export const styledWithGenerator = <XStyledInterface>(
49+
((component: Parameters<typeof scStyled>[0], generator: StyleGenerator) =>
50+
getCreateStyle(scStyled(component), generator))
4351
)
4452

53+
const shouldForwardProp = createShouldForwardProp(system)
54+
4555
// @ts-ignore
46-
styled.box = styled(x.div)
56+
styled.box = styledWithGenerator('div', system).withConfig({
57+
shouldForwardProp,
58+
})
4759

4860
Object.keys(scStyled).forEach((key) => {
4961
// @ts-ignore
5062
styled[key] = styled(key)
5163
// @ts-ignore
52-
styled[`${key}Box`] = styled(x[key])
64+
styled[`${key}Box`] = styledWithGenerator(key, system).withConfig({
65+
shouldForwardProp,
66+
})
5367
})

0 commit comments

Comments
 (0)