Initial library setup
This commit is contained in:
@@ -0,0 +1,102 @@
|
|||||||
|
import {View, Text as RNText} from 'react-native'
|
||||||
|
|
||||||
|
import {createSystem} from './lib/system'
|
||||||
|
import {light, dark} from './themes'
|
||||||
|
import {web} from './lib/utils'
|
||||||
|
|
||||||
|
export * from './lib/utils'
|
||||||
|
|
||||||
|
const {
|
||||||
|
ThemeProvider,
|
||||||
|
useTheme,
|
||||||
|
useTokens,
|
||||||
|
useBreakpoints,
|
||||||
|
useStyle,
|
||||||
|
useStyles,
|
||||||
|
styled,
|
||||||
|
} = createSystem({
|
||||||
|
light,
|
||||||
|
dark,
|
||||||
|
})
|
||||||
|
|
||||||
|
export {
|
||||||
|
ThemeProvider,
|
||||||
|
useTheme,
|
||||||
|
useTokens,
|
||||||
|
useBreakpoints,
|
||||||
|
useStyle,
|
||||||
|
useStyles,
|
||||||
|
styled,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Box = styled(View)
|
||||||
|
export const Text = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 's',
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://necolas.github.io/react-native-web/docs/accessibility/#semantic-html
|
||||||
|
* @see https://docs.expo.dev/develop/user-interface/fonts/
|
||||||
|
*/
|
||||||
|
export const H1 = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 'l',
|
||||||
|
gtMobile: {
|
||||||
|
fontSize: 'xl',
|
||||||
|
},
|
||||||
|
...web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 1,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const H2 = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 'm',
|
||||||
|
gtMobile: {
|
||||||
|
fontSize: 'l',
|
||||||
|
},
|
||||||
|
...web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 2,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const H3 = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 'm',
|
||||||
|
...web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 3,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const H4 = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 's',
|
||||||
|
...web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 4,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const H5 = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 'xs',
|
||||||
|
...web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 5,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const H6 = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 'xxs',
|
||||||
|
...web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 6,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
export const P = styled(RNText, {
|
||||||
|
color: 'l8',
|
||||||
|
fontSize: 's',
|
||||||
|
...web({
|
||||||
|
role: 'paragraph',
|
||||||
|
}),
|
||||||
|
})
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import type {ViewProps} from 'react-native'
|
||||||
|
import TestRenderer from 'react-test-renderer'
|
||||||
|
import {jest, test, expect} from '@jest/globals'
|
||||||
|
|
||||||
|
import {createTheme} from '../theme'
|
||||||
|
import {createSystem} from '../system'
|
||||||
|
|
||||||
|
jest.mock('react-native', () => ({
|
||||||
|
Dimensions: {
|
||||||
|
get: () => ({width: 0, height: 0}),
|
||||||
|
addEventListener() {},
|
||||||
|
},
|
||||||
|
StyleSheet: {
|
||||||
|
create(style: any) {
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const theme = createTheme({
|
||||||
|
tokens: {
|
||||||
|
space: {
|
||||||
|
s: 4,
|
||||||
|
m: 8,
|
||||||
|
l: 16,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
theme: 'tomato',
|
||||||
|
text: 'black',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
px: ['paddingLeft', 'paddingRight'],
|
||||||
|
},
|
||||||
|
macros: {
|
||||||
|
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
gtPhone: 640,
|
||||||
|
gtTablet: 1024,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {ThemeProvider, useTheme, useStyle, useStyles, styled} = createSystem({
|
||||||
|
theme,
|
||||||
|
})
|
||||||
|
|
||||||
|
function Root(props: React.PropsWithChildren<{}>) {
|
||||||
|
return <ThemeProvider theme="theme">{props.children}</ThemeProvider>
|
||||||
|
}
|
||||||
|
|
||||||
|
test('useTheme', () => {
|
||||||
|
function Component() {
|
||||||
|
const theme = useTheme()
|
||||||
|
return <>{JSON.stringify(theme)}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
const dom = TestRenderer.create(
|
||||||
|
<Root>
|
||||||
|
<Component />
|
||||||
|
</Root>,
|
||||||
|
)
|
||||||
|
const json = JSON.parse(dom.toJSON() as unknown as string)
|
||||||
|
|
||||||
|
expect(json.themeName).toEqual('theme')
|
||||||
|
expect(json.theme).toBeTruthy()
|
||||||
|
expect(Object.keys(json.themes)).toEqual(['theme'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('useStyle', () => {
|
||||||
|
function Component() {
|
||||||
|
const style = useStyle({color: 'red'})
|
||||||
|
return <>{JSON.stringify(style)}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
const dom = TestRenderer.create(
|
||||||
|
<Root>
|
||||||
|
<Component />
|
||||||
|
</Root>,
|
||||||
|
)
|
||||||
|
const json = JSON.parse(dom.toJSON() as unknown as string)
|
||||||
|
|
||||||
|
expect(json.color).toEqual('red')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('useStyles', () => {
|
||||||
|
function Component() {
|
||||||
|
const {text} = useStyles({
|
||||||
|
text: {color: 'red'},
|
||||||
|
})
|
||||||
|
return <>{JSON.stringify(text)}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
const dom = TestRenderer.create(
|
||||||
|
<Root>
|
||||||
|
<Component />
|
||||||
|
</Root>,
|
||||||
|
)
|
||||||
|
const json = JSON.parse(dom.toJSON() as unknown as string)
|
||||||
|
|
||||||
|
expect(json.color).toEqual('red')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('styled', () => {
|
||||||
|
function Component(props: ViewProps) {
|
||||||
|
return <>{JSON.stringify(props)}</>
|
||||||
|
}
|
||||||
|
const Box = styled(Component, {
|
||||||
|
color: 'theme',
|
||||||
|
})
|
||||||
|
|
||||||
|
const dom = TestRenderer.create(
|
||||||
|
<Root>
|
||||||
|
<Box color="red" id="foo" style={{padding: 10}} />
|
||||||
|
</Root>,
|
||||||
|
)
|
||||||
|
const json = JSON.parse(dom.toJSON() as unknown as string)
|
||||||
|
|
||||||
|
expect(json.style).toEqual([{color: 'red'}, {padding: 10}])
|
||||||
|
expect(json.id).toEqual('foo')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('types', () => {
|
||||||
|
function Component(props: ViewProps & {foo: boolean}) {
|
||||||
|
// @ts-expect-error
|
||||||
|
const {foo} = useStyles({
|
||||||
|
text: {color: 'red'},
|
||||||
|
})
|
||||||
|
// just need a way to use `foo`
|
||||||
|
return <React.Fragment key={foo}>{JSON.stringify(props)}</React.Fragment>
|
||||||
|
}
|
||||||
|
const Box = styled(Component, {
|
||||||
|
id: true,
|
||||||
|
// @ts-expect-error
|
||||||
|
color: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
TestRenderer.create(
|
||||||
|
<Root>
|
||||||
|
{/* @ts-expect-error */}
|
||||||
|
<Box color="red" id="foo" style={{padding: 10}}>
|
||||||
|
<Component foo />
|
||||||
|
</Box>
|
||||||
|
</Root>,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
import type {ViewProps} from 'react-native'
|
||||||
|
import {describe, test, expect} from '@jest/globals'
|
||||||
|
|
||||||
|
import {createTheme} from '../theme'
|
||||||
|
|
||||||
|
const theme = createTheme({
|
||||||
|
tokens: {
|
||||||
|
space: {
|
||||||
|
s: 4,
|
||||||
|
m: 8,
|
||||||
|
l: 16,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
theme: 'tomato',
|
||||||
|
text: 'black',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
px: ['paddingLeft', 'paddingRight'],
|
||||||
|
},
|
||||||
|
macros: {
|
||||||
|
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
gtPhone: 640,
|
||||||
|
gtTablet: 1024,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('non-style props', () => {
|
||||||
|
test('e.g. className', () => {
|
||||||
|
const {styles, props} = theme.style<ViewProps>(
|
||||||
|
{
|
||||||
|
id: 'foo',
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({})
|
||||||
|
expect(props).toEqual({
|
||||||
|
id: 'foo',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('properties', () => {
|
||||||
|
test('standard', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
color: 'blue',
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
color: 'blue',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
px: 20,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
paddingLeft: 20,
|
||||||
|
paddingRight: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('undefined values', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
px: undefined,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('tokens', () => {
|
||||||
|
test('match', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
color: 'theme',
|
||||||
|
paddingTop: 's',
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
color: 'tomato',
|
||||||
|
paddingTop: 4,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('no match', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
color: 'blue',
|
||||||
|
paddingTop: 20,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
color: 'blue',
|
||||||
|
paddingTop: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('macros', () => {
|
||||||
|
test('truthy', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
caps: true,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('falsy', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
caps: false,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('breakpoints', () => {
|
||||||
|
test('applies', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
paddingTop: 's',
|
||||||
|
gtPhone: {
|
||||||
|
paddingTop: 'm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
['gtPhone'],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
paddingTop: 8,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('not applies', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
paddingTop: 's',
|
||||||
|
gtPhone: {
|
||||||
|
paddingTop: 'm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
paddingTop: 4,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('applies in order, asc', () => {
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
paddingTop: 's',
|
||||||
|
gtPhone: {
|
||||||
|
paddingTop: 'm',
|
||||||
|
},
|
||||||
|
gtTablet: {
|
||||||
|
paddingTop: 'l',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
['gtTablet', 'gtPhone'],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
paddingTop: 16,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('applies in order even if defined in reverse', () => {
|
||||||
|
const theme = createTheme({
|
||||||
|
tokens: {
|
||||||
|
space: {
|
||||||
|
s: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
// reverse order
|
||||||
|
gtTablet: 1024,
|
||||||
|
gtPhone: 640,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {styles} = theme.style(
|
||||||
|
{
|
||||||
|
paddingTop: 20,
|
||||||
|
gtTablet: {
|
||||||
|
paddingTop: 30,
|
||||||
|
},
|
||||||
|
gtPhone: {
|
||||||
|
paddingTop: 25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
['gtTablet', 'gtPhone'],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(styles).toEqual({
|
||||||
|
paddingTop: 30,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('utils', () => {
|
||||||
|
test('getActiveBreakpoints', () => {
|
||||||
|
const breakpoints = theme.getActiveBreakpoints({width: 1000})
|
||||||
|
expect(breakpoints).toEqual({
|
||||||
|
current: 'gtPhone',
|
||||||
|
active: ['gtPhone'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('types', () => {
|
||||||
|
const theme = createTheme({
|
||||||
|
tokens: {
|
||||||
|
space: {
|
||||||
|
s: 4,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
theme: 'tomato',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
px: ['paddingLeft', 'paddingRight'],
|
||||||
|
},
|
||||||
|
macros: {
|
||||||
|
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
gtPhone: 640,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
test('style', () => {
|
||||||
|
theme.style(
|
||||||
|
{
|
||||||
|
// @ts-expect-error
|
||||||
|
color: false,
|
||||||
|
px: 20,
|
||||||
|
// @ts-expect-error
|
||||||
|
caps: 1,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
theme.style(
|
||||||
|
{},
|
||||||
|
// @ts-expect-error
|
||||||
|
['taco'],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {test, expect} from '@jest/globals'
|
||||||
|
|
||||||
|
import * as utils from '../utils'
|
||||||
|
|
||||||
|
test(`web`, async () => {
|
||||||
|
const result = utils.web('foo')
|
||||||
|
expect(result).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`ios`, async () => {
|
||||||
|
const result = utils.ios('foo')
|
||||||
|
// TODO will fail in CI
|
||||||
|
expect(result).toBe('foo')
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`android`, async () => {
|
||||||
|
const result = utils.android('foo')
|
||||||
|
expect(result).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`native`, async () => {
|
||||||
|
const result = utils.native('foo')
|
||||||
|
expect(result).toBe('foo')
|
||||||
|
})
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {Dimensions} from 'react-native'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Theme,
|
||||||
|
ResponsiveStyles,
|
||||||
|
Tokens,
|
||||||
|
Properties,
|
||||||
|
Macros,
|
||||||
|
Breakpoints,
|
||||||
|
} from './theme'
|
||||||
|
|
||||||
|
type ThemeConfig<
|
||||||
|
T extends Tokens,
|
||||||
|
P extends Properties,
|
||||||
|
M extends Macros<T>,
|
||||||
|
B extends Breakpoints,
|
||||||
|
> = {
|
||||||
|
[key: string]: Theme<T, P, M, B>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSystem<
|
||||||
|
T extends Tokens,
|
||||||
|
P extends Properties,
|
||||||
|
M extends Macros<T>,
|
||||||
|
B extends Breakpoints,
|
||||||
|
>(themes: ThemeConfig<T, P, M, B>) {
|
||||||
|
const defaultTheme = Object.values(themes)[0]
|
||||||
|
|
||||||
|
type SystemTheme = Theme<T, P, M, B>
|
||||||
|
type ResponsiveStyleProps = ResponsiveStyles<T, P, M, B>
|
||||||
|
type StylesAndProps = Parameters<typeof defaultTheme.style<any>>[0]
|
||||||
|
type DebugProps = {
|
||||||
|
/**
|
||||||
|
* Debug mode will log the styles and props to the console
|
||||||
|
*/
|
||||||
|
debug?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeName = keyof typeof themes
|
||||||
|
const Context = React.createContext<{
|
||||||
|
themeName: ThemeName
|
||||||
|
theme: SystemTheme
|
||||||
|
themes: {
|
||||||
|
[key in ThemeName]: SystemTheme
|
||||||
|
}
|
||||||
|
}>({
|
||||||
|
themeName: Object.keys(themes)[0],
|
||||||
|
theme: defaultTheme,
|
||||||
|
themes,
|
||||||
|
})
|
||||||
|
|
||||||
|
const ThemeProvider = ({
|
||||||
|
children,
|
||||||
|
theme,
|
||||||
|
}: React.PropsWithChildren<{theme: ThemeName}>) => (
|
||||||
|
<Context.Provider
|
||||||
|
value={{
|
||||||
|
themeName: theme,
|
||||||
|
theme: themes[theme],
|
||||||
|
themes,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
|
||||||
|
function useTheme() {
|
||||||
|
return React.useContext(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTokens() {
|
||||||
|
return React.useContext(Context).theme.config.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
function useBreakpoints() {
|
||||||
|
const {theme} = useTheme()
|
||||||
|
const [breakpoints, setBreakpoints] = React.useState(
|
||||||
|
theme.getActiveBreakpoints({width: Dimensions.get('window').width}),
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const listener = Dimensions.addEventListener('change', ({window}) => {
|
||||||
|
const bp = theme.getActiveBreakpoints({width: window.width})
|
||||||
|
if (bp.current !== breakpoints.current) setBreakpoints(bp)
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
listener.remove()
|
||||||
|
}
|
||||||
|
}, [breakpoints, theme])
|
||||||
|
|
||||||
|
return breakpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
function useStyle(props: ResponsiveStyleProps) {
|
||||||
|
const {theme} = useTheme()
|
||||||
|
const breakpoints = useBreakpoints()
|
||||||
|
return theme.style(props as StylesAndProps, breakpoints.active).styles
|
||||||
|
}
|
||||||
|
|
||||||
|
function useStyles<O extends Record<string, ResponsiveStyleProps>>(
|
||||||
|
styles: O,
|
||||||
|
): {
|
||||||
|
[Name in keyof O]: ReturnType<SystemTheme['style']>['styles']
|
||||||
|
} {
|
||||||
|
const {theme} = useTheme()
|
||||||
|
const breakpoints = useBreakpoints()
|
||||||
|
return React.useMemo(() => {
|
||||||
|
return Object.entries(styles).reduce((acc, [key, style]) => {
|
||||||
|
acc[key as keyof O] = theme.style(
|
||||||
|
style as StylesAndProps,
|
||||||
|
breakpoints.active,
|
||||||
|
).styles
|
||||||
|
return acc
|
||||||
|
}, {} as {[Name in keyof O]: ReturnType<SystemTheme['style']>['styles']})
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [styles, breakpoints.current, theme])
|
||||||
|
}
|
||||||
|
|
||||||
|
function styled<Props extends Record<string, any>>(
|
||||||
|
Component: React.ComponentType<Props>,
|
||||||
|
defaultProps: ResponsiveStyleProps = {},
|
||||||
|
) {
|
||||||
|
const comp = React.forwardRef<
|
||||||
|
Props,
|
||||||
|
Props &
|
||||||
|
ResponsiveStyleProps &
|
||||||
|
DebugProps & {children?: React.ReactNode | React.ReactNodeArray}
|
||||||
|
>((props, ref) => {
|
||||||
|
const {theme} = useTheme()
|
||||||
|
const breakpoints = useBreakpoints()
|
||||||
|
const {styles, props: rest} = theme.style<Props>(
|
||||||
|
{
|
||||||
|
...defaultProps,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
breakpoints.active,
|
||||||
|
)
|
||||||
|
if (props.debug) console.log({styles, props: rest})
|
||||||
|
return <Component {...rest} style={[styles, rest.style]} ref={ref} />
|
||||||
|
})
|
||||||
|
|
||||||
|
comp.displayName = Component.displayName || 'NovaComponent'
|
||||||
|
|
||||||
|
return comp
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
Context,
|
||||||
|
ThemeProvider,
|
||||||
|
useTheme,
|
||||||
|
useTokens,
|
||||||
|
useBreakpoints,
|
||||||
|
useStyle,
|
||||||
|
useStyles,
|
||||||
|
styled,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,394 @@
|
|||||||
|
import type {
|
||||||
|
ViewStyle,
|
||||||
|
TextStyle,
|
||||||
|
ImageStyle,
|
||||||
|
DimensionValue,
|
||||||
|
ColorValue,
|
||||||
|
} from 'react-native'
|
||||||
|
import {StyleSheet} from 'react-native'
|
||||||
|
|
||||||
|
type StyleObject = ViewStyle & TextStyle & ImageStyle
|
||||||
|
type StyleObjectProperties = keyof StyleObject
|
||||||
|
type ColorProperties =
|
||||||
|
| 'color'
|
||||||
|
| 'backgroundColor'
|
||||||
|
| 'borderColor'
|
||||||
|
| 'borderTopColor'
|
||||||
|
| 'borderRightColor'
|
||||||
|
| 'borderBottomColor'
|
||||||
|
| 'borderLeftColor'
|
||||||
|
type DimensionProperties =
|
||||||
|
| 'padding'
|
||||||
|
| 'paddingTop'
|
||||||
|
| 'paddingBottom'
|
||||||
|
| 'paddingLeft'
|
||||||
|
| 'paddingRight'
|
||||||
|
| 'paddingVertical'
|
||||||
|
| 'paddingHorizontal'
|
||||||
|
| 'margin'
|
||||||
|
| 'marginTop'
|
||||||
|
| 'marginBottom'
|
||||||
|
| 'marginLeft'
|
||||||
|
| 'marginRight'
|
||||||
|
| 'top'
|
||||||
|
| 'bottom'
|
||||||
|
| 'left'
|
||||||
|
| 'right'
|
||||||
|
| 'gap'
|
||||||
|
| 'rowGap'
|
||||||
|
| 'columnGap'
|
||||||
|
|
||||||
|
export type Tokens = {
|
||||||
|
space?: DimensionValue[] | Record<string, DimensionValue>
|
||||||
|
} & {
|
||||||
|
[Property in StyleObjectProperties]?: Record<
|
||||||
|
string,
|
||||||
|
Required<StyleObject>[Property]
|
||||||
|
>
|
||||||
|
}
|
||||||
|
export type Properties = Record<string, Readonly<StyleObjectProperties[]>>
|
||||||
|
export type Macros<T extends Tokens> = Record<
|
||||||
|
string,
|
||||||
|
(value: any, tokens: T) => StyleObject
|
||||||
|
>
|
||||||
|
export type Breakpoints = {
|
||||||
|
[Breakpoint: string]: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Styles<
|
||||||
|
T extends Tokens,
|
||||||
|
P extends Properties,
|
||||||
|
M extends Macros<T>,
|
||||||
|
> = {
|
||||||
|
[Property in ColorProperties]?:
|
||||||
|
| keyof T['color']
|
||||||
|
| Omit<ColorValue, keyof T['color']>
|
||||||
|
} & {
|
||||||
|
[Property in DimensionProperties]?: keyof T['space'] | DimensionValue
|
||||||
|
} & {
|
||||||
|
[Property in Exclude<
|
||||||
|
StyleObjectProperties,
|
||||||
|
ColorProperties | DimensionProperties
|
||||||
|
>]?: keyof T[Property] | StyleObject[Property]
|
||||||
|
} & {
|
||||||
|
// this empty P is to avoid circular references back to Properties type
|
||||||
|
[Property in keyof P]?: Styles<T, {}, M>[P[Property][0]]
|
||||||
|
} & {
|
||||||
|
[Property in keyof M]?: M[Property] extends (
|
||||||
|
value: infer Value,
|
||||||
|
tokens: T,
|
||||||
|
) => StyleObject
|
||||||
|
? Value
|
||||||
|
: never
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResponsiveStyles<
|
||||||
|
T extends Tokens,
|
||||||
|
P extends Properties,
|
||||||
|
M extends Macros<T>,
|
||||||
|
B extends Breakpoints,
|
||||||
|
> =
|
||||||
|
| Styles<T, P, M>
|
||||||
|
| {
|
||||||
|
[Breakpoint in keyof B]?: Styles<T, P, M>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Theme<
|
||||||
|
T extends Tokens,
|
||||||
|
P extends Properties,
|
||||||
|
M extends Macros<T>,
|
||||||
|
B extends Breakpoints,
|
||||||
|
> = {
|
||||||
|
config: {
|
||||||
|
tokens: T
|
||||||
|
properties: P
|
||||||
|
macros: M
|
||||||
|
breakpoints: B
|
||||||
|
}
|
||||||
|
style: <Props>(
|
||||||
|
props: ResponsiveStyles<T, P, M, B> & Omit<Props, keyof P>,
|
||||||
|
breakpoints?: (keyof B)[],
|
||||||
|
) => {
|
||||||
|
styles: StyleObject
|
||||||
|
props: Props
|
||||||
|
}
|
||||||
|
getActiveBreakpoints: ({width}: {width: number}) => {
|
||||||
|
active: (keyof B)[]
|
||||||
|
current: keyof B
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const specialTokenMapping = {
|
||||||
|
color: 'color',
|
||||||
|
backgroundColor: 'color',
|
||||||
|
borderColor: 'color',
|
||||||
|
borderTopColor: 'color',
|
||||||
|
borderRightColor: 'color',
|
||||||
|
borderBottomColor: 'color',
|
||||||
|
borderLeftColor: 'color',
|
||||||
|
padding: 'space',
|
||||||
|
paddingTop: 'space',
|
||||||
|
paddingBottom: 'space',
|
||||||
|
paddingLeft: 'space',
|
||||||
|
paddingRight: 'space',
|
||||||
|
paddingVertical: 'space',
|
||||||
|
paddingHorizontal: 'space',
|
||||||
|
margin: 'space',
|
||||||
|
marginTop: 'space',
|
||||||
|
marginBottom: 'space',
|
||||||
|
marginLeft: 'space',
|
||||||
|
marginRight: 'space',
|
||||||
|
top: 'space',
|
||||||
|
bottom: 'space',
|
||||||
|
left: 'space',
|
||||||
|
right: 'space',
|
||||||
|
gap: 'space',
|
||||||
|
rowGap: 'space',
|
||||||
|
columnGap: 'space',
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyMapping: Properties = {
|
||||||
|
// FlexStyle
|
||||||
|
alignContent: ['alignContent'],
|
||||||
|
alignItems: ['alignItems'],
|
||||||
|
alignSelf: ['alignSelf'],
|
||||||
|
aspectRatio: ['aspectRatio'],
|
||||||
|
borderBottomWidth: ['borderBottomWidth'],
|
||||||
|
borderEndWidth: ['borderEndWidth'],
|
||||||
|
borderLeftWidth: ['borderLeftWidth'],
|
||||||
|
borderRightWidth: ['borderRightWidth'],
|
||||||
|
borderStartWidth: ['borderStartWidth'],
|
||||||
|
borderTopWidth: ['borderTopWidth'],
|
||||||
|
borderWidth: ['borderWidth'],
|
||||||
|
bottom: ['bottom'],
|
||||||
|
display: ['display'],
|
||||||
|
end: ['end'],
|
||||||
|
flex: ['flex'],
|
||||||
|
flexBasis: ['flexBasis'],
|
||||||
|
flexDirection: ['flexDirection'],
|
||||||
|
rowGap: ['rowGap'],
|
||||||
|
gap: ['gap'],
|
||||||
|
columnGap: ['columnGap'],
|
||||||
|
flexGrow: ['flexGrow'],
|
||||||
|
flexShrink: ['flexShrink'],
|
||||||
|
flexWrap: ['flexWrap'],
|
||||||
|
height: ['height'],
|
||||||
|
justifyContent: ['justifyContent'],
|
||||||
|
left: ['left'],
|
||||||
|
margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
||||||
|
marginBottom: ['marginBottom'],
|
||||||
|
marginEnd: ['marginEnd'],
|
||||||
|
marginHorizontal: ['marginLeft', 'marginRight'],
|
||||||
|
marginLeft: ['marginLeft'],
|
||||||
|
marginRight: ['marginRight'],
|
||||||
|
marginStart: ['marginStart'],
|
||||||
|
marginTop: ['marginTop'],
|
||||||
|
marginVertical: ['marginTop', 'marginBottom'],
|
||||||
|
maxHeight: ['maxHeight'],
|
||||||
|
maxWidth: ['maxWidth'],
|
||||||
|
overflow: ['overflow'],
|
||||||
|
padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
||||||
|
paddingBottom: ['paddingBottom'],
|
||||||
|
paddingEnd: ['paddingEnd'],
|
||||||
|
paddingHorizontal: ['paddingLeft', 'paddingRight'],
|
||||||
|
paddingLeft: ['paddingLeft'],
|
||||||
|
paddingRight: ['paddingRight'],
|
||||||
|
paddingStart: ['paddingStart'],
|
||||||
|
paddingTop: ['paddingTop'],
|
||||||
|
paddingVertical: ['paddingTop', 'paddingBottom'],
|
||||||
|
position: ['position'],
|
||||||
|
right: ['right'],
|
||||||
|
start: ['start'],
|
||||||
|
top: ['top'],
|
||||||
|
width: ['width'],
|
||||||
|
zIndex: ['zIndex'],
|
||||||
|
direction: ['direction'],
|
||||||
|
|
||||||
|
// ShadowStyle
|
||||||
|
shadowColor: ['shadowColor'],
|
||||||
|
shadowOffset: ['shadowOffset'],
|
||||||
|
shadowOpacity: ['shadowOpacity'],
|
||||||
|
shadowRadius: ['shadowRadius'],
|
||||||
|
|
||||||
|
// TransformStyle
|
||||||
|
transform: ['transform'],
|
||||||
|
transformMatrix: ['transformMatrix'],
|
||||||
|
rotation: ['rotation'],
|
||||||
|
scaleX: ['scaleX'],
|
||||||
|
scaleY: ['scaleY'],
|
||||||
|
translateX: ['translateX'],
|
||||||
|
translateY: ['translateY'],
|
||||||
|
|
||||||
|
// ViewStyle
|
||||||
|
backfaceVisibility: ['backfaceVisibility'],
|
||||||
|
backgroundColor: ['backgroundColor'],
|
||||||
|
borderBlockColor: ['borderBlockColor'],
|
||||||
|
borderBlockEndColor: ['borderBlockEndColor'],
|
||||||
|
borderBlockStartColor: ['borderBlockStartColor'],
|
||||||
|
borerBottomColor: ['borderBottomColor'],
|
||||||
|
borderBottomEndRadius: ['borderBottomEndRadius'],
|
||||||
|
borderBottomLeftRadius: ['borderBottomLeftRadius'],
|
||||||
|
borderBottomRightRadius: ['borderBottomRightRadius'],
|
||||||
|
borderBottomStartRadius: ['borderBottomStartRadius'],
|
||||||
|
borderColor: ['borderColor'],
|
||||||
|
borderCurve: ['borderCurve'],
|
||||||
|
borderEndColor: ['borderEndColor'],
|
||||||
|
borderEndEndRadius: ['borderEndEndRadius'],
|
||||||
|
borderEndStartRadius: ['borderEndStartRadius'],
|
||||||
|
borderLeftColor: ['borderLeftColor'],
|
||||||
|
borderRadius: ['borderRadius'],
|
||||||
|
borderRightColor: ['borderRightColor'],
|
||||||
|
borderStartColor: ['borderStartColor'],
|
||||||
|
borderStartEndRadius: ['borderStartEndRadius'],
|
||||||
|
borderStartStartRadius: ['borderStartStartRadius'],
|
||||||
|
borderStyle: ['borderStyle'],
|
||||||
|
borderTopColor: ['borderTopColor'],
|
||||||
|
borderTopEndRadius: ['borderTopEndRadius'],
|
||||||
|
borderTopLeftRadius: ['borderTopLeftRadius'],
|
||||||
|
borderTopRightRadius: ['borderTopRightRadius'],
|
||||||
|
borderTopStartRadius: ['borderTopStartRadius'],
|
||||||
|
opacity: ['opacity'],
|
||||||
|
elevation: ['elevation'],
|
||||||
|
pointerEvents: ['pointerEvents'],
|
||||||
|
|
||||||
|
// TextStyleIOS
|
||||||
|
fontVariant: ['fontVariant'],
|
||||||
|
textDecorationColor: ['textDecorationColor'],
|
||||||
|
textDecorationStyle: ['textDecorationStyle'],
|
||||||
|
writingDirection: ['writingDirection'],
|
||||||
|
|
||||||
|
// TextStyleAndroid
|
||||||
|
textAlignVertical: ['textAlignVertical'],
|
||||||
|
verticalAlign: ['verticalAlign'],
|
||||||
|
includeFontPadding: ['includeFontPadding'],
|
||||||
|
|
||||||
|
// TextStyle
|
||||||
|
color: ['color'],
|
||||||
|
fontFamily: ['fontFamily'],
|
||||||
|
fontSize: ['fontSize'],
|
||||||
|
fontStyle: ['fontStyle'],
|
||||||
|
fontWeight: ['fontWeight'],
|
||||||
|
letterSpacing: ['letterSpacing'],
|
||||||
|
lineHeight: ['lineHeight'],
|
||||||
|
textAlign: ['textAlign'],
|
||||||
|
textDecorationLine: ['textDecorationLine'],
|
||||||
|
textShadowColor: ['textShadowColor'],
|
||||||
|
textShadowOffset: ['textShadowOffset'],
|
||||||
|
textTransform: ['textTransform'],
|
||||||
|
|
||||||
|
// ImageStyle
|
||||||
|
resizeMode: ['resizeMode'],
|
||||||
|
overlayColor: ['overlayColor'],
|
||||||
|
tintColor: ['tintColor'],
|
||||||
|
objectFit: ['objectFit'],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createTheme = <
|
||||||
|
T extends Tokens,
|
||||||
|
P extends Properties,
|
||||||
|
M extends Macros<T>,
|
||||||
|
B extends Breakpoints,
|
||||||
|
>({
|
||||||
|
tokens,
|
||||||
|
properties: userProperties = {},
|
||||||
|
macros: userMacros = {},
|
||||||
|
breakpoints: userBreakpoints = {},
|
||||||
|
}: {
|
||||||
|
tokens: T
|
||||||
|
properties?: Partial<P>
|
||||||
|
macros?: Partial<M>
|
||||||
|
breakpoints?: Partial<B>
|
||||||
|
}): Theme<T, P, M, B> => {
|
||||||
|
const properties = Object.assign({}, propertyMapping, userProperties) as P
|
||||||
|
const macros = userMacros as M
|
||||||
|
const breakpoints = Object.entries(userBreakpoints)
|
||||||
|
.sort(([_a, a], [_b, b]) => {
|
||||||
|
return a - b
|
||||||
|
})
|
||||||
|
.reduce((breakpoints, [key, value]) => {
|
||||||
|
breakpoints[key as keyof B] = value
|
||||||
|
return breakpoints
|
||||||
|
}, {} as B)
|
||||||
|
|
||||||
|
type InnerTheme = Theme<
|
||||||
|
typeof tokens,
|
||||||
|
typeof properties,
|
||||||
|
typeof macros,
|
||||||
|
typeof breakpoints
|
||||||
|
>
|
||||||
|
|
||||||
|
function _applyStyleProperty(styles: StyleObject, prop: string, value: any) {
|
||||||
|
for (const _prop of properties[prop]) {
|
||||||
|
// @ts-ignore no index sig, it's fine
|
||||||
|
const t = specialTokenMapping[_prop]
|
||||||
|
? // @ts-ignore no index sig, it's fine
|
||||||
|
tokens[specialTokenMapping[_prop]]
|
||||||
|
: tokens[_prop]
|
||||||
|
styles[_prop] = t?.[value] || value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const style: InnerTheme['style'] = (rawProps, activeBreakpoints = []) => {
|
||||||
|
let styles: StyleObject = {}
|
||||||
|
const props: any = {}
|
||||||
|
|
||||||
|
Object.keys(rawProps).forEach(prop => {
|
||||||
|
// @ts-ignore no index sig, it's fine
|
||||||
|
const value = rawProps[prop]
|
||||||
|
|
||||||
|
if (value === undefined) return
|
||||||
|
|
||||||
|
if (properties[prop]) {
|
||||||
|
_applyStyleProperty(styles, prop, value)
|
||||||
|
} else if (macros[prop]) {
|
||||||
|
styles = {
|
||||||
|
...styles,
|
||||||
|
// @ts-ignore no index sig, it's fine
|
||||||
|
...(rawProps[prop] === true ? macros[prop](value, tokens) : {}),
|
||||||
|
}
|
||||||
|
} else if (breakpoints[prop]) {
|
||||||
|
for (const b of Object.keys(breakpoints)) {
|
||||||
|
if (activeBreakpoints.includes(b)) {
|
||||||
|
// @ts-ignore no index sig, it's fine
|
||||||
|
const breakpointStyles = rawProps[b] || {}
|
||||||
|
const r = style(breakpointStyles, activeBreakpoints)
|
||||||
|
styles = {...styles, ...r.styles}
|
||||||
|
} else {
|
||||||
|
// @ts-ignore no index sig, it's fine
|
||||||
|
delete rawProps[b]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// @ts-ignore no index sig, it's fine
|
||||||
|
props[prop] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
styles: StyleSheet.create({sheet: styles}).sheet,
|
||||||
|
props: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveBreakpoints({width}: {width: number}) {
|
||||||
|
const active: (keyof typeof breakpoints)[] = Object.keys(
|
||||||
|
breakpoints,
|
||||||
|
).filter(breakpoint => width >= breakpoints[breakpoint])
|
||||||
|
|
||||||
|
return {
|
||||||
|
active,
|
||||||
|
current: active[active.length - 1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
config: {
|
||||||
|
tokens,
|
||||||
|
properties,
|
||||||
|
macros,
|
||||||
|
breakpoints,
|
||||||
|
},
|
||||||
|
style,
|
||||||
|
getActiveBreakpoints,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {Platform} from 'react-native'
|
||||||
|
|
||||||
|
export function web(value: any) {
|
||||||
|
return Platform.select({
|
||||||
|
web: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ios(value: any) {
|
||||||
|
return Platform.select({
|
||||||
|
ios: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function android(value: any) {
|
||||||
|
return Platform.select({
|
||||||
|
android: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function native(value: any) {
|
||||||
|
return Platform.select({
|
||||||
|
native: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import {createTheme} from './lib/theme'
|
||||||
|
|
||||||
|
const palette = {
|
||||||
|
white: '#FFFFFF',
|
||||||
|
black: '#0A0B0D',
|
||||||
|
gray1: '#F7F9FC',
|
||||||
|
gray2: '#E4E8EE',
|
||||||
|
gray3: '#CED5DE',
|
||||||
|
gray4: '#828A99',
|
||||||
|
gray5: '#4D5564',
|
||||||
|
gray6: '#1D2126',
|
||||||
|
blue: '#1185FE',
|
||||||
|
green: '#54D469',
|
||||||
|
red: '#FB4566',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const light = createTheme({
|
||||||
|
tokens: {
|
||||||
|
color: {
|
||||||
|
primary: palette.blue,
|
||||||
|
l1: palette.white,
|
||||||
|
l2: palette.gray1,
|
||||||
|
l3: palette.gray2,
|
||||||
|
l4: palette.gray3,
|
||||||
|
l5: palette.gray4,
|
||||||
|
l6: palette.gray5,
|
||||||
|
l7: palette.gray6,
|
||||||
|
l8: palette.black,
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
xxs: 10,
|
||||||
|
xs: 12,
|
||||||
|
s: 14,
|
||||||
|
m: 16,
|
||||||
|
l: 18,
|
||||||
|
xl: 22,
|
||||||
|
xxl: 26,
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
xs: 12,
|
||||||
|
s: 14,
|
||||||
|
m: 16,
|
||||||
|
l: 18,
|
||||||
|
xl: 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
w: ['width'],
|
||||||
|
h: ['height'],
|
||||||
|
c: ['color'],
|
||||||
|
bg: ['backgroundColor'],
|
||||||
|
ma: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
||||||
|
mt: ['marginTop'],
|
||||||
|
mb: ['marginBottom'],
|
||||||
|
ml: ['marginLeft'],
|
||||||
|
mr: ['marginRight'],
|
||||||
|
my: ['marginTop', 'marginBottom'],
|
||||||
|
mx: ['marginLeft', 'marginRight'],
|
||||||
|
/**
|
||||||
|
* Alias for `padding`, maps to all padding properties e.g. `paddingTop`,
|
||||||
|
* `paddingBottom`, etc.
|
||||||
|
*/
|
||||||
|
pa: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
||||||
|
pt: ['paddingTop'],
|
||||||
|
pb: ['paddingBottom'],
|
||||||
|
pl: ['paddingLeft'],
|
||||||
|
pr: ['paddingRight'],
|
||||||
|
py: ['paddingTop', 'paddingBottom'],
|
||||||
|
px: ['paddingLeft', 'paddingRight'],
|
||||||
|
z: ['zIndex'],
|
||||||
|
radius: ['borderRadius'],
|
||||||
|
},
|
||||||
|
macros: {
|
||||||
|
row: (_: boolean) => ({flexDirection: 'row'}),
|
||||||
|
column: (_: boolean) => ({flex: 1}),
|
||||||
|
abs: (_: boolean) => ({position: 'absolute'}),
|
||||||
|
cover: (_: boolean) => ({
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
}),
|
||||||
|
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
||||||
|
fontSize(value: 'xs' | 's' | 'm' | 'l' | 'xl', tokens) {
|
||||||
|
return {
|
||||||
|
fontSize: tokens.fontSize[value],
|
||||||
|
lineHeight: tokens.lineHeight[value],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
gtMobile: 800,
|
||||||
|
gtTablet: 1300,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const dark = createTheme({
|
||||||
|
...light.config,
|
||||||
|
tokens: {
|
||||||
|
...light.config.tokens,
|
||||||
|
color: {
|
||||||
|
...light.config.tokens.color,
|
||||||
|
l1: palette.black,
|
||||||
|
l2: palette.gray6,
|
||||||
|
l3: palette.gray5,
|
||||||
|
l4: palette.gray4,
|
||||||
|
l5: palette.gray3,
|
||||||
|
l6: palette.gray2,
|
||||||
|
l7: palette.gray1,
|
||||||
|
l8: palette.white,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user