Skip to content

Commit

Permalink
fix: styled.box with rule ordering instead of specificity
Browse files Browse the repository at this point in the history
  • Loading branch information
agriffis authored and gregberge committed Apr 25, 2021
1 parent 30030c6 commit 09c543d
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 45 deletions.
11 changes: 4 additions & 7 deletions packages/emotion/src/createX.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
import * as React from 'react'
import { Theme } from '@emotion/react'
import styled, { StyledComponent } from '@emotion/styled'
import emStyled, { StyledComponent } from '@emotion/styled'
import { compose, StyleGenerator } from '@xstyled/system'
import { createShouldForwardProp } from './createShouldForwardProp'
import { styledWithGenerator } from './styled'

type JSXElementKeys = keyof JSX.IntrinsicElements

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

const tags = Object.keys(styled) as JSXElementKeys[]

export const createX: CreateX = <TProps extends object>(
generator: StyleGenerator,
) => {
Expand All @@ -32,11 +31,9 @@ export const createX: CreateX = <TProps extends object>(

const shouldForwardProp = createShouldForwardProp(generator)

tags.forEach((tag) => {
Object.keys(emStyled).forEach((tag) => {
// @ts-ignore
x[tag] = styled(tag, {
shouldForwardProp,
})<TProps>(() => [`&&{`, generator, `}`])
x[tag] = styledWithGenerator(tag, { shouldForwardProp }, generator)``
})

return x
Expand Down
14 changes: 14 additions & 0 deletions packages/emotion/src/styled.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ describe('#styled.xxxBox', () => {
expect(container.firstChild).toHaveStyle('margin: 4px;')
})

it('overrides styles with props', () => {
const Dummy = styled.box`
margin: 2px;
`
const { container } = render(
<SpaceTheme>
<Dummy m={1} />
</SpaceTheme>,
)
expect(container.firstChild!.nodeName).toBe('DIV')
expect(container.firstChild).toHaveStyle('margin: 4px;')
expect(container.firstChild).not.toHaveStyle('margin: 2px;')
})

it("doesn't forward attributes", () => {
const Dummy = styled.box``
const { container } = render(
Expand Down
52 changes: 40 additions & 12 deletions packages/emotion/src/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,42 @@
import * as React from 'react'
import emStyled, { CreateStyledComponent, CreateStyled } from '@emotion/styled'
import { Theme } from '@emotion/react'
import { SystemProps } from '@xstyled/system'
import { StyleGenerator, SystemProps, system } from '@xstyled/system'
import { BoxElements } from '@xstyled/core'
import { createShouldForwardProp } from './createShouldForwardProp'
import { css } from './css'
import { x } from './x'

function flattenArgs(arg: any, props: any): any {
if (typeof arg === 'function') return flattenArgs(arg(props), props)
if (Array.isArray(arg)) return arg.map((arg) => flattenArgs(arg, props))
return arg
}

function getCreateStyle(baseCreateStyle: any) {
function getCreateStyle(baseCreateStyle: any, ...generators: StyleGenerator[]) {
return (strings: any, ...args: any) =>
baseCreateStyle((props: any) => {
const flattenedArgs = flattenArgs(args, props)
// @ts-ignore
const result = css(strings, ...flattenedArgs)(props)
return result
let flattenedArgs = flattenArgs(args, props)

// Emotion's css function can receive: template literal (array of
// strings followed by interpolations), style object, or array of style
// objects. Additional generators supplied to getCreateStyle need to be
// interpolated differently depending on which form is called.
if (generators.length) {
if (Array.isArray(strings) && typeof strings[0] === 'string') {
// The tagged template literal should receive an equal number of
// additional separators.
strings = strings.concat(generators.map(() => '\n'))
flattenedArgs = flattenedArgs.concat(flattenArgs(generators, props))
} else if (Array.isArray(strings)) {
// Resolve generators to objects and append to existing array.
strings = strings.concat(flattenArgs(generators, props))
} else {
// Convert single object to array.
strings = [strings].concat(flattenArgs(generators, props))
}
}

return css(strings, ...flattenedArgs)(props)
})
}

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

// @ts-ignore
export const styled: CreateXStyled = (component: any, options: any) => {
return getCreateStyle(emStyled(component, options))
}
export const styled: CreateXStyled = (component: any, options: any) =>
getCreateStyle(emStyled(component, options))

styled.box = styled(x.div)
// exported for x.* but not for xstyled API
// @ts-ignore
export const styledWithGenerator: CreateXStyled = (
component: any,
options: any,
generator: StyleGenerator,
) => getCreateStyle(emStyled(component, options), generator)

const shouldForwardProp = createShouldForwardProp(system)

// @ts-ignore
styled.box = styledWithGenerator('div', { shouldForwardProp }, system)

Object.keys(emStyled).forEach((key) => {
// @ts-ignore
styled[key] = styled(key)
// @ts-ignore
styled[`${key}Box`] = styled(x[key])
styled[`${key}Box`] = styledWithGenerator(key, { shouldForwardProp }, system)
})
12 changes: 5 additions & 7 deletions packages/styled-components/src/createX.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
import styled, { StyledComponent, DefaultTheme } from 'styled-components'
import scStyled, { StyledComponent, DefaultTheme } from 'styled-components'
import { compose, StyleGenerator } from '@xstyled/system'
import { createShouldForwardProp } from './createShouldForwardProp'
import { styledWithGenerator } from './styled'

type JSXElementKeys = keyof JSX.IntrinsicElements

const tags = Object.keys(styled)

type SafeIntrinsicComponent<T extends keyof JSX.IntrinsicElements> = (
props: Omit<JSX.IntrinsicElements[T], 'color'>,
) => React.ReactElement<any, T>
Expand All @@ -32,12 +31,11 @@ export const createX = <TProps extends object>(generator: StyleGenerator) => {

const shouldForwardProp = createShouldForwardProp(generator)

tags.forEach((tag) => {
Object.keys(scStyled).forEach((tag) => {
// @ts-ignore
x[tag] = styled(tag).withConfig({
x[tag] = styledWithGenerator(tag, generator).withConfig({
shouldForwardProp,
// @ts-ignore
})<TProps>(() => [`&&{`, generator, `}`])
})``
})

return x
Expand Down
10 changes: 10 additions & 0 deletions packages/styled-components/src/styled.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,16 @@ describe('#styled.xxxBox', () => {
expect(container.firstChild).toHaveStyle('margin: 1px;')
})

it('overrides styles with props', () => {
const Dummy = styled.box`
margin: 2px;
`
const { container } = render(<Dummy m={1} />)
expect(container.firstChild!.nodeName).toBe('DIV')
expect(container.firstChild).toHaveStyle('margin: 1px;')
expect(container.firstChild).not.toHaveStyle('margin: 2px;')
})

it("doesn't forward attributes", () => {
const Dummy = styled.box``
const { container } = render(<Dummy margin={1} />)
Expand Down
52 changes: 33 additions & 19 deletions packages/styled-components/src/styled.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
import { BoxElements } from '@xstyled/core'
import { StyleGenerator, system, SystemProps } from '@xstyled/system'
import scStyled, {
ThemedStyledFunction,
DefaultTheme,
StyledConfig,
ThemedBaseStyledInterface,
DefaultTheme,
ThemedStyledFunction,
} from 'styled-components'
import { SystemProps } from '@xstyled/system'
import { BoxElements } from '@xstyled/core'
import { x } from './x'

import { createShouldForwardProp } from './createShouldForwardProp'
import { css } from './css'

function getCreateStyle(baseCreateStyle: ThemedStyledFunction<any, any>) {
// @ts-ignore
const createStyle = (...args: any) => baseCreateStyle`${css(...args)}`
createStyle.attrs = (attrs: any) => {
const nextCreateStyle = baseCreateStyle.attrs(attrs)
return getCreateStyle(nextCreateStyle)
}
createStyle.withConfig = (config: StyledConfig<any>) => {
const nextCreateStyle = baseCreateStyle.withConfig(config)
return getCreateStyle(nextCreateStyle)
}
function getCreateStyle(
baseCreateStyle: ThemedStyledFunction<any, any>,
...generators: StyleGenerator[]
) {
const createStyle = (...args: Parameters<typeof css>) =>
// @ts-ignore
baseCreateStyle`${css(...args, ...generators)}`
createStyle.attrs = (attrs: Parameters<typeof baseCreateStyle.attrs>[0]) =>
getCreateStyle(baseCreateStyle.attrs(attrs), ...generators)
createStyle.withConfig = (config: StyledConfig<any>) =>
getCreateStyle(baseCreateStyle.withConfig(config), ...generators)
return createStyle
}

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

export const styled = <XStyledInterface>(
((component: any) => getCreateStyle(scStyled(component)))
((component: Parameters<typeof scStyled>[0]) =>
getCreateStyle(scStyled(component)))
)

// exported for x.* but not for xstyled API
export const styledWithGenerator = <XStyledInterface>(
((component: Parameters<typeof scStyled>[0], generator: StyleGenerator) =>
getCreateStyle(scStyled(component), generator))
)

const shouldForwardProp = createShouldForwardProp(system)

// @ts-ignore
styled.box = styled(x.div)
styled.box = styledWithGenerator('div', system).withConfig({
shouldForwardProp,
})

Object.keys(scStyled).forEach((key) => {
// @ts-ignore
styled[key] = styled(key)
// @ts-ignore
styled[`${key}Box`] = styled(x[key])
styled[`${key}Box`] = styledWithGenerator(key, system).withConfig({
shouldForwardProp,
})
})

0 comments on commit 09c543d

Please sign in to comment.