Skip to content

Commit d41f223

Browse files
committed
feat: replace styled-components for native
1 parent 2a25cc6 commit d41f223

File tree

9 files changed

+587
-0
lines changed

9 files changed

+587
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
2+
import { css as scCss } from 'styled-components/native'
3+
import {
4+
FlattenSimpleInterpolation,
5+
ThemedCssFunction,
6+
} from 'styled-components'
7+
import { StyleGenerator, Theme } from '@xstyled/system'
8+
import { flattenStrings } from '@xstyled/util'
9+
import { createTransform } from '@xstyled/core'
10+
11+
export type XCSSFunction = ThemedCssFunction<Theme>
12+
13+
export const createCssFunction = <TGen extends StyleGenerator>(
14+
generator: TGen,
15+
): XCSSFunction => {
16+
const transform = createTransform(generator)
17+
return ((...args: Parameters<XCSSFunction>) => {
18+
const scCssArgs = scCss(...args)
19+
const flattenedArgs = flattenStrings(scCssArgs as any[])
20+
return flattenedArgs.map(transform) as FlattenSimpleInterpolation
21+
}) as XCSSFunction
22+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
2+
import type { ElementType } from 'react'
3+
import type { ReactNativeThemedStyledFunction } from 'styled-components/native'
4+
import { string } from '@xstyled/util'
5+
import { StyleGenerator, Theme } from '@xstyled/system'
6+
import { StyledConfig } from 'styled-components'
7+
import * as RN from 'react-native'
8+
import { scStyled } from './scStyled'
9+
import { createCssFunction, XCSSFunction } from './createCssFunction'
10+
import type { ReactNativeThemedBaseStyledInterface } from './types'
11+
12+
const getCreateStyle = (
13+
baseCreateStyle: ReactNativeThemedStyledFunction<any, any>,
14+
css: XCSSFunction,
15+
generator?: StyleGenerator,
16+
) => {
17+
const createStyle = (...args: Parameters<typeof css>) =>
18+
// @ts-ignore
19+
baseCreateStyle`${css(...args)}${generator}`
20+
createStyle.attrs = (attrs: Parameters<typeof baseCreateStyle.attrs>[0]) =>
21+
getCreateStyle(baseCreateStyle.attrs(attrs), css, generator)
22+
createStyle.withConfig = (config: StyledConfig<any>) =>
23+
getCreateStyle(baseCreateStyle.withConfig(config), css, generator)
24+
25+
return createStyle
26+
}
27+
28+
export type XStyled = ReactNativeThemedBaseStyledInterface<Theme>
29+
30+
const createShouldForwardProp = (
31+
generator: StyleGenerator,
32+
): ((
33+
prop: string | number | symbol,
34+
defaultValidatorFn: (prop: string | number | symbol) => boolean,
35+
elementToBeCreated?: ElementType,
36+
) => boolean) => {
37+
const propSet = new Set<string>(generator.meta.props)
38+
39+
return (
40+
prop: string | number | symbol,
41+
defaultValidatorFn: (prop: string | number | symbol) => boolean,
42+
elementToBeCreated?: ElementType,
43+
) => {
44+
if (string(prop) && propSet.has(prop)) {
45+
return false
46+
}
47+
if (typeof elementToBeCreated === 'string') {
48+
// We must test elementToBeCreated so we can pass through props for <x.div
49+
// as={Component} />. However elementToBeCreated isn't available until
50+
// styled-components 5.2.4 or 6, and in the meantime will be undefined.
51+
// This means that HTML elements could get unwanted props, but ultimately
52+
// this is a bug in the caller, because why are they passing unwanted
53+
// props?
54+
return defaultValidatorFn(prop)
55+
}
56+
return true
57+
}
58+
}
59+
60+
export const createBaseStyled = <TGen extends StyleGenerator>(
61+
css: XCSSFunction,
62+
generator?: TGen,
63+
): XStyled => {
64+
const config = generator
65+
? {
66+
shouldForwardProp: createShouldForwardProp(generator),
67+
}
68+
: {}
69+
return ((component: Parameters<typeof scStyled>[0]) => {
70+
const baseStyled = scStyled(component)
71+
72+
return getCreateStyle(
73+
config ? baseStyled.withConfig(config) : baseStyled,
74+
css,
75+
generator,
76+
)
77+
}) as XStyled
78+
}
79+
80+
export const createStyled = <TGen extends StyleGenerator>(
81+
generator: TGen,
82+
): XStyled => {
83+
const css = createCssFunction(generator)
84+
// const styled = createBaseStyled(css)
85+
const xstyled = createBaseStyled(css, generator)
86+
87+
Object.keys(scStyled).forEach((tag) => {
88+
Object.defineProperty(xstyled, tag, {
89+
enumerable: true,
90+
configurable: false,
91+
get() {
92+
// @ts-ignore
93+
return xstyled(RN[tag])
94+
},
95+
})
96+
})
97+
98+
return xstyled
99+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
2+
import { StyledComponent } from 'styled-components'
3+
import { DefaultTheme } from 'styled-components/native'
4+
import { scStyled } from './scStyled'
5+
import { StyleGenerator, StyleGeneratorProps } from '@xstyled/system'
6+
import { createBaseStyled } from './createStyled'
7+
import { createCssFunction } from './createCssFunction'
8+
import { NativeElement, NativeElementsKeys } from './types'
9+
import * as RN from 'react-native'
10+
11+
export type X<TGen extends StyleGenerator> = {
12+
[Key in NativeElementsKeys]: StyledComponent<
13+
NativeElement<Key>,
14+
DefaultTheme,
15+
StyleGeneratorProps<TGen>,
16+
'color'
17+
>
18+
}
19+
20+
export const createX = <TGen extends StyleGenerator>(
21+
generator: TGen,
22+
): X<TGen> => {
23+
const xstyled = createBaseStyled(createCssFunction(generator), generator)
24+
25+
const x = {} as X<TGen>
26+
27+
Object.keys(scStyled).forEach((tag) => {
28+
Object.defineProperty(x, tag, {
29+
enumerable: true,
30+
configurable: false,
31+
get() {
32+
// @ts-ignore
33+
return xstyled(RN[tag])``
34+
},
35+
})
36+
})
37+
38+
return x
39+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as React from 'react'
2+
import '@testing-library/jest-native/extend-expect'
3+
import { render, screen } from '@testing-library/react-native'
4+
import { css, ThemeProvider } from '.'
5+
import { scStyled as styled } from './scStyled'
6+
7+
const SpaceTheme = ({ children }: { children: React.ReactNode }) => {
8+
return (
9+
<ThemeProvider theme={{ space: { 1: 4, 2: 8 } }}>{children}</ThemeProvider>
10+
)
11+
}
12+
13+
describe('#css', () => {
14+
it('transforms rules', () => {
15+
const Dummy = styled.View`
16+
${css`
17+
margin: 2;
18+
padding: 1;
19+
margin-top: 2px;
20+
`}
21+
`
22+
render(
23+
<SpaceTheme>
24+
<Dummy testID="dummy" />
25+
</SpaceTheme>,
26+
)
27+
28+
const expectedStyle = {
29+
marginBottom: 8,
30+
marginLeft: 8,
31+
marginRight: 8,
32+
marginTop: 2,
33+
paddingBottom: 4,
34+
paddingLeft: 4,
35+
paddingRight: 4,
36+
paddingTop: 4,
37+
}
38+
39+
expect(screen.getByTestId('dummy')).toHaveStyle(expectedStyle)
40+
})
41+
42+
it('transforms multi values', () => {
43+
const Dummy = styled.View`
44+
${css`
45+
margin: 1 2;
46+
`}
47+
`
48+
render(
49+
<SpaceTheme>
50+
<Dummy testID="dummy" />
51+
</SpaceTheme>,
52+
)
53+
expect(screen.getByTestId('dummy')).toHaveStyle({
54+
marginBottom: 4,
55+
marginTop: 4,
56+
marginLeft: 8,
57+
marginRight: 8,
58+
})
59+
})
60+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defaultTheme } from './defaultTheme'
2+
export {
3+
isStyledComponent,
4+
ThemeConsumer,
5+
ThemeContext,
6+
ThemeProvider,
7+
withTheme,
8+
} from 'styled-components/native'
9+
export * from './theme'
10+
export * from '@xstyled/system'
11+
export * from './create'
12+
13+
// Create and export default system
14+
import { system } from '@xstyled/system'
15+
import { createCss } from './create'
16+
17+
const { css, styled, x } = createCss(system)
18+
export { css, styled, styled as default, x, defaultTheme }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import styled, {
2+
DefaultTheme,
3+
ReactNativeStyledInterface,
4+
} from 'styled-components/native'
5+
6+
// Provide interop since `styled-components` does not work out of the box with ESM
7+
export const scStyled: ReactNativeStyledInterface<DefaultTheme> =
8+
// @ts-ignore
9+
typeof styled === 'function' ? styled : styled.default

0 commit comments

Comments
 (0)