diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/Button.tsx | 204 | ||||
-rw-r--r-- | src/view/com/Typography.tsx | 104 |
2 files changed, 308 insertions, 0 deletions
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx new file mode 100644 index 000000000..d1f70d4ae --- /dev/null +++ b/src/view/com/Button.tsx @@ -0,0 +1,204 @@ +import React from 'react' +import {Pressable, Text, PressableProps, TextProps} from 'react-native' +import * as tokens from '#/alf/tokens' +import {atoms} from '#/alf' + +export type ButtonType = + | 'primary' + | 'secondary' + | 'tertiary' + | 'positive' + | 'negative' +export type ButtonSize = 'small' | 'large' + +export type VariantProps = { + type?: ButtonType + size?: ButtonSize +} +type ButtonState = { + pressed: boolean + hovered: boolean + focused: boolean +} +export type ButtonProps = Omit<PressableProps, 'children'> & + VariantProps & { + children: + | ((props: { + state: ButtonState + type?: ButtonType + size?: ButtonSize + }) => React.ReactNode) + | React.ReactNode + | string + } +export type ButtonTextProps = TextProps & VariantProps + +export function Button({children, style, type, size, ...rest}: ButtonProps) { + const {baseStyles, hoverStyles} = React.useMemo(() => { + const baseStyles = [] + const hoverStyles = [] + + switch (type) { + case 'primary': + baseStyles.push({ + backgroundColor: tokens.color.blue_500, + }) + break + case 'secondary': + baseStyles.push({ + backgroundColor: tokens.color.gray_200, + }) + hoverStyles.push({ + backgroundColor: tokens.color.gray_100, + }) + break + default: + } + + switch (size) { + case 'large': + baseStyles.push( + atoms.py_md, + atoms.px_xl, + atoms.rounded_md, + atoms.gap_sm, + ) + break + case 'small': + baseStyles.push( + atoms.py_sm, + atoms.px_md, + atoms.rounded_sm, + atoms.gap_xs, + ) + break + default: + } + + return { + baseStyles, + hoverStyles, + } + }, [type, size]) + + const [state, setState] = React.useState({ + pressed: false, + hovered: false, + focused: false, + }) + + const onPressIn = React.useCallback(() => { + setState(s => ({ + ...s, + pressed: true, + })) + }, [setState]) + const onPressOut = React.useCallback(() => { + setState(s => ({ + ...s, + pressed: false, + })) + }, [setState]) + const onHoverIn = React.useCallback(() => { + setState(s => ({ + ...s, + hovered: true, + })) + }, [setState]) + const onHoverOut = React.useCallback(() => { + setState(s => ({ + ...s, + hovered: false, + })) + }, [setState]) + const onFocus = React.useCallback(() => { + setState(s => ({ + ...s, + focused: true, + })) + }, [setState]) + const onBlur = React.useCallback(() => { + setState(s => ({ + ...s, + focused: false, + })) + }, [setState]) + + return ( + <Pressable + {...rest} + style={state => [ + atoms.flex_row, + atoms.align_center, + ...baseStyles, + ...(state.hovered ? hoverStyles : []), + typeof style === 'function' ? style(state) : style, + ]} + onPressIn={onPressIn} + onPressOut={onPressOut} + onHoverIn={onHoverIn} + onHoverOut={onHoverOut} + onFocus={onFocus} + onBlur={onBlur}> + {typeof children === 'string' ? ( + <ButtonText type={type} size={size}> + {children} + </ButtonText> + ) : typeof children === 'function' ? ( + children({state, type, size}) + ) : ( + children + )} + </Pressable> + ) +} + +export function ButtonText({ + children, + style, + type, + size, + ...rest +}: ButtonTextProps) { + const textStyles = React.useMemo(() => { + const base = [] + + switch (type) { + case 'primary': + base.push({color: tokens.color.white}) + break + case 'secondary': + base.push({ + color: tokens.color.gray_700, + }) + break + default: + } + + switch (size) { + case 'small': + base.push(atoms.text_sm, {paddingBottom: 1}) + break + case 'large': + base.push(atoms.text_md, {paddingBottom: 1}) + break + default: + } + + return base + }, [type, size]) + + return ( + <Text + {...rest} + style={[ + atoms.flex_1, + atoms.font_semibold, + atoms.text_center, + ...textStyles, + style, + ]}> + {children} + </Text> + ) +} diff --git a/src/view/com/Typography.tsx b/src/view/com/Typography.tsx new file mode 100644 index 000000000..6579c2e51 --- /dev/null +++ b/src/view/com/Typography.tsx @@ -0,0 +1,104 @@ +import React from 'react' +import {Text as RNText, TextProps} from 'react-native' +import {useTheme, atoms, web} from '#/alf' + +export function Text({style, ...rest}: TextProps) { + const t = useTheme() + return <RNText style={[atoms.text_sm, t.atoms.text, style]} {...rest} /> +} + +export function H1({style, ...rest}: TextProps) { + const t = useTheme() + const attr = + web({ + role: 'heading', + 'aria-level': 1, + }) || {} + return ( + <RNText + {...attr} + {...rest} + style={[atoms.text_xl, atoms.font_bold, t.atoms.text, style]} + /> + ) +} + +export function H2({style, ...rest}: TextProps) { + const t = useTheme() + const attr = + web({ + role: 'heading', + 'aria-level': 2, + }) || {} + return ( + <RNText + {...attr} + {...rest} + style={[atoms.text_lg, atoms.font_bold, t.atoms.text, style]} + /> + ) +} + +export function H3({style, ...rest}: TextProps) { + const t = useTheme() + const attr = + web({ + role: 'heading', + 'aria-level': 3, + }) || {} + return ( + <RNText + {...attr} + {...rest} + style={[atoms.text_md, atoms.font_bold, t.atoms.text, style]} + /> + ) +} + +export function H4({style, ...rest}: TextProps) { + const t = useTheme() + const attr = + web({ + role: 'heading', + 'aria-level': 4, + }) || {} + return ( + <RNText + {...attr} + {...rest} + style={[atoms.text_sm, atoms.font_bold, t.atoms.text, style]} + /> + ) +} + +export function H5({style, ...rest}: TextProps) { + const t = useTheme() + const attr = + web({ + role: 'heading', + 'aria-level': 5, + }) || {} + return ( + <RNText + {...attr} + {...rest} + style={[atoms.text_xs, atoms.font_bold, t.atoms.text, style]} + /> + ) +} + +export function H6({style, ...rest}: TextProps) { + const t = useTheme() + const attr = + web({ + role: 'heading', + 'aria-level': 6, + }) || {} + return ( + <RNText + {...attr} + {...rest} + style={[atoms.text_xxs, atoms.font_bold, t.atoms.text, style]} + /> + ) +} |