From 1162d38f0ff171807ed049b3082c73269f23d22d Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 4 Jan 2024 12:09:04 -0600 Subject: [PATCH] Smooth brain --- src/alf/atoms.ts | 564 ++++++++++++++++++++++-------- src/alf/components/Button.tsx | 171 --------- src/alf/components/Typography.tsx | 74 ---- src/alf/themes.ts | 4 +- src/alf/tokens.ts | 13 +- src/alf/types.ts | 16 + 6 files changed, 448 insertions(+), 394 deletions(-) delete mode 100644 src/alf/components/Button.tsx delete mode 100644 src/alf/components/Typography.tsx create mode 100644 src/alf/types.ts diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 9b054cf355..5c41f8c98a 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -1,133 +1,5 @@ -import {TextStyle} from 'react-native' import * as tokens from '#/alf/tokens' -const gap = Object.keys(tokens.space).reduce((acc, key) => { - const k = key as tokens.Space - acc[`gap_${k}`] = { - gap: tokens.space[k], - } - return acc -}, {} as Record<`gap_${tokens.Space}`, {gap: number}>) - -const fontSize = Object.keys(tokens.fontSize).reduce((acc, key) => { - const k = key as tokens.FontSize - acc[`text_${k}`] = { - fontSize: tokens.fontSize[k], - lineHeight: tokens.fontSize[k], - } - return acc -}, {} as Record<`text_${tokens.FontSize}`, {fontSize: number; lineHeight: number}>) - -const lineHeight = Object.keys(tokens.lineHeight).reduce((acc, key) => { - const k = key as tokens.LineHeight - acc[`leading_${k}`] = { - lineHeight: tokens.lineHeight[k], - } - return acc -}, {} as Record<`leading_${tokens.LineHeight}`, {lineHeight: number}>) - -const fontWeight = Object.keys(tokens.fontWeight).reduce((acc, key) => { - const k = key as tokens.FontWeight - acc[`font_${k}`] = { - fontWeight: tokens.fontWeight[k] as TextStyle['fontWeight'], - } - return acc -}, {} as Record<`font_${tokens.FontWeight}`, {fontWeight: TextStyle['fontWeight']}>) - -const radius = Object.keys(tokens.borderRadius).reduce((acc, key) => { - const k = key as tokens.BorderRadius - acc[`rounded_${k}`] = { - borderRadius: tokens.borderRadius[k], - } - return acc -}, {} as Record<`rounded_${tokens.BorderRadius}`, {borderRadius: number}>) - -const padding = Object.keys(tokens.space).reduce( - (acc, key) => { - const k = key as tokens.Space - const value = tokens.space[k] - return { - ...acc, - [`p_${k}`]: { - padding: value, - }, - [`px_${k}`]: { - paddingLeft: value, - paddingRight: value, - }, - [`py_${k}`]: { - paddingTop: value, - paddingBottom: value, - }, - [`pt_${k}`]: { - paddingTop: value, - }, - [`pb_${k}`]: { - paddingBottom: value, - }, - [`pl_${k}`]: { - paddingLeft: value, - }, - [`pr_${k}`]: { - paddingRight: value, - }, - } - }, - {} as Record< - `${'p' | 'px' | 'py' | 'pt' | 'pb' | 'pl' | 'pr'}_${tokens.Space}`, - { - padding?: number - paddingLeft?: number - paddingRight?: number - paddingTop?: number - paddingBottom?: number - } - >, -) - -const margin = Object.keys(tokens.space).reduce( - (acc, key) => { - const k = key as tokens.Space - const value = tokens.space[k] - return { - ...acc, - [`m_${k}`]: { - margin: value, - }, - [`mx_${k}`]: { - margin: value, - marginRight: value, - }, - [`my_${k}`]: { - marginTop: value, - marginBottom: value, - }, - [`mt_${k}`]: { - marginTop: value, - }, - [`mb_${k}`]: { - marginBottom: value, - }, - [`ml_${k}`]: { - margin: value, - }, - [`mr_${k}`]: { - marginRight: value, - }, - } - }, - {} as Record< - `${'m' | 'mx' | 'my' | 'mt' | 'mb' | 'ml' | 'mr'}_${tokens.Space}`, - { - margin?: number - marginLeft?: number - marginRight?: number - marginTop?: number - marginBottom?: number - } - >, -) - export const atoms = { /* * Positioning @@ -160,18 +32,53 @@ export const atoms = { zIndex: 50, }, - ...radius, + /* + * Width + */ + w_full: { + width: '100%', + }, + h_full: { + height: '100%', + }, /* - * Spacing + * Border radius */ - ...padding, - ...margin, + rounded_sm: { + borderRadius: tokens.borderRadius.sm, + }, + rounded_md: { + borderRadius: tokens.borderRadius.md, + }, + rounded_full: { + borderRadius: tokens.borderRadius.full, + }, /* * Flex */ - ...gap, + 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', }, @@ -218,9 +125,64 @@ export const atoms = { text_right: { textAlign: 'right', }, - ...fontSize, - ...lineHeight, - ...fontWeight, + 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_xxs: { + lineHeight: tokens.fontSize.xxs, + }, + leading_xs: { + lineHeight: tokens.fontSize.xs, + }, + leading_sm: { + lineHeight: tokens.fontSize.sm, + }, + leading_md: { + lineHeight: tokens.fontSize.md, + }, + leading_lg: { + lineHeight: tokens.fontSize.lg, + }, + leading_xl: { + lineHeight: tokens.fontSize.xl, + }, + leading_xxl: { + lineHeight: tokens.fontSize.xxl, + }, + font_normal: { + fontWeight: tokens.fontWeight.normal, + }, + font_semibold: { + fontWeight: tokens.fontWeight.semibold, + }, + font_bold: { + fontWeight: tokens.fontWeight.bold, + }, /* * Border @@ -236,12 +198,332 @@ export const atoms = { }, /* - * Width + * Padding */ - w_full: { - width: '100%', + p_xxs: { + padding: tokens.space.xxs, }, - h_full: { - height: '100%', + 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 diff --git a/src/alf/components/Button.tsx b/src/alf/components/Button.tsx deleted file mode 100644 index 9179f85b92..0000000000 --- a/src/alf/components/Button.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react' -import {Pressable} from 'react-native' - -import {useStyles} from '#/alf/system' -import {Text} from '#/alf/components/Typography' - -export type ButtonType = - | 'primary' - | 'secondary' - | 'tertiary' - | 'positive' - | 'negative' -export type ButtonSize = 'small' | 'large' - -export type ButtonProps = React.ComponentProps & { - type?: ButtonType - size?: ButtonSize -} - -const colorVariants: { - [key in ButtonType]: Parameters[0] -} = { - primary: { - pressable: { - bg: 'primary', - }, - pressableHovered: { - bg: 'primary', - }, - pressableFocused: { - bg: 'primary', - }, - pressablePressed: { - bg: 'primary', - }, - text: { - color: 'white', - }, - }, - secondary: { - pressable: { - bg: 'l2', - }, - pressableHovered: { - bg: 'l1', - }, - pressableFocused: { - bg: 'l1', - }, - pressablePressed: { - bg: 'l1', - }, - text: { - color: 'l6', - }, - }, - tertiary: {}, - positive: { - pressable: { - bg: 'green', - }, - pressableHovered: { - bg: 'green', - }, - pressableFocused: { - bg: 'green', - }, - pressablePressed: { - bg: 'green', - }, - text: { - color: 'l0', - }, - }, - negative: { - pressable: { - bg: 'red', - }, - pressableHovered: { - bg: 'red', - }, - pressableFocused: { - bg: 'red', - }, - pressablePressed: { - bg: 'red', - }, - text: { - color: 'l0', - }, - }, -} - -const sizeVariants: { - [key in ButtonSize]: Parameters[0] -} = { - small: { - pressable: { - py: 'm', - px: 'l', - radius: 'm', - }, - text: { - fontSize: 'm', - fontWeight: 'semi', - }, - }, - large: { - pressable: { - py: 'm', - px: 'l', - radius: 'm', - }, - text: { - fontSize: 'm', - fontWeight: 'semi', - }, - }, -} - -export function Button({ - children, - type = 'primary', - size = 'large', - ...rest -}: React.PropsWithChildren) { - const styles = useStyles( - React.useMemo[0]>( - () => ({ - pressable: { - ...colorVariants[type].pressable, - ...sizeVariants[size].pressable, - }, - text: { - textAlign: 'center', - ...colorVariants[type].text, - ...sizeVariants[size].text, - }, - pressableHovered: { - ...colorVariants[type].pressableHovered, - }, - pressableFocused: { - ...colorVariants[type].pressableFocused, - }, - pressablePressed: { - ...colorVariants[type].pressablePressed, - }, - }), - [type, size], - ), - ) - - return ( - - [ - styles.pressable, - state.hovered && styles.pressableHovered, - state.focused && styles.pressableFocused, - state.pressed && styles.pressablePressed, - ].filter(Boolean) - }> - {typeof children === 'string' ? ( - {children} - ) : ( - children - )} - - ) -} diff --git a/src/alf/components/Typography.tsx b/src/alf/components/Typography.tsx deleted file mode 100644 index 52a74ee61e..0000000000 --- a/src/alf/components/Typography.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import {Text as RNText} from 'react-native' -import {styled} from '#/alf/system' -import {web} from '#/alf/util/platform' - -export const Text = styled(RNText, { - color: 'l7', - 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: 'l7', - fontSize: 'l', - gtMobile: { - fontSize: 'xl', - }, - ...web({ - role: 'heading', - 'aria-level': 1, - }), -}) -export const H2 = styled(RNText, { - color: 'l7', - fontSize: 'm', - gtMobile: { - fontSize: 'l', - }, - ...web({ - role: 'heading', - 'aria-level': 2, - }), -}) -export const H3 = styled(RNText, { - color: 'l7', - fontSize: 'm', - ...web({ - role: 'heading', - 'aria-level': 3, - }), -}) -export const H4 = styled(RNText, { - color: 'l7', - fontSize: 's', - ...web({ - role: 'heading', - 'aria-level': 4, - }), -}) -export const H5 = styled(RNText, { - color: 'l7', - fontSize: 'xs', - ...web({ - role: 'heading', - 'aria-level': 5, - }), -}) -export const H6 = styled(RNText, { - color: 'l7', - fontSize: 'xxs', - ...web({ - role: 'heading', - 'aria-level': 6, - }), -}) -export const P = styled(RNText, { - color: 'l7', - fontSize: 's', - ...web({ - role: 'paragraph', - }), -}) diff --git a/src/alf/themes.ts b/src/alf/themes.ts index f5af08e567..aae5c58931 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -1,7 +1,9 @@ import * as tokens from '#/alf/tokens' +import type {Mutable} from '#/alf/types' export type ThemeName = 'light' | 'dark' -export type Theme = typeof light +export type ReadonlyTheme = typeof light +export type Theme = Mutable export type Palette = { primary: string diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts index 159a7213cd..4034e0deb4 100644 --- a/src/alf/tokens.ts +++ b/src/alf/tokens.ts @@ -51,7 +51,7 @@ export const color = { 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, @@ -61,7 +61,7 @@ export const space = { lg: 18, xl: 24, xxl: 32, -} +} as const export const fontSize = { xxs: 10, @@ -71,27 +71,26 @@ export const fontSize = { 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, - xl: 36, 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 diff --git a/src/alf/types.ts b/src/alf/types.ts new file mode 100644 index 0000000000..76ac05d40e --- /dev/null +++ b/src/alf/types.ts @@ -0,0 +1,16 @@ +type LiteralToCommon = 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 = { + -readonly [K in keyof T]: T[K] extends PropertyKey + ? LiteralToCommon + : Mutable +}