about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/Button.tsx204
-rw-r--r--src/view/com/Typography.tsx104
-rw-r--r--src/view/screens/DebugNew.tsx541
3 files changed, 849 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]}
+    />
+  )
+}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
new file mode 100644
index 000000000..0b7c5f03b
--- /dev/null
+++ b/src/view/screens/DebugNew.tsx
@@ -0,0 +1,541 @@
+import React from 'react'
+import {View} from 'react-native'
+import {CenteredView, ScrollView} from '#/view/com/util/Views'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+
+import {useSetColorMode} from '#/state/shell'
+import * as tokens from '#/alf/tokens'
+import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
+import {Button, ButtonText} from '#/view/com/Button'
+import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
+
+function ThemeSelector() {
+  const setColorMode = useSetColorMode()
+
+  return (
+    <View style={[a.flex_row, a.gap_md]}>
+      <Button
+        type="secondary"
+        size="small"
+        onPress={() => setColorMode('system')}>
+        System
+      </Button>
+      <Button
+        type="secondary"
+        size="small"
+        onPress={() => setColorMode('light')}>
+        Light
+      </Button>
+      <Button
+        type="secondary"
+        size="small"
+        onPress={() => setColorMode('dark')}>
+        Dark
+      </Button>
+    </View>
+  )
+}
+
+function BreakpointDebugger() {
+  const t = useTheme()
+  const breakpoints = useBreakpoints()
+
+  return (
+    <View>
+      <H3 style={[a.pb_md]}>Breakpoint Debugger</H3>
+      <Text style={[a.pb_md]}>
+        Current breakpoint: {!breakpoints.gtMobile && <Text>mobile</Text>}
+        {breakpoints.gtMobile && !breakpoints.gtTablet && <Text>tablet</Text>}
+        {breakpoints.gtTablet && <Text>desktop</Text>}
+      </Text>
+      <Text
+        style={[a.p_md, t.atoms.bg_contrast_100, {fontFamily: 'monospace'}]}>
+        {JSON.stringify(breakpoints, null, 2)}
+      </Text>
+    </View>
+  )
+}
+
+function ThemedSection() {
+  const t = useTheme()
+
+  return (
+    <View style={[t.atoms.bg, a.gap_md, a.p_xl]}>
+      <H3 style={[a.font_bold]}>theme.atoms.text</H3>
+      <View style={[a.flex_1, t.atoms.border, a.border_t]} />
+      <H3 style={[a.font_bold, t.atoms.text_contrast_700]}>
+        theme.atoms.text_contrast_700
+      </H3>
+      <View style={[a.flex_1, t.atoms.border, a.border_t]} />
+      <H3 style={[a.font_bold, t.atoms.text_contrast_500]}>
+        theme.atoms.text_contrast_500
+      </H3>
+      <View style={[a.flex_1, t.atoms.border_contrast_500, a.border_t]} />
+
+      <View style={[a.flex_row, a.gap_md]}>
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg,
+            a.align_center,
+            a.justify_center,
+            {height: 60},
+          ]}>
+          <Text>theme.bg</Text>
+        </View>
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg_contrast_100,
+            a.align_center,
+            a.justify_center,
+            {height: 60},
+          ]}>
+          <Text>theme.bg_contrast_100</Text>
+        </View>
+      </View>
+      <View style={[a.flex_row, a.gap_md]}>
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg_contrast_200,
+            a.align_center,
+            a.justify_center,
+            {height: 60},
+          ]}>
+          <Text>theme.bg_contrast_200</Text>
+        </View>
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg_contrast_300,
+            a.align_center,
+            a.justify_center,
+            {height: 60},
+          ]}>
+          <Text>theme.bg_contrast_300</Text>
+        </View>
+      </View>
+      <View style={[a.flex_row, a.gap_md]}>
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg_positive,
+            a.align_center,
+            a.justify_center,
+            {height: 60},
+          ]}>
+          <Text>theme.bg_positive</Text>
+        </View>
+        <View
+          style={[
+            a.flex_1,
+            t.atoms.bg_negative,
+            a.align_center,
+            a.justify_center,
+            {height: 60},
+          ]}>
+          <Text>theme.bg_negative</Text>
+        </View>
+      </View>
+    </View>
+  )
+}
+
+export function DebugScreen() {
+  const t = useTheme()
+
+  return (
+    <ScrollView>
+      <CenteredView style={[t.atoms.bg]}>
+        <View style={[a.p_xl, a.gap_xxl, {paddingBottom: 200}]}>
+          <ThemeSelector />
+
+          <Alf theme="light">
+            <ThemedSection />
+          </Alf>
+          <Alf theme="dark">
+            <ThemedSection />
+          </Alf>
+
+          <H1>Heading 1</H1>
+          <H2>Heading 2</H2>
+          <H3>Heading 3</H3>
+          <H4>Heading 4</H4>
+          <H5>Heading 5</H5>
+          <H6>Heading 6</H6>
+
+          <Text style={[a.text_xxl]}>atoms.text_xxl</Text>
+          <Text style={[a.text_xl]}>atoms.text_xl</Text>
+          <Text style={[a.text_lg]}>atoms.text_lg</Text>
+          <Text style={[a.text_md]}>atoms.text_md</Text>
+          <Text style={[a.text_sm]}>atoms.text_sm</Text>
+          <Text style={[a.text_xs]}>atoms.text_xs</Text>
+          <Text style={[a.text_xxs]}>atoms.text_xxs</Text>
+
+          <View style={[a.gap_md, a.align_start]}>
+            <Button>
+              {({state}) => (
+                <View style={[a.p_md, a.rounded_full, t.atoms.bg_contrast_300]}>
+                  <Text>Unstyled button, state: {JSON.stringify(state)}</Text>
+                </View>
+              )}
+            </Button>
+
+            <Button type="primary" size="small">
+              Button
+            </Button>
+            <Button type="secondary" size="small">
+              Button
+            </Button>
+
+            <Button type="primary" size="large">
+              Button
+            </Button>
+            <Button type="secondary" size="large">
+              Button
+            </Button>
+
+            <Button type="secondary" size="small">
+              {({type, size}) => (
+                <>
+                  <FontAwesomeIcon icon={['fas', 'plus']} size={12} />
+                  <ButtonText type={type} size={size}>
+                    With an icon
+                  </ButtonText>
+                </>
+              )}
+            </Button>
+            <Button type="primary" size="large">
+              {({state: _state, ...rest}) => (
+                <>
+                  <FontAwesomeIcon icon={['fas', 'plus']} />
+                  <ButtonText {...rest}>With an icon</ButtonText>
+                </>
+              )}
+            </Button>
+          </View>
+
+          <View style={[a.gap_md]}>
+            <View style={[a.flex_row, a.gap_md]}>
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_0},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_100},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_200},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_300},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_400},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_500},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_600},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_700},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_800},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_900},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.gray_1000},
+                ]}
+              />
+            </View>
+
+            <View style={[a.flex_row, a.gap_md]}>
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_0},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_100},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_200},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_300},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_400},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_500},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_600},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_700},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_800},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_900},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.blue_1000},
+                ]}
+              />
+            </View>
+            <View style={[a.flex_row, a.gap_md]}>
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_0},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_100},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_200},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_300},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_400},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_500},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_600},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_700},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_800},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_900},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.green_1000},
+                ]}
+              />
+            </View>
+            <View style={[a.flex_row, a.gap_md]}>
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_0},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_100},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_200},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_300},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_400},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_500},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_600},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_700},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_800},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_900},
+                ]}
+              />
+              <View
+                style={[
+                  a.flex_1,
+                  {height: 60, backgroundColor: tokens.color.red_1000},
+                ]}
+              />
+            </View>
+          </View>
+
+          <View>
+            <H3 style={[a.pb_md, a.font_bold]}>Spacing</H3>
+
+            <View style={[a.gap_md]}>
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>xxs (2px)</Text>
+                <View style={[a.flex_1, a.pt_xxs, t.atoms.bg_contrast_300]} />
+              </View>
+
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>xs (4px)</Text>
+                <View style={[a.flex_1, a.pt_xs, t.atoms.bg_contrast_300]} />
+              </View>
+
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>sm (8px)</Text>
+                <View style={[a.flex_1, a.pt_sm, t.atoms.bg_contrast_300]} />
+              </View>
+
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>md (12px)</Text>
+                <View style={[a.flex_1, a.pt_md, t.atoms.bg_contrast_300]} />
+              </View>
+
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>lg (18px)</Text>
+                <View style={[a.flex_1, a.pt_lg, t.atoms.bg_contrast_300]} />
+              </View>
+
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>xl (24px)</Text>
+                <View style={[a.flex_1, a.pt_xl, t.atoms.bg_contrast_300]} />
+              </View>
+
+              <View style={[a.flex_row, a.align_center]}>
+                <Text style={{width: 80}}>xxl (32px)</Text>
+                <View style={[a.flex_1, a.pt_xxl, t.atoms.bg_contrast_300]} />
+              </View>
+            </View>
+          </View>
+
+          <BreakpointDebugger />
+        </View>
+      </CenteredView>
+    </ScrollView>
+  )
+}