Application Layout Framework (#1732)
* Initial library setup * Add docblocks * Some cleanup * New storybook * Playing around * Remove silly test, use for...in * Memo * Memo * Add hooks example * Tweak colors, bit of cleanup * Improve macro handling * Add some more examples * Rename for better diff * Cleanup * Add nested context example * Add todo * Less break more perf * Buttons, you get the idea * Fix test * Remove temp colors * Add a few more common macros * Docs * Perf improvements * Alf go brrrr * Update breakpoint handling * I think it'll work * Better naming, better code * Fix typo * Some renaming * More complete pass at Tailwind naming * Build out storybook * Playing around with curves * Revert "Playing around with curves" This reverts commit 6b0e0e5c9d842a2d9af31b53affe2f6291c3fa0d. * Smooth brain * Remove outdated docs * Some docs, fix line-height values, export tokens
This commit is contained in:
parent
0ee0554b86
commit
a5b474895a
13 changed files with 1793 additions and 18 deletions
56
src/alf/README.md
Normal file
56
src/alf/README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Application Layout Framework (ALF)
|
||||
|
||||
A set of UI primitives and components.
|
||||
|
||||
## Usage
|
||||
|
||||
Naming conventions follow Tailwind — delimited with a `_` instead of `-` to
|
||||
enable object access — with a couple exceptions:
|
||||
|
||||
**Spacing**
|
||||
|
||||
Uses "t-shirt" sizes `xxs`, `xs`, `sm`, `md`, `lg`, `xl` and `xxl` instead of
|
||||
increments of 4px. We only use a few common spacings, and otherwise typically
|
||||
rely on many one-off values.
|
||||
|
||||
**Text Size**
|
||||
|
||||
Uses "t-shirt" sizes `xxs`, `xs`, `sm`, `md`, `lg`, `xl` and `xxl` to match our
|
||||
type scale.
|
||||
|
||||
**Line Height**
|
||||
|
||||
The text size atoms also apply a line-height with the same value as the size,
|
||||
for a 1:1 ratio. `tight` and `normal` are retained for use in the few places
|
||||
where we need leading.
|
||||
|
||||
### Atoms
|
||||
|
||||
An (mostly-complete) set of style definitions that match Tailwind CSS selectors.
|
||||
These are static and reused throughout the app.
|
||||
|
||||
```tsx
|
||||
import { atoms } from '#/alf'
|
||||
|
||||
<View style={[atoms.flex_row]} />
|
||||
```
|
||||
|
||||
### Theme
|
||||
|
||||
Any values that rely on the theme, namely colors.
|
||||
|
||||
```tsx
|
||||
const t = useTheme()
|
||||
|
||||
<View style={[atoms.flex_row, t.atoms.bg]} />
|
||||
```
|
||||
|
||||
### Breakpoints
|
||||
|
||||
```tsx
|
||||
const b = useBreakpoints()
|
||||
|
||||
if (b.gtMobile) {
|
||||
// render tablet or desktop UI
|
||||
}
|
||||
```
|
514
src/alf/atoms.ts
Normal file
514
src/alf/atoms.ts
Normal file
|
@ -0,0 +1,514 @@
|
|||
import * as tokens from '#/alf/tokens'
|
||||
|
||||
export const atoms = {
|
||||
/*
|
||||
* Positioning
|
||||
*/
|
||||
absolute: {
|
||||
position: 'absolute',
|
||||
},
|
||||
relative: {
|
||||
position: 'relative',
|
||||
},
|
||||
inset_0: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
z_10: {
|
||||
zIndex: 10,
|
||||
},
|
||||
z_20: {
|
||||
zIndex: 20,
|
||||
},
|
||||
z_30: {
|
||||
zIndex: 30,
|
||||
},
|
||||
z_40: {
|
||||
zIndex: 40,
|
||||
},
|
||||
z_50: {
|
||||
zIndex: 50,
|
||||
},
|
||||
|
||||
/*
|
||||
* Width
|
||||
*/
|
||||
w_full: {
|
||||
width: '100%',
|
||||
},
|
||||
h_full: {
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
/*
|
||||
* Border radius
|
||||
*/
|
||||
rounded_sm: {
|
||||
borderRadius: tokens.borderRadius.sm,
|
||||
},
|
||||
rounded_md: {
|
||||
borderRadius: tokens.borderRadius.md,
|
||||
},
|
||||
rounded_full: {
|
||||
borderRadius: tokens.borderRadius.full,
|
||||
},
|
||||
|
||||
/*
|
||||
* Flex
|
||||
*/
|
||||
gap_xxs: {
|
||||
gap: tokens.space.xxs,
|
||||
},
|
||||
gap_xs: {
|
||||
gap: tokens.space.xs,
|
||||
},
|
||||
gap_sm: {
|
||||
gap: tokens.space.sm,
|
||||
},
|
||||
gap_md: {
|
||||
gap: tokens.space.md,
|
||||
},
|
||||
gap_lg: {
|
||||
gap: tokens.space.lg,
|
||||
},
|
||||
gap_xl: {
|
||||
gap: tokens.space.xl,
|
||||
},
|
||||
gap_xxl: {
|
||||
gap: tokens.space.xxl,
|
||||
},
|
||||
flex: {
|
||||
display: 'flex',
|
||||
},
|
||||
flex_row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
flex_wrap: {
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
flex_1: {
|
||||
flex: 1,
|
||||
},
|
||||
flex_grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
flex_shrink: {
|
||||
flexShrink: 1,
|
||||
},
|
||||
justify_center: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
justify_between: {
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
justify_end: {
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
align_center: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
align_start: {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
align_end: {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
|
||||
/*
|
||||
* Text
|
||||
*/
|
||||
text_center: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
text_right: {
|
||||
textAlign: 'right',
|
||||
},
|
||||
text_xxs: {
|
||||
fontSize: tokens.fontSize.xxs,
|
||||
lineHeight: tokens.fontSize.xxs,
|
||||
},
|
||||
text_xs: {
|
||||
fontSize: tokens.fontSize.xs,
|
||||
lineHeight: tokens.fontSize.xs,
|
||||
},
|
||||
text_sm: {
|
||||
fontSize: tokens.fontSize.sm,
|
||||
lineHeight: tokens.fontSize.sm,
|
||||
},
|
||||
text_md: {
|
||||
fontSize: tokens.fontSize.md,
|
||||
lineHeight: tokens.fontSize.md,
|
||||
},
|
||||
text_lg: {
|
||||
fontSize: tokens.fontSize.lg,
|
||||
lineHeight: tokens.fontSize.lg,
|
||||
},
|
||||
text_xl: {
|
||||
fontSize: tokens.fontSize.xl,
|
||||
lineHeight: tokens.fontSize.xl,
|
||||
},
|
||||
text_xxl: {
|
||||
fontSize: tokens.fontSize.xxl,
|
||||
lineHeight: tokens.fontSize.xxl,
|
||||
},
|
||||
leading_tight: {
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
leading_normal: {
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
font_normal: {
|
||||
fontWeight: tokens.fontWeight.normal,
|
||||
},
|
||||
font_semibold: {
|
||||
fontWeight: tokens.fontWeight.semibold,
|
||||
},
|
||||
font_bold: {
|
||||
fontWeight: tokens.fontWeight.bold,
|
||||
},
|
||||
|
||||
/*
|
||||
* Border
|
||||
*/
|
||||
border: {
|
||||
borderWidth: 1,
|
||||
},
|
||||
border_t: {
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
border_b: {
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
|
||||
/*
|
||||
* Padding
|
||||
*/
|
||||
p_xxs: {
|
||||
padding: tokens.space.xxs,
|
||||
},
|
||||
p_xs: {
|
||||
padding: tokens.space.xs,
|
||||
},
|
||||
p_sm: {
|
||||
padding: tokens.space.sm,
|
||||
},
|
||||
p_md: {
|
||||
padding: tokens.space.md,
|
||||
},
|
||||
p_lg: {
|
||||
padding: tokens.space.lg,
|
||||
},
|
||||
p_xl: {
|
||||
padding: tokens.space.xl,
|
||||
},
|
||||
p_xxl: {
|
||||
padding: tokens.space.xxl,
|
||||
},
|
||||
px_xxs: {
|
||||
paddingLeft: tokens.space.xxs,
|
||||
paddingRight: tokens.space.xxs,
|
||||
},
|
||||
px_xs: {
|
||||
paddingLeft: tokens.space.xs,
|
||||
paddingRight: tokens.space.xs,
|
||||
},
|
||||
px_sm: {
|
||||
paddingLeft: tokens.space.sm,
|
||||
paddingRight: tokens.space.sm,
|
||||
},
|
||||
px_md: {
|
||||
paddingLeft: tokens.space.md,
|
||||
paddingRight: tokens.space.md,
|
||||
},
|
||||
px_lg: {
|
||||
paddingLeft: tokens.space.lg,
|
||||
paddingRight: tokens.space.lg,
|
||||
},
|
||||
px_xl: {
|
||||
paddingLeft: tokens.space.xl,
|
||||
paddingRight: tokens.space.xl,
|
||||
},
|
||||
px_xxl: {
|
||||
paddingLeft: tokens.space.xxl,
|
||||
paddingRight: tokens.space.xxl,
|
||||
},
|
||||
py_xxs: {
|
||||
paddingTop: tokens.space.xxs,
|
||||
paddingBottom: tokens.space.xxs,
|
||||
},
|
||||
py_xs: {
|
||||
paddingTop: tokens.space.xs,
|
||||
paddingBottom: tokens.space.xs,
|
||||
},
|
||||
py_sm: {
|
||||
paddingTop: tokens.space.sm,
|
||||
paddingBottom: tokens.space.sm,
|
||||
},
|
||||
py_md: {
|
||||
paddingTop: tokens.space.md,
|
||||
paddingBottom: tokens.space.md,
|
||||
},
|
||||
py_lg: {
|
||||
paddingTop: tokens.space.lg,
|
||||
paddingBottom: tokens.space.lg,
|
||||
},
|
||||
py_xl: {
|
||||
paddingTop: tokens.space.xl,
|
||||
paddingBottom: tokens.space.xl,
|
||||
},
|
||||
py_xxl: {
|
||||
paddingTop: tokens.space.xxl,
|
||||
paddingBottom: tokens.space.xxl,
|
||||
},
|
||||
pt_xxs: {
|
||||
paddingTop: tokens.space.xxs,
|
||||
},
|
||||
pt_xs: {
|
||||
paddingTop: tokens.space.xs,
|
||||
},
|
||||
pt_sm: {
|
||||
paddingTop: tokens.space.sm,
|
||||
},
|
||||
pt_md: {
|
||||
paddingTop: tokens.space.md,
|
||||
},
|
||||
pt_lg: {
|
||||
paddingTop: tokens.space.lg,
|
||||
},
|
||||
pt_xl: {
|
||||
paddingTop: tokens.space.xl,
|
||||
},
|
||||
pt_xxl: {
|
||||
paddingTop: tokens.space.xxl,
|
||||
},
|
||||
pb_xxs: {
|
||||
paddingBottom: tokens.space.xxs,
|
||||
},
|
||||
pb_xs: {
|
||||
paddingBottom: tokens.space.xs,
|
||||
},
|
||||
pb_sm: {
|
||||
paddingBottom: tokens.space.sm,
|
||||
},
|
||||
pb_md: {
|
||||
paddingBottom: tokens.space.md,
|
||||
},
|
||||
pb_lg: {
|
||||
paddingBottom: tokens.space.lg,
|
||||
},
|
||||
pb_xl: {
|
||||
paddingBottom: tokens.space.xl,
|
||||
},
|
||||
pb_xxl: {
|
||||
paddingBottom: tokens.space.xxl,
|
||||
},
|
||||
pl_xxs: {
|
||||
paddingLeft: tokens.space.xxs,
|
||||
},
|
||||
pl_xs: {
|
||||
paddingLeft: tokens.space.xs,
|
||||
},
|
||||
pl_sm: {
|
||||
paddingLeft: tokens.space.sm,
|
||||
},
|
||||
pl_md: {
|
||||
paddingLeft: tokens.space.md,
|
||||
},
|
||||
pl_lg: {
|
||||
paddingLeft: tokens.space.lg,
|
||||
},
|
||||
pl_xl: {
|
||||
paddingLeft: tokens.space.xl,
|
||||
},
|
||||
pl_xxl: {
|
||||
paddingLeft: tokens.space.xxl,
|
||||
},
|
||||
pr_xxs: {
|
||||
paddingRight: tokens.space.xxs,
|
||||
},
|
||||
pr_xs: {
|
||||
paddingRight: tokens.space.xs,
|
||||
},
|
||||
pr_sm: {
|
||||
paddingRight: tokens.space.sm,
|
||||
},
|
||||
pr_md: {
|
||||
paddingRight: tokens.space.md,
|
||||
},
|
||||
pr_lg: {
|
||||
paddingRight: tokens.space.lg,
|
||||
},
|
||||
pr_xl: {
|
||||
paddingRight: tokens.space.xl,
|
||||
},
|
||||
pr_xxl: {
|
||||
paddingRight: tokens.space.xxl,
|
||||
},
|
||||
|
||||
/*
|
||||
* Margin
|
||||
*/
|
||||
m_xxs: {
|
||||
margin: tokens.space.xxs,
|
||||
},
|
||||
m_xs: {
|
||||
margin: tokens.space.xs,
|
||||
},
|
||||
m_sm: {
|
||||
margin: tokens.space.sm,
|
||||
},
|
||||
m_md: {
|
||||
margin: tokens.space.md,
|
||||
},
|
||||
m_lg: {
|
||||
margin: tokens.space.lg,
|
||||
},
|
||||
m_xl: {
|
||||
margin: tokens.space.xl,
|
||||
},
|
||||
m_xxl: {
|
||||
margin: tokens.space.xxl,
|
||||
},
|
||||
mx_xxs: {
|
||||
marginLeft: tokens.space.xxs,
|
||||
marginRight: tokens.space.xxs,
|
||||
},
|
||||
mx_xs: {
|
||||
marginLeft: tokens.space.xs,
|
||||
marginRight: tokens.space.xs,
|
||||
},
|
||||
mx_sm: {
|
||||
marginLeft: tokens.space.sm,
|
||||
marginRight: tokens.space.sm,
|
||||
},
|
||||
mx_md: {
|
||||
marginLeft: tokens.space.md,
|
||||
marginRight: tokens.space.md,
|
||||
},
|
||||
mx_lg: {
|
||||
marginLeft: tokens.space.lg,
|
||||
marginRight: tokens.space.lg,
|
||||
},
|
||||
mx_xl: {
|
||||
marginLeft: tokens.space.xl,
|
||||
marginRight: tokens.space.xl,
|
||||
},
|
||||
mx_xxl: {
|
||||
marginLeft: tokens.space.xxl,
|
||||
marginRight: tokens.space.xxl,
|
||||
},
|
||||
my_xxs: {
|
||||
marginTop: tokens.space.xxs,
|
||||
marginBottom: tokens.space.xxs,
|
||||
},
|
||||
my_xs: {
|
||||
marginTop: tokens.space.xs,
|
||||
marginBottom: tokens.space.xs,
|
||||
},
|
||||
my_sm: {
|
||||
marginTop: tokens.space.sm,
|
||||
marginBottom: tokens.space.sm,
|
||||
},
|
||||
my_md: {
|
||||
marginTop: tokens.space.md,
|
||||
marginBottom: tokens.space.md,
|
||||
},
|
||||
my_lg: {
|
||||
marginTop: tokens.space.lg,
|
||||
marginBottom: tokens.space.lg,
|
||||
},
|
||||
my_xl: {
|
||||
marginTop: tokens.space.xl,
|
||||
marginBottom: tokens.space.xl,
|
||||
},
|
||||
my_xxl: {
|
||||
marginTop: tokens.space.xxl,
|
||||
marginBottom: tokens.space.xxl,
|
||||
},
|
||||
mt_xxs: {
|
||||
marginTop: tokens.space.xxs,
|
||||
},
|
||||
mt_xs: {
|
||||
marginTop: tokens.space.xs,
|
||||
},
|
||||
mt_sm: {
|
||||
marginTop: tokens.space.sm,
|
||||
},
|
||||
mt_md: {
|
||||
marginTop: tokens.space.md,
|
||||
},
|
||||
mt_lg: {
|
||||
marginTop: tokens.space.lg,
|
||||
},
|
||||
mt_xl: {
|
||||
marginTop: tokens.space.xl,
|
||||
},
|
||||
mt_xxl: {
|
||||
marginTop: tokens.space.xxl,
|
||||
},
|
||||
mb_xxs: {
|
||||
marginBottom: tokens.space.xxs,
|
||||
},
|
||||
mb_xs: {
|
||||
marginBottom: tokens.space.xs,
|
||||
},
|
||||
mb_sm: {
|
||||
marginBottom: tokens.space.sm,
|
||||
},
|
||||
mb_md: {
|
||||
marginBottom: tokens.space.md,
|
||||
},
|
||||
mb_lg: {
|
||||
marginBottom: tokens.space.lg,
|
||||
},
|
||||
mb_xl: {
|
||||
marginBottom: tokens.space.xl,
|
||||
},
|
||||
mb_xxl: {
|
||||
marginBottom: tokens.space.xxl,
|
||||
},
|
||||
ml_xxs: {
|
||||
marginLeft: tokens.space.xxs,
|
||||
},
|
||||
ml_xs: {
|
||||
marginLeft: tokens.space.xs,
|
||||
},
|
||||
ml_sm: {
|
||||
marginLeft: tokens.space.sm,
|
||||
},
|
||||
ml_md: {
|
||||
marginLeft: tokens.space.md,
|
||||
},
|
||||
ml_lg: {
|
||||
marginLeft: tokens.space.lg,
|
||||
},
|
||||
ml_xl: {
|
||||
marginLeft: tokens.space.xl,
|
||||
},
|
||||
ml_xxl: {
|
||||
marginLeft: tokens.space.xxl,
|
||||
},
|
||||
mr_xxs: {
|
||||
marginRight: tokens.space.xxs,
|
||||
},
|
||||
mr_xs: {
|
||||
marginRight: tokens.space.xs,
|
||||
},
|
||||
mr_sm: {
|
||||
marginRight: tokens.space.sm,
|
||||
},
|
||||
mr_md: {
|
||||
marginRight: tokens.space.md,
|
||||
},
|
||||
mr_lg: {
|
||||
marginRight: tokens.space.lg,
|
||||
},
|
||||
mr_xl: {
|
||||
marginRight: tokens.space.xl,
|
||||
},
|
||||
mr_xxl: {
|
||||
marginRight: tokens.space.xxl,
|
||||
},
|
||||
} as const
|
92
src/alf/index.tsx
Normal file
92
src/alf/index.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
import React from 'react'
|
||||
import {Dimensions} from 'react-native'
|
||||
import * as themes from '#/alf/themes'
|
||||
|
||||
export * as tokens from '#/alf/tokens'
|
||||
export {atoms} from '#/alf/atoms'
|
||||
export * from '#/alf/util/platform'
|
||||
|
||||
type BreakpointName = keyof typeof breakpoints
|
||||
|
||||
/*
|
||||
* Breakpoints
|
||||
*/
|
||||
const breakpoints: {
|
||||
[key: string]: number
|
||||
} = {
|
||||
gtMobile: 800,
|
||||
gtTablet: 1200,
|
||||
}
|
||||
function getActiveBreakpoints({width}: {width: number}) {
|
||||
const active: (keyof typeof breakpoints)[] = Object.keys(breakpoints).filter(
|
||||
breakpoint => width >= breakpoints[breakpoint],
|
||||
)
|
||||
|
||||
return {
|
||||
active: active[active.length - 1],
|
||||
gtMobile: active.includes('gtMobile'),
|
||||
gtTablet: active.includes('gtTablet'),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Context
|
||||
*/
|
||||
export const Context = React.createContext<{
|
||||
themeName: themes.ThemeName
|
||||
theme: themes.Theme
|
||||
breakpoints: {
|
||||
active: BreakpointName | undefined
|
||||
gtMobile: boolean
|
||||
gtTablet: boolean
|
||||
}
|
||||
}>({
|
||||
themeName: 'light',
|
||||
theme: themes.light,
|
||||
breakpoints: {
|
||||
active: undefined,
|
||||
gtMobile: false,
|
||||
gtTablet: false,
|
||||
},
|
||||
})
|
||||
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
theme: themeName,
|
||||
}: React.PropsWithChildren<{theme: themes.ThemeName}>) {
|
||||
const theme = themes[themeName]
|
||||
const [breakpoints, setBreakpoints] = React.useState(() =>
|
||||
getActiveBreakpoints({width: Dimensions.get('window').width}),
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
const listener = Dimensions.addEventListener('change', ({window}) => {
|
||||
const bp = getActiveBreakpoints({width: window.width})
|
||||
if (bp.active !== breakpoints.active) setBreakpoints(bp)
|
||||
})
|
||||
|
||||
return listener.remove
|
||||
}, [breakpoints, setBreakpoints])
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={React.useMemo(
|
||||
() => ({
|
||||
themeName: themeName,
|
||||
theme: theme,
|
||||
breakpoints,
|
||||
}),
|
||||
[theme, themeName, breakpoints],
|
||||
)}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
return React.useContext(Context).theme
|
||||
}
|
||||
|
||||
export function useBreakpoints() {
|
||||
return React.useContext(Context).breakpoints
|
||||
}
|
108
src/alf/themes.ts
Normal file
108
src/alf/themes.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
import * as tokens from '#/alf/tokens'
|
||||
import type {Mutable} from '#/alf/types'
|
||||
|
||||
export type ThemeName = 'light' | 'dark'
|
||||
export type ReadonlyTheme = typeof light
|
||||
export type Theme = Mutable<ReadonlyTheme>
|
||||
|
||||
export type Palette = {
|
||||
primary: string
|
||||
positive: string
|
||||
negative: string
|
||||
}
|
||||
|
||||
export const lightPalette: Palette = {
|
||||
primary: tokens.color.blue_500,
|
||||
positive: tokens.color.green_500,
|
||||
negative: tokens.color.red_500,
|
||||
} as const
|
||||
|
||||
export const darkPalette: Palette = {
|
||||
primary: tokens.color.blue_500,
|
||||
positive: tokens.color.green_400,
|
||||
negative: tokens.color.red_400,
|
||||
} as const
|
||||
|
||||
export const light = {
|
||||
palette: lightPalette,
|
||||
atoms: {
|
||||
text: {
|
||||
color: tokens.color.gray_1000,
|
||||
},
|
||||
text_contrast_700: {
|
||||
color: tokens.color.gray_700,
|
||||
},
|
||||
text_contrast_500: {
|
||||
color: tokens.color.gray_500,
|
||||
},
|
||||
text_inverted: {
|
||||
color: tokens.color.white,
|
||||
},
|
||||
bg: {
|
||||
backgroundColor: tokens.color.white,
|
||||
},
|
||||
bg_contrast_100: {
|
||||
backgroundColor: tokens.color.gray_100,
|
||||
},
|
||||
bg_contrast_200: {
|
||||
backgroundColor: tokens.color.gray_200,
|
||||
},
|
||||
bg_contrast_300: {
|
||||
backgroundColor: tokens.color.gray_300,
|
||||
},
|
||||
bg_positive: {
|
||||
backgroundColor: tokens.color.green_500,
|
||||
},
|
||||
bg_negative: {
|
||||
backgroundColor: tokens.color.red_400,
|
||||
},
|
||||
border: {
|
||||
borderColor: tokens.color.gray_200,
|
||||
},
|
||||
border_contrast_500: {
|
||||
borderColor: tokens.color.gray_500,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const dark: Theme = {
|
||||
palette: darkPalette,
|
||||
atoms: {
|
||||
text: {
|
||||
color: tokens.color.white,
|
||||
},
|
||||
text_contrast_700: {
|
||||
color: tokens.color.gray_300,
|
||||
},
|
||||
text_contrast_500: {
|
||||
color: tokens.color.gray_500,
|
||||
},
|
||||
text_inverted: {
|
||||
color: tokens.color.gray_1000,
|
||||
},
|
||||
bg: {
|
||||
backgroundColor: tokens.color.gray_1000,
|
||||
},
|
||||
bg_contrast_100: {
|
||||
backgroundColor: tokens.color.gray_900,
|
||||
},
|
||||
bg_contrast_200: {
|
||||
backgroundColor: tokens.color.gray_800,
|
||||
},
|
||||
bg_contrast_300: {
|
||||
backgroundColor: tokens.color.gray_700,
|
||||
},
|
||||
bg_positive: {
|
||||
backgroundColor: tokens.color.green_400,
|
||||
},
|
||||
bg_negative: {
|
||||
backgroundColor: tokens.color.red_400,
|
||||
},
|
||||
border: {
|
||||
borderColor: tokens.color.gray_800,
|
||||
},
|
||||
border_contrast_500: {
|
||||
borderColor: tokens.color.gray_500,
|
||||
},
|
||||
},
|
||||
}
|
100
src/alf/tokens.ts
Normal file
100
src/alf/tokens.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
const BLUE_HUE = 211
|
||||
const GRAYSCALE_SATURATION = 22
|
||||
|
||||
export const color = {
|
||||
white: '#FFFFFF',
|
||||
|
||||
gray_0: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 100%)`,
|
||||
gray_100: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 95%)`,
|
||||
gray_200: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 85%)`,
|
||||
gray_300: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 75%)`,
|
||||
gray_400: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 65%)`,
|
||||
gray_500: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 55%)`,
|
||||
gray_600: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 45%)`,
|
||||
gray_700: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 35%)`,
|
||||
gray_800: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 25%)`,
|
||||
gray_900: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 15%)`,
|
||||
gray_1000: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 5%)`,
|
||||
|
||||
blue_0: `hsl(${BLUE_HUE}, 99%, 100%)`,
|
||||
blue_100: `hsl(${BLUE_HUE}, 99%, 93%)`,
|
||||
blue_200: `hsl(${BLUE_HUE}, 99%, 83%)`,
|
||||
blue_300: `hsl(${BLUE_HUE}, 99%, 73%)`,
|
||||
blue_400: `hsl(${BLUE_HUE}, 99%, 63%)`,
|
||||
blue_500: `hsl(${BLUE_HUE}, 99%, 53%)`,
|
||||
blue_600: `hsl(${BLUE_HUE}, 99%, 43%)`,
|
||||
blue_700: `hsl(${BLUE_HUE}, 99%, 33%)`,
|
||||
blue_800: `hsl(${BLUE_HUE}, 99%, 23%)`,
|
||||
blue_900: `hsl(${BLUE_HUE}, 99%, 13%)`,
|
||||
blue_1000: `hsl(${BLUE_HUE}, 99%, 8%)`,
|
||||
|
||||
green_0: `hsl(130, 60%, 100%)`,
|
||||
green_100: `hsl(130, 60%, 95%)`,
|
||||
green_200: `hsl(130, 60%, 85%)`,
|
||||
green_300: `hsl(130, 60%, 75%)`,
|
||||
green_400: `hsl(130, 60%, 65%)`,
|
||||
green_500: `hsl(130, 60%, 55%)`,
|
||||
green_600: `hsl(130, 60%, 45%)`,
|
||||
green_700: `hsl(130, 60%, 35%)`,
|
||||
green_800: `hsl(130, 60%, 25%)`,
|
||||
green_900: `hsl(130, 60%, 15%)`,
|
||||
green_1000: `hsl(130, 60%, 5%)`,
|
||||
|
||||
red_0: `hsl(349, 96%, 100%)`,
|
||||
red_100: `hsl(349, 96%, 95%)`,
|
||||
red_200: `hsl(349, 96%, 85%)`,
|
||||
red_300: `hsl(349, 96%, 75%)`,
|
||||
red_400: `hsl(349, 96%, 65%)`,
|
||||
red_500: `hsl(349, 96%, 55%)`,
|
||||
red_600: `hsl(349, 96%, 45%)`,
|
||||
red_700: `hsl(349, 96%, 35%)`,
|
||||
red_800: `hsl(349, 96%, 25%)`,
|
||||
red_900: `hsl(349, 96%, 15%)`,
|
||||
red_1000: `hsl(349, 96%, 5%)`,
|
||||
} as const
|
||||
|
||||
export const space = {
|
||||
xxs: 2,
|
||||
xs: 4,
|
||||
sm: 8,
|
||||
md: 12,
|
||||
lg: 18,
|
||||
xl: 24,
|
||||
xxl: 32,
|
||||
} as const
|
||||
|
||||
export const fontSize = {
|
||||
xxs: 10,
|
||||
xs: 12,
|
||||
sm: 14,
|
||||
md: 16,
|
||||
lg: 18,
|
||||
xl: 22,
|
||||
xxl: 26,
|
||||
} as const
|
||||
|
||||
// TODO test
|
||||
export const lineHeight = {
|
||||
none: 1,
|
||||
normal: 1.5,
|
||||
relaxed: 1.625,
|
||||
} as const
|
||||
|
||||
export const borderRadius = {
|
||||
sm: 8,
|
||||
md: 12,
|
||||
full: 999,
|
||||
} as const
|
||||
|
||||
export const fontWeight = {
|
||||
normal: '400',
|
||||
semibold: '600',
|
||||
bold: '900',
|
||||
} as const
|
||||
|
||||
export type Color = keyof typeof color
|
||||
export type Space = keyof typeof space
|
||||
export type FontSize = keyof typeof fontSize
|
||||
export type LineHeight = keyof typeof lineHeight
|
||||
export type BorderRadius = keyof typeof borderRadius
|
||||
export type FontWeight = keyof typeof fontWeight
|
16
src/alf/types.ts
Normal file
16
src/alf/types.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
type LiteralToCommon<T extends PropertyKey> = T extends number
|
||||
? number
|
||||
: T extends string
|
||||
? string
|
||||
: T extends symbol
|
||||
? symbol
|
||||
: never
|
||||
|
||||
/**
|
||||
* @see https://stackoverflow.com/questions/68249999/use-as-const-in-typescript-without-adding-readonly-modifiers
|
||||
*/
|
||||
export type Mutable<T> = {
|
||||
-readonly [K in keyof T]: T[K] extends PropertyKey
|
||||
? LiteralToCommon<T[K]>
|
||||
: Mutable<T[K]>
|
||||
}
|
25
src/alf/util/platform.ts
Normal file
25
src/alf/util/platform.ts
Normal file
|
@ -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,
|
||||
})
|
||||
}
|
10
src/alf/util/useColorModeTheme.ts
Normal file
10
src/alf/util/useColorModeTheme.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import {useColorScheme} from 'react-native'
|
||||
|
||||
import * as persisted from '#/state/persisted'
|
||||
|
||||
export function useColorModeTheme(
|
||||
theme: persisted.Schema['colorMode'],
|
||||
): 'light' | 'dark' {
|
||||
const colorScheme = useColorScheme()
|
||||
return (theme === 'system' ? colorScheme : theme) || 'light'
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue