feat: update design tokens

This commit is contained in:
Hossein Mehrabi 2023-02-13 13:13:50 +03:30
parent 1749b02558
commit e9cb00ea23
11 changed files with 317 additions and 329 deletions

View File

@ -14,7 +14,7 @@ export const parameters: Parameters = {
default: 'light', default: 'light',
values: Object.entries(defaultThemes).map(([name, theme]) => ({ values: Object.entries(defaultThemes).map(([name, theme]) => ({
name, name,
value: theme.palette.background.primary, value: `rgb(${theme.palette.secondary})`,
})), })),
}, },
viewport: { viewport: {

View File

@ -1,21 +1,26 @@
import { DecoratorFunction, useGlobals } from '@storybook/addons' import { DecoratorFunction, useGlobals } from '@storybook/addons'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { defaultThemes, ThemeProvider } from '../src' import { defaultThemes, Theme, ThemeProvider } from '../src'
export const withTheme: DecoratorFunction = (Story, context) => { export const withTheme: DecoratorFunction = (Story, context) => {
const StoryComponent = Story as any as React.ComponentType const StoryComponent = Story as any as React.ComponentType
const themeName = context.globals?.theme ?? 'light' const themeName = context.globals?.theme ?? 'light'
const theme = defaultThemes[themeName] const theme = defaultThemes[themeName] as Theme
const [globals, setGlobals] = useGlobals() const [globals, setGlobals] = useGlobals()
useEffect(() => { useEffect(() => {
const background = (context.parameters.backgrounds?.values ?? []).find(
(value) => value.name === themeName,
)?.value
globals.backgrounds?.value !== background &&
setGlobals({ setGlobals({
...globals, ...globals,
backgrounds: { backgrounds: {
...(globals.background ?? {}), ...(globals.background ?? {}),
value: theme.palette.background.primary, value: background,
}, },
}) })
}, [theme]) }, [theme])

View File

@ -1,28 +1,20 @@
import { css } from '@emotion/react' import { css } from '@emotion/react'
import { withTheme } from '../Theme/withTheme'
import { buttonClasses } from './Button.classes' import { buttonClasses } from './Button.classes'
export const ButtonStyles = withTheme( export const ButtonStyles = css`
(theme) => css`
.${buttonClasses.root} { .${buttonClasses.root} {
width: auto; width: auto;
color: var(--lsd-text-primary); color: rgb(var(--lsd-text-primary));
background: none; background: none;
border: 1px solid var(--lsd-surface-primary); border: 1px solid rgb(var(--lsd-border-primary));
cursor: pointer; cursor: pointer;
padding: 6px 24px; padding: 6px 24px;
@media (max-width: ${theme.breakpoints.lg.width}px) {
color: red;
border-color: red;
}
} }
.${buttonClasses.disabled} { .${buttonClasses.disabled} {
cursor: default; cursor: default;
color: var(--lsd-surface-disabled); opacity: 0.34;
border-color: var(--lsd-surface-disabled);
} }
.${buttonClasses.large} { .${buttonClasses.large} {
@ -43,5 +35,4 @@ export const ButtonStyles = withTheme(
} }
} }
} }
`, `
)

View File

@ -1,14 +1,17 @@
import { Global } from '@emotion/react' import { Global, SerializedStyles } from '@emotion/react'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { ButtonStyles } from '../Button/Button.styles' import { ButtonStyles } from '../Button/Button.styles'
import { defaultThemes, Theme } from '../Theme' import { defaultThemes, Theme, withTheme } from '../Theme'
const componentStyles: Array<ReturnType<typeof withTheme> | SerializedStyles> =
[ButtonStyles]
export const CSSBaseline: React.FC<{ theme?: Theme }> = ({ export const CSSBaseline: React.FC<{ theme?: Theme }> = ({
theme = defaultThemes.light, theme = defaultThemes.light,
}) => { }) => {
const styles = useMemo( const styles = useMemo(
() => () =>
[ButtonStyles] componentStyles
.map((style) => (typeof style === 'function' ? style(theme) : style)) .map((style) => (typeof style === 'function' ? style(theme) : style))
.map((style) => <Global key={style.name} styles={style} />), .map((style) => <Global key={style.name} styles={style} />),
[theme], [theme],

View File

@ -7,173 +7,140 @@ export const baseTheme: Theme = {
xs: { xs: {
width: 0, width: 0,
typography: { typography: {
headlineLg: {}, display1: {},
headlineMd: {}, display2: {},
headlineStd: {}, h1: {},
headlineSm: {}, h2: {},
titleLg: {}, h3: {},
titleMd: {}, h4: {},
titleSm: {}, h5: {},
bodyLg: {}, h6: {},
bodyMd: {}, body1: {},
bodySm: {}, body2: {},
labelLg: {}, body3: {},
labelMd: {}, label1: {},
labelSm: {}, label2: {},
subtitle1: {},
subtitle2: {},
}, },
}, },
sm: { sm: {
width: 400, width: 400,
typography: { typography: {
headlineLg: {}, display1: {},
headlineMd: {}, display2: {},
headlineStd: {}, h1: {},
headlineSm: {}, h2: {},
titleLg: {}, h3: {},
titleMd: {}, h4: {},
titleSm: {}, h5: {},
bodyLg: {}, h6: {},
bodyMd: {}, body1: {},
bodySm: {}, body2: {},
labelLg: {}, body3: {},
labelMd: {}, label1: {},
labelSm: {}, label2: {},
subtitle1: {},
subtitle2: {},
}, },
}, },
md: { md: {
width: 768, width: 768,
typography: { typography: {
headlineLg: {}, display1: {},
headlineMd: {}, display2: {},
headlineStd: {}, h1: {},
headlineSm: {}, h2: {},
titleLg: {}, h3: {},
titleMd: {}, h4: {},
titleSm: {}, h5: {},
bodyLg: {}, h6: {},
bodyMd: {}, body1: {},
bodySm: {}, body2: {},
labelLg: {}, body3: {},
labelMd: {}, label1: {},
labelSm: {}, label2: {},
subtitle1: {},
subtitle2: {},
}, },
}, },
lg: { lg: {
width: 1024, width: 1024,
typography: { typography: {
headlineLg: {}, display1: {},
headlineMd: {}, display2: {},
headlineStd: {}, h1: {},
headlineSm: {}, h2: {},
titleLg: {}, h3: {},
titleMd: {}, h4: {},
titleSm: {}, h5: {},
bodyLg: {}, h6: {},
bodyMd: {}, body1: {},
bodySm: {}, body2: {},
labelLg: {}, body3: {},
labelMd: {}, label1: {},
labelSm: {}, label2: {},
subtitle1: {},
subtitle2: {},
}, },
}, },
xl: { xl: {
width: 1200, width: 1200,
typography: { typography: {
headlineLg: {}, display1: {},
headlineMd: {}, display2: {},
headlineStd: {}, h1: {},
headlineSm: {}, h2: {},
titleLg: {}, h3: {},
titleMd: {}, h4: {},
titleSm: {}, h5: {},
bodyLg: {}, h6: {},
bodyMd: {}, body1: {},
bodySm: {}, body2: {},
labelLg: {}, body3: {},
labelMd: {}, label1: {},
labelSm: {}, label2: {},
subtitle1: {},
subtitle2: {},
}, },
}, },
}, },
typography: { typography: {
headlineLg: { display1: { fontSize: '5.625rem', lineHeight: '6.125rem' },
fontSize: '2.875rem', display2: { fontSize: '3.5625rem', lineHeight: '4rem' },
lineHeight: '3.25rem', h1: { fontSize: '2.875rem', lineHeight: '3.25rem' },
}, h2: { fontSize: '2.25rem', lineHeight: '2.75rem' },
headlineMd: { h3: { fontSize: '2rem', lineHeight: '2.5rem' },
fontSize: '1.75rem', h4: { fontSize: '1.75rem', lineHeight: '2.25rem' },
lineHeight: '2.25rem', h5: { fontSize: '1.5rem', lineHeight: '2rem' },
}, h6: { fontSize: '1.375rem', lineHeight: '1.75rem' },
headlineStd: { subtitle1: { fontSize: '1rem', lineHeight: '1.5rem' },
fontSize: '2rem', subtitle2: { fontSize: '0.875rem', lineHeight: '1.25rem' },
lineHeight: '2.5rem', body1: { fontSize: '1rem', lineHeight: '1.5rem' },
}, body2: { fontSize: '0.875rem', lineHeight: '1.25rem' },
headlineSm: { body3: { fontSize: '0.75rem', lineHeight: '1rem' },
fontSize: '1.5rem', label1: { fontSize: '0.875rem', lineHeight: '1.25rem' },
lineHeight: '2rem', label2: { fontSize: '0.75rem', lineHeight: '1rem' },
},
titleLg: {
fontSize: '1.375rem',
lineHeight: '1.75rem',
},
titleMd: {
fontSize: '1rem',
lineHeight: '1.5rem',
},
titleSm: {
fontSize: '0.875rem',
lineHeight: '1.25rem',
},
bodyLg: {
fontSize: '1rem',
lineHeight: '1.5rem',
},
bodyMd: {
fontSize: '0.875rem',
lineHeight: '1.25rem',
},
bodySm: {
fontSize: '0.75rem',
lineHeight: '1rem',
},
labelLg: {
fontSize: '0.875rem',
lineHeight: '1.25rem',
},
labelMd: {
fontSize: '0.875rem',
lineHeight: '1.25rem',
},
labelSm: {
fontSize: '0.75rem',
lineHeight: '1rem',
},
}, },
palette: { palette: {
background: { primary: '0, 0, 0',
primary: 'rgba(255, 255, 255, 1)', secondary: '255, 255, 255',
secondary: 'rgba(0, 0, 0, 1)',
},
border: {
primary: 'rgba(51, 51, 56, 1)',
secondary: 'rgba(255, 255, 255, 1)',
tertiary: 'rgba(223, 223, 226, 1)',
},
surface: { surface: {
primary: 'rgba(0, 0, 0, 1)', primary: '255, 255, 255',
secondary: 'rgba(0, 0, 0, 0.34)', secondary: '0, 0, 0',
tertiary: 'rgba(0, 0, 0, 0.2)',
disabled: 'rgba(168, 168, 168, 1)',
}, },
text: { text: {
primary: 'rgba(0, 0, 0, 1)', primary: '0, 0, 0',
secondary: 'rgba(0, 0, 0, 0.34)', secondary: '255, 255, 255',
placeholder: 'rgba(0, 0, 0, 0.34)', tertiary: '0, 0, 0, 0.34',
disabled: 'rgba(0, 0, 0, 0.34)',
}, },
icons: { border: {
primary: 'rgba(0, 0, 0, 1)', primary: '0, 0, 0',
disabled: 'rgba(0, 0, 0, 0.34)', secondary: '255, 255, 255',
},
icon: {
primary: '0, 0, 0',
secondary: '255, 255, 255',
}, },
}, },
globalStyles: css``, globalStyles: css``,

View File

@ -10,21 +10,36 @@ export const LSD_NAMESPACE = 'lsd'
export const THEME_BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl'] as Breakpoints[] export const THEME_BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl'] as Breakpoints[]
export const THEME_TYPOGRAPHY_VARIANTS = [ export const THEME_TYPOGRAPHY_VARIANTS = [
'headlineLg', 'display1',
'headlineStd', 'display2',
'headlineMd', 'h1',
'headlineSm', 'h2',
'titleLg', 'h3',
'titleMd', 'h4',
'titleSm', 'h5',
'bodySm', 'h6',
'bodyMd', 'subtitle1',
'bodyLg', 'subtitle2',
'labelLg', 'body1',
'labelMd', 'body2',
'labelSm', 'body3',
'label1',
'label2',
] as TypographyVariants[] ] as TypographyVariants[]
export const THEME_TYPOGRAPHY_ELEMENTS: Partial<
Record<TypographyVariants, string[]>
> = {
h1: ['h1'],
h2: ['h2'],
h3: ['h3'],
h4: ['h4'],
h5: ['h5'],
h6: ['h6'],
body1: ['body'],
label1: ['label'],
}
export const THEME_TYPOGRAPHY_PROPERTIES = [ export const THEME_TYPOGRAPHY_PROPERTIES = [
'fontSize', 'fontSize',
'lineHeight', 'lineHeight',

View File

@ -1,5 +1,4 @@
import { css } from '@emotion/react' import { css } from '@emotion/react'
import defaultsDeep from 'lodash/defaultsDeep'
import { pairs } from '../../utils/object.utils' import { pairs } from '../../utils/object.utils'
import { baseTheme } from './baseTheme' import { baseTheme } from './baseTheme'
import { THEME_BREAKPOINTS, THEME_TYPOGRAPHY_VARIANTS } from './constants' import { THEME_BREAKPOINTS, THEME_TYPOGRAPHY_VARIANTS } from './constants'
@ -9,6 +8,7 @@ import type {
CreateThemeProps, CreateThemeProps,
Theme, Theme,
ThemeBreakpoints, ThemeBreakpoints,
ThemePalette,
TypographyVariants, TypographyVariants,
VariantThemeProperties, VariantThemeProperties,
} from './types' } from './types'
@ -32,6 +32,9 @@ const createBreakpointStyle = (
...defaultTheme[key][variant], ...defaultTheme[key][variant],
...theme[key][variant], ...theme[key][variant],
...(all?.[index - 1]?.[key]?.[variant] ?? {}), ...(all?.[index - 1]?.[key]?.[variant] ?? {}),
...(defaultTheme.breakpoints?.[THEME_BREAKPOINTS[index]]?.[key]?.[
variant
] ?? {}),
...(theme.breakpoints?.[THEME_BREAKPOINTS[index]]?.[key]?.[variant] ?? ...(theme.breakpoints?.[THEME_BREAKPOINTS[index]]?.[key]?.[variant] ??
{}), {}),
})) }))
@ -68,8 +71,34 @@ const createBreakpointStyles = (
).map((styles, index) => [THEME_BREAKPOINTS[index], styles]), ).map((styles, index) => [THEME_BREAKPOINTS[index], styles]),
) as ThemeBreakpoints ) as ThemeBreakpoints
const createPaletteStyles = (theme: CreateThemeProps, defaultTheme: Theme) => const createPaletteStyles = (theme: CreateThemeProps, defaultTheme: Theme) => {
defaultsDeep(theme.palette, defaultTheme.palette) const primary = theme.palette.primary ?? defaultTheme.palette.primary
const secondary = theme.palette.secondary ?? defaultTheme.palette.secondary
const palette: ThemePalette = {
primary,
secondary,
surface: {
primary: theme.palette.surface?.primary ?? secondary,
secondary: theme.palette.surface?.secondary ?? primary,
},
border: {
primary: theme.palette.border?.primary ?? primary,
secondary: theme.palette.border?.secondary ?? secondary,
},
icon: {
primary: theme.palette.icon?.primary ?? primary,
secondary: theme.palette.icon?.secondary ?? secondary,
},
text: {
primary: theme.palette.text?.primary ?? primary,
secondary: theme.palette.text?.secondary ?? secondary,
tertiary: theme.palette.text?.tertiary ?? `${primary}, 0.34`,
},
}
return palette
}
export const createTheme = ( export const createTheme = (
props: CreateThemeProps, props: CreateThemeProps,

View File

@ -15,15 +15,8 @@ const darkTheme = createTheme(
breakpoints: {}, breakpoints: {},
typography: {}, typography: {},
palette: { palette: {
background: { primary: '255, 255, 255',
primary: '#000', secondary: '0, 0, 0',
},
surface: {
primary: '#fff',
},
text: {
primary: 'rgb(255, 255, 255)',
},
}, },
}, },
lightTheme, lightTheme,

View File

@ -1,109 +1,104 @@
import { css } from '@emotion/react' import { css } from '@emotion/react'
import { import {
LSD_NAMESPACE,
THEME_BREAKPOINTS, THEME_BREAKPOINTS,
THEME_TYPOGRAPHY_PROPERTIES, THEME_TYPOGRAPHY_PROPERTIES,
THEME_TYPOGRAPHY_VARIANTS, THEME_TYPOGRAPHY_VARIANTS,
} from './constants' } from './constants'
import { Breakpoints, Theme, TypographyVariants } from './types' import { Theme, TypographyProperties, TypographyVariants } from './types'
import { withTheme } from './withTheme'
export const gs = {
breakpoint: (
theme: Theme,
breakpoint: Breakpoints,
content: string,
) => `@media (min-width: ${theme.breakpoints[breakpoint].width}px) {
${content}
}`,
const cssUtils = {
vars: { vars: {
name: (...parts: string[]) => `--${[LSD_NAMESPACE, ...parts].join('-')}`, lsd: (...seq: string[]) => `--${['lsd', ...seq].join('-')}`,
wrap: (v: string, wrap = true) => (!wrap ? v : `var(${v})`),
define: (name: string, value: any) => `${name}: ${value};`,
typography: ( typography: (
variant: TypographyVariants, variant: TypographyVariants | string,
property: string, property: TypographyProperties | string,
breakpoint?: Breakpoints | 'default', ) => cssUtils.vars.lsd(variant, property),
) => gs.vars.name(variant, property, ...(breakpoint ? [breakpoint] : [])), color: (category: string, variant: string) =>
palette: (category: string, variant: string) => cssUtils.vars.lsd(category, variant),
gs.vars.name(category, variant), wrap: (name: string) => `var(${name})`,
}, },
all: (theme: Theme) => define: (name: string, value: string) => `${name}: ${value};`,
[gs.typography.all(theme), gs.palette.all(theme)].join('\n'), }
typography: { const generateThemeGlobalStyles = withTheme((theme) => {
all: (theme: Theme) => [gs.typography.variants(theme)].join('\n'), const vars: Array<string | string[]> = []
const styles: Array<string | string[]> = []
const breakpointStyles: string[][] = THEME_BREAKPOINTS.map(() => [])
const breakpointVars: string[][] = THEME_BREAKPOINTS.map(() => [])
variants: (theme: Theme) => {
[ THEME_TYPOGRAPHY_VARIANTS.forEach((variant) => {
...THEME_TYPOGRAPHY_VARIANTS.flatMap((variant) => THEME_TYPOGRAPHY_PROPERTIES.forEach((property) => {
THEME_TYPOGRAPHY_PROPERTIES.map((prop) => const value = theme.typography[variant][property]?.toString() ?? 'unset'
gs.vars.define( vars.push(
gs.vars.typography(variant, prop), cssUtils.define(cssUtils.vars.typography(variant, property), value),
theme.typography[variant][prop], )
), })
), })
),
gs.typography.breakpoints(theme),
].join('\n'),
breakpoints: (theme: Theme) => THEME_BREAKPOINTS.forEach((breakpoint, breakpointIndex) => {
THEME_BREAKPOINTS.map((breakpoint, index) => THEME_TYPOGRAPHY_VARIANTS.forEach((variant) => {
gs.breakpoint( THEME_TYPOGRAPHY_PROPERTIES.forEach((property) => {
theme, const value =
breakpoint, theme.breakpoints[breakpoint].typography[variant][property]
gs.typography.breakpoint(theme, breakpoint, index),
),
).join('\n'),
breakpoint: (
theme: Theme,
breakpoint: Breakpoints,
breakpointIndex: number,
) =>
THEME_TYPOGRAPHY_VARIANTS.flatMap((variant) =>
THEME_TYPOGRAPHY_PROPERTIES.map((prop) => {
const value = theme.breakpoints[breakpoint].typography[variant][prop]
const current = const current =
breakpointIndex > 0 breakpointIndex > 0
? theme.breakpoints?.[THEME_BREAKPOINTS[breakpointIndex - 1]] ? theme.breakpoints?.[THEME_BREAKPOINTS[breakpointIndex - 1]]
?.typography?.[variant]?.[prop] ?.typography?.[variant]?.[property]
: theme.typography[variant][prop] : theme.typography[variant][property]
return value !== current if (value && value !== current) {
? gs.vars.define(gs.vars.typography(variant, prop), value) breakpointVars[breakpointIndex].push(
: undefined cssUtils.define(
}), cssUtils.vars.typography(variant, property),
value.toString(),
),
) )
.filter((value) => !!value) }
.join('\n'), })
}, })
})
}
palette: { {
all: (theme: Theme) => { const { primary, secondary, ...rest } = theme.palette
const palette = theme.palette as Record<string, Record<string, string>> const palette = rest as Record<string, Record<string, string>>
return [ vars.push(
cssUtils.define(cssUtils.vars.color('theme', 'primary'), primary),
cssUtils.define(cssUtils.vars.color('theme', 'secondary'), secondary),
...Object.keys(palette).flatMap((name) => ...Object.keys(palette).flatMap((name) =>
Object.keys(palette[name]).map((variant) => Object.keys(palette[name]).map((variant) =>
gs.palette.color(name, variant, palette[name][variant]), cssUtils.define(
cssUtils.vars.color(name, variant),
palette[name][variant],
), ),
), ),
),
`:root { )
html, body {
background-color: ${theme.palette.background.primary};
} }
}
`,
].join('\n')
},
color: (name: string, variant: string, value: string) => THEME_BREAKPOINTS.map((breakpoint, index) => {
gs.vars.define(gs.vars.palette(name, variant), value), styles.push(`@media (min-width: ${theme.breakpoints[breakpoint].width}px) {
}, :root {
} ${breakpointVars[index]}
}
${breakpointStyles[index]}
}`)
})
return css`
:root {
${vars}
}
${styles}
`
})
export const createThemeGlobalStyles = (() => { export const createThemeGlobalStyles = (() => {
return (theme: Theme) => { return (theme: Theme) => {
@ -117,15 +112,8 @@ export const createThemeGlobalStyles = (() => {
) )
return cache[key] return cache[key]
const styles = globalStyles.all(theme) cache[key] = generateThemeGlobalStyles(theme)
cache[key] = css`
:root {
${styles}
}
`
return cache[key] return cache[key]
} }
})() })()
export const globalStyles = gs

View File

@ -7,7 +7,7 @@ export {
} from './constants' } from './constants'
export { createTheme } from './createTheme' export { createTheme } from './createTheme'
export { defaultThemes } from './defaultThemes' export { defaultThemes } from './defaultThemes'
export { createThemeGlobalStyles, globalStyles } from './globalStyles' export { createThemeGlobalStyles } from './globalStyles'
export { ThemeProvider, type ThemeProviderProps } from './ThemeProvider' export { ThemeProvider, type ThemeProviderProps } from './ThemeProvider'
export * from './types' export * from './types'
export { useTheme } from './useTheme' export { useTheme } from './useTheme'

View File

@ -5,19 +5,21 @@ import { DeepPartial } from 'utility-types'
export type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl' export type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export type TypographyTypefaces = 'sans-serif' | 'serif' | 'mono' export type TypographyTypefaces = 'sans-serif' | 'serif' | 'mono'
export type TypographyVariants = export type TypographyVariants =
| 'headlineLg' | 'display1'
| 'headlineStd' | 'display2'
| 'headlineMd' | 'h1'
| 'headlineSm' | 'h2'
| 'titleLg' | 'h3'
| 'titleMd' | 'h4'
| 'titleSm' | 'h5'
| 'bodyLg' | 'h6'
| 'bodyMd' | 'subtitle1'
| 'bodySm' | 'subtitle2'
| 'labelLg' | 'body1'
| 'labelMd' | 'body2'
| 'labelSm' | 'body3'
| 'label1'
| 'label2'
export type VariantThemeProperties = keyof Pick<Theme, 'typography'> export type VariantThemeProperties = keyof Pick<Theme, 'typography'>
@ -28,30 +30,25 @@ export type ThemeTypography<T extends string = TypographyVariants> = {
} }
export type ThemePalette = { export type ThemePalette = {
background: { primary: string
secondary: string
surface: {
primary: string primary: string
secondary: string secondary: string
} }
border: { border: {
primary: string primary: string
secondary: string secondary: string
tertiary: string
}
surface: {
primary: string
secondary: string
tertiary: string
disabled: string
} }
text: { text: {
primary: string primary: string
secondary: string secondary: string
placeholder: string tertiary: string
disabled: string
} }
icons: { icon: {
primary: string primary: string
disabled: string secondary: string
} }
} }