New component library based on ALF (#2459)

* Install on native as well

* Add button and link components

* Comments

* Use new prop

* Add some form elements

* Add labels to input

* Fix line height, add suffix

* Date inputs

* Autofill styles

* Clean up InputDate types

* Improve types for InputText, value handling

* Enforce a11y props on buttons

* Add Dialog, Portal

* Dialog contents

* Native dialog

* Clean up

* Fix animations

* Improvements to web modal, exiting still broken

* Clean up dialog types

* Add Prompt, Dialog refinement, mobile refinement

* Integrate new design tokens, reorg storybook

* Button colors

* Dim mode

* Reorg

* Some styles

* Toggles

* Improve a11y

* Autosize dialog, handle max height, Dialog.ScrolLView not working

* Try to use BottomSheet's own APIs

* Scrollable dialogs

* Add web shadow

* Handle overscroll

* Styles

* Dialog text input

* Shadows

* Button focus states

* Button pressed states

* Gradient poc

* Gradient colors and hovers

* Add hrefAttrs to Link

* Some more a11y

* Toggle invalid states

* Update dialog descriptions for demo

* Icons

* WIP Toggle cleanup

* Refactor toggle to not rely on immediate children

* Make Toggle controlled

* Clean up Toggles storybook

* ToggleButton styles

* Improve a11y labels

* ToggleButton hover darkmode

* Some i18n

* Refactor input

* Allow extension of input

* Remove old input

* Improve icons, add CalendarDays

* Refactor DateField, web done

* Add label example

* Clean up old InputDate, DateField android, text area example

* Consistent imports

* Button context, icons

* Add todo

* Add closeAllDialogs control

* Alignment

* Expand color palette

* Hitslops, add shortcut to Storybook in dev

* Fix multiline on ios

* Mark dialog close button as unused
This commit is contained in:
Eric Bailey 2024-01-18 20:28:04 -06:00 committed by GitHub
parent 9cbd3c0937
commit 66b8774ecb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 4683 additions and 968 deletions

View file

@ -1,541 +0,0 @@
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>
)
}

View file

@ -0,0 +1,25 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
import {Text, H3} from '#/components/Typography'
export function Breakpoints() {
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>
)
}

View file

@ -0,0 +1,124 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {
Button,
ButtonVariant,
ButtonColor,
ButtonIcon,
ButtonText,
} from '#/components/Button'
import {H1} from '#/components/Typography'
import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
export function Buttons() {
return (
<View style={[a.gap_md]}>
<H1>Buttons</H1>
<View style={[a.flex_row, a.flex_wrap, a.gap_md, a.align_start]}>
{['primary', 'secondary', 'negative'].map(color => (
<View key={color} style={[a.gap_md, a.align_start]}>
{['solid', 'outline', 'ghost'].map(variant => (
<React.Fragment key={variant}>
<Button
variant={variant as ButtonVariant}
color={color as ButtonColor}
size="large"
label="Click here">
Button
</Button>
<Button
disabled
variant={variant as ButtonVariant}
color={color as ButtonColor}
size="large"
label="Click here">
Button
</Button>
</React.Fragment>
))}
</View>
))}
<View style={[a.flex_row, a.gap_md, a.align_start]}>
<View style={[a.gap_md, a.align_start]}>
{['gradient_sky', 'gradient_midnight', 'gradient_sunrise'].map(
name => (
<React.Fragment key={name}>
<Button
variant="gradient"
color={name as ButtonColor}
size="large"
label="Click here">
Button
</Button>
<Button
disabled
variant="gradient"
color={name as ButtonColor}
size="large"
label="Click here">
Button
</Button>
</React.Fragment>
),
)}
</View>
<View style={[a.gap_md, a.align_start]}>
{['gradient_sunset', 'gradient_nordic', 'gradient_bonfire'].map(
name => (
<React.Fragment key={name}>
<Button
variant="gradient"
color={name as ButtonColor}
size="large"
label="Click here">
Button
</Button>
<Button
disabled
variant="gradient"
color={name as ButtonColor}
size="large"
label="Click here">
Button
</Button>
</React.Fragment>
),
)}
</View>
</View>
<Button
variant="gradient"
color="gradient_sky"
size="large"
label="Link out">
<ButtonText>Link out</ButtonText>
<ButtonIcon icon={ArrowTopRight} />
</Button>
<Button
variant="gradient"
color="gradient_sky"
size="small"
label="Link out">
<ButtonText>Link out</ButtonText>
<ButtonIcon icon={ArrowTopRight} />
</Button>
<Button
variant="gradient"
color="gradient_sky"
size="small"
label="Link out">
<ButtonIcon icon={Globe} />
<ButtonText>See the world</ButtonText>
</Button>
</View>
</View>
)
}

View file

@ -0,0 +1,90 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {Button} from '#/components/Button'
import {H3, P} from '#/components/Typography'
import * as Dialog from '#/components/Dialog'
import * as Prompt from '#/components/Prompt'
import {useDialogStateControlContext} from '#/state/dialogs'
export function Dialogs() {
const control = Dialog.useDialogControl()
const prompt = Prompt.usePromptControl()
const {closeAllDialogs} = useDialogStateControlContext()
return (
<View style={[a.gap_md]}>
<Button
variant="outline"
color="secondary"
size="small"
onPress={() => {
control.open()
prompt.open()
}}
label="Open basic dialog">
Open basic dialog
</Button>
<Button
variant="solid"
color="primary"
size="small"
onPress={() => prompt.open()}
label="Open prompt">
Open prompt
</Button>
<Prompt.Outer control={prompt}>
<Prompt.Title>This is a prompt</Prompt.Title>
<Prompt.Description>
This is a generic prompt component. It accepts a title and a
description, as well as two actions.
</Prompt.Description>
<Prompt.Actions>
<Prompt.Cancel>Cancel</Prompt.Cancel>
<Prompt.Action>Confirm</Prompt.Action>
</Prompt.Actions>
</Prompt.Outer>
<Dialog.Outer
control={control}
nativeOptions={{sheet: {snapPoints: ['90%']}}}>
<Dialog.Handle />
<Dialog.ScrollableInner
accessibilityDescribedBy="dialog-description"
accessibilityLabelledBy="dialog-title">
<View style={[a.relative, a.gap_md, a.w_full]}>
<H3 nativeID="dialog-title">Dialog</H3>
<P nativeID="dialog-description">
A scrollable dialog with an input within it.
</P>
<Dialog.Input value="" onChangeText={() => {}} label="Type here" />
<Button
variant="outline"
color="secondary"
size="small"
onPress={closeAllDialogs}
label="Close all dialogs">
Close all dialogs
</Button>
<View style={{height: 1000}} />
<View style={[a.flex_row, a.justify_end]}>
<Button
variant="outline"
color="primary"
size="small"
onPress={() => control.close()}
label="Open basic dialog">
Close basic dialog
</Button>
</View>
</View>
</Dialog.ScrollableInner>
</Dialog.Outer>
</View>
)
}

View file

@ -0,0 +1,215 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {H1, H3} from '#/components/Typography'
import * as TextField from '#/components/forms/TextField'
import {DateField, Label} from '#/components/forms/DateField'
import * as Toggle from '#/components/forms/Toggle'
import * as ToggleButton from '#/components/forms/ToggleButton'
import {Button} from '#/components/Button'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
export function Forms() {
const [toggleGroupAValues, setToggleGroupAValues] = React.useState(['a'])
const [toggleGroupBValues, setToggleGroupBValues] = React.useState(['a', 'b'])
const [toggleGroupCValues, setToggleGroupCValues] = React.useState(['a', 'b'])
const [toggleGroupDValues, setToggleGroupDValues] = React.useState(['warn'])
const [value, setValue] = React.useState('')
const [date, setDate] = React.useState('2001-01-01')
return (
<View style={[a.gap_4xl, a.align_start]}>
<H1>Forms</H1>
<View style={[a.gap_md, a.align_start, a.w_full]}>
<H3>InputText</H3>
<TextField.Input
value={value}
onChangeText={setValue}
label="Text field"
/>
<TextField.Root>
<TextField.Icon icon={Globe} />
<TextField.Input
value={value}
onChangeText={setValue}
label="Text field"
/>
</TextField.Root>
<View style={[a.w_full]}>
<TextField.Label>Text field</TextField.Label>
<TextField.Root>
<TextField.Icon icon={Globe} />
<TextField.Input
value={value}
onChangeText={setValue}
label="Text field"
/>
<TextField.Suffix label="@gmail.com">@gmail.com</TextField.Suffix>
</TextField.Root>
</View>
<View style={[a.w_full]}>
<TextField.Label>Textarea</TextField.Label>
<TextField.Input
multiline
numberOfLines={4}
value={value}
onChangeText={setValue}
label="Text field"
/>
</View>
<H3>DateField</H3>
<View style={[a.w_full]}>
<Label>Date</Label>
<DateField
testID="date"
value={date}
onChangeDate={date => {
console.log(date)
setDate(date)
}}
label="Input"
/>
</View>
</View>
<View style={[a.gap_md, a.align_start, a.w_full]}>
<H3>Toggles</H3>
<Toggle.Item name="a" label="Click me">
<Toggle.Checkbox />
<Toggle.Label>Uncontrolled toggle</Toggle.Label>
</Toggle.Item>
<Toggle.Group
label="Toggle"
type="checkbox"
maxSelections={2}
values={toggleGroupAValues}
onChange={setToggleGroupAValues}>
<View style={[a.gap_md]}>
<Toggle.Item name="a" label="Click me">
<Toggle.Switch />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="b" label="Click me">
<Toggle.Switch />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="c" label="Click me">
<Toggle.Switch />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="d" disabled label="Click me">
<Toggle.Switch />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="e" isInvalid label="Click me">
<Toggle.Switch />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
</View>
</Toggle.Group>
<Toggle.Group
label="Toggle"
type="checkbox"
maxSelections={2}
values={toggleGroupBValues}
onChange={setToggleGroupBValues}>
<View style={[a.gap_md]}>
<Toggle.Item name="a" label="Click me">
<Toggle.Checkbox />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="b" label="Click me">
<Toggle.Checkbox />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="c" label="Click me">
<Toggle.Checkbox />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="d" disabled label="Click me">
<Toggle.Checkbox />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="e" isInvalid label="Click me">
<Toggle.Checkbox />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
</View>
</Toggle.Group>
<Toggle.Group
label="Toggle"
type="radio"
values={toggleGroupCValues}
onChange={setToggleGroupCValues}>
<View style={[a.gap_md]}>
<Toggle.Item name="a" label="Click me">
<Toggle.Radio />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="b" label="Click me">
<Toggle.Radio />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="c" label="Click me">
<Toggle.Radio />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="d" disabled label="Click me">
<Toggle.Radio />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
<Toggle.Item name="e" isInvalid label="Click me">
<Toggle.Radio />
<Toggle.Label>Click me</Toggle.Label>
</Toggle.Item>
</View>
</Toggle.Group>
</View>
<Button
variant="gradient"
color="gradient_nordic"
size="small"
label="Reset all toggles"
onPress={() => {
setToggleGroupAValues(['a'])
setToggleGroupBValues(['a', 'b'])
setToggleGroupCValues(['a'])
}}>
Reset all toggles
</Button>
<View style={[a.gap_md, a.align_start, a.w_full]}>
<H3>ToggleButton</H3>
<ToggleButton.Group
label="Preferences"
values={toggleGroupDValues}
onChange={setToggleGroupDValues}>
<ToggleButton.Button name="hide" label="Hide">
Hide
</ToggleButton.Button>
<ToggleButton.Button name="warn" label="Warn">
Warn
</ToggleButton.Button>
<ToggleButton.Button name="show" label="Show">
Show
</ToggleButton.Button>
</ToggleButton.Group>
</View>
</View>
)
}

View file

@ -0,0 +1,41 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {H1} from '#/components/Typography'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
export function Icons() {
const t = useTheme()
return (
<View style={[a.gap_md]}>
<H1>Icons</H1>
<View style={[a.flex_row, a.gap_xl]}>
<Globe size="xs" fill={t.atoms.text.color} />
<Globe size="sm" fill={t.atoms.text.color} />
<Globe size="md" fill={t.atoms.text.color} />
<Globe size="lg" fill={t.atoms.text.color} />
<Globe size="xl" fill={t.atoms.text.color} />
</View>
<View style={[a.flex_row, a.gap_xl]}>
<ArrowTopRight size="xs" fill={t.atoms.text.color} />
<ArrowTopRight size="sm" fill={t.atoms.text.color} />
<ArrowTopRight size="md" fill={t.atoms.text.color} />
<ArrowTopRight size="lg" fill={t.atoms.text.color} />
<ArrowTopRight size="xl" fill={t.atoms.text.color} />
</View>
<View style={[a.flex_row, a.gap_xl]}>
<CalendarDays size="xs" fill={t.atoms.text.color} />
<CalendarDays size="sm" fill={t.atoms.text.color} />
<CalendarDays size="md" fill={t.atoms.text.color} />
<CalendarDays size="lg" fill={t.atoms.text.color} />
<CalendarDays size="xl" fill={t.atoms.text.color} />
</View>
</View>
)
}

View file

@ -0,0 +1,48 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {ButtonText} from '#/components/Button'
import {Link} from '#/components/Link'
import {H1, H3} from '#/components/Typography'
export function Links() {
return (
<View style={[a.gap_md, a.align_start]}>
<H1>Links</H1>
<View style={[a.gap_md, a.align_start]}>
<Link
to="https://blueskyweb.xyz"
warnOnMismatchingTextChild
style={[a.text_md]}>
External
</Link>
<Link to="https://blueskyweb.xyz" style={[a.text_md]}>
<H3>External with custom children</H3>
</Link>
<Link
to="https://blueskyweb.xyz"
warnOnMismatchingTextChild
style={[a.text_lg]}>
https://blueskyweb.xyz
</Link>
<Link
to="https://bsky.app/profile/bsky.app"
warnOnMismatchingTextChild
style={[a.text_md]}>
Internal
</Link>
<Link
variant="solid"
color="primary"
size="large"
label="View @bsky.app's profile"
to="https://bsky.app/profile/bsky.app">
<ButtonText>Link as a button</ButtonText>
</Link>
</View>
</View>
)
}

View file

@ -0,0 +1,336 @@
import React from 'react'
import {View} from 'react-native'
import * as tokens from '#/alf/tokens'
import {atoms as a} from '#/alf'
export function Palette() {
return (
<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_25},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_50},
]}
/>
<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_950},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_975},
]}
/>
<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_25},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.blue_50},
]}
/>
<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_950},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.blue_975},
]}
/>
</View>
<View style={[a.flex_row, a.gap_md]}>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.green_25},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.green_50},
]}
/>
<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_950},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.green_975},
]}
/>
</View>
<View style={[a.flex_row, a.gap_md]}>
<View
style={[a.flex_1, {height: 60, backgroundColor: tokens.color.red_25}]}
/>
<View
style={[a.flex_1, {height: 60, backgroundColor: tokens.color.red_50}]}
/>
<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_950},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.red_975},
]}
/>
</View>
</View>
)
}

View file

@ -0,0 +1,53 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {H1, Text} from '#/components/Typography'
export function Shadows() {
const t = useTheme()
return (
<View style={[a.gap_md]}>
<H1>Shadows</H1>
<View style={[a.flex_row, a.gap_5xl]}>
<View
style={[
a.flex_1,
a.justify_center,
a.px_lg,
a.py_2xl,
t.atoms.bg,
t.atoms.shadow_sm,
]}>
<Text>shadow_sm</Text>
</View>
<View
style={[
a.flex_1,
a.justify_center,
a.px_lg,
a.py_2xl,
t.atoms.bg,
t.atoms.shadow_md,
]}>
<Text>shadow_md</Text>
</View>
<View
style={[
a.flex_1,
a.justify_center,
a.px_lg,
a.py_2xl,
t.atoms.bg,
t.atoms.shadow_lg,
]}>
<Text>shadow_lg</Text>
</View>
</View>
</View>
)
}

View file

@ -0,0 +1,64 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {Text, H1} from '#/components/Typography'
export function Spacing() {
const t = useTheme()
return (
<View style={[a.gap_md]}>
<H1>Spacing</H1>
<View style={[a.flex_row, a.align_center]}>
<Text style={{width: 80}}>2xs (2px)</Text>
<View style={[a.flex_1, a.pt_2xs, 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 (16px)</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 (20px)</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}}>2xl (24px)</Text>
<View style={[a.flex_1, a.pt_2xl, t.atoms.bg_contrast_300]} />
</View>
<View style={[a.flex_row, a.align_center]}>
<Text style={{width: 80}}>3xl (28px)</Text>
<View style={[a.flex_1, a.pt_3xl, t.atoms.bg_contrast_300]} />
</View>
<View style={[a.flex_row, a.align_center]}>
<Text style={{width: 80}}>4xl (32px)</Text>
<View style={[a.flex_1, a.pt_4xl, t.atoms.bg_contrast_300]} />
</View>
<View style={[a.flex_row, a.align_center]}>
<Text style={{width: 80}}>5xl (40px)</Text>
<View style={[a.flex_1, a.pt_5xl, t.atoms.bg_contrast_300]} />
</View>
</View>
)
}

View file

@ -0,0 +1,56 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import {Palette} from './Palette'
export function Theming() {
const t = useTheme()
return (
<View style={[t.atoms.bg, a.gap_lg, a.p_xl]}>
<Palette />
<Text style={[a.font_bold, a.pt_xl, a.px_md]}>theme.atoms.text</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_600, a.px_md]}>
theme.atoms.text_contrast_600
</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_500, a.px_md]}>
theme.atoms.text_contrast_500
</Text>
<View style={[a.flex_1, t.atoms.border, a.border_t]} />
<Text style={[a.font_bold, t.atoms.text_contrast_400, a.px_md]}>
theme.atoms.text_contrast_400
</Text>
<View style={[a.flex_1, t.atoms.border_contrast, a.border_t]} />
<View style={[a.w_full, a.gap_md]}>
<View style={[t.atoms.bg, a.justify_center, a.p_md]}>
<Text>theme.atoms.bg</Text>
</View>
<View style={[t.atoms.bg_contrast_25, a.justify_center, a.p_md]}>
<Text>theme.atoms.bg_contrast_25</Text>
</View>
<View style={[t.atoms.bg_contrast_50, a.justify_center, a.p_md]}>
<Text>theme.atoms.bg_contrast_50</Text>
</View>
<View style={[t.atoms.bg_contrast_100, a.justify_center, a.p_md]}>
<Text>theme.atoms.bg_contrast_100</Text>
</View>
<View style={[t.atoms.bg_contrast_200, a.justify_center, a.p_md]}>
<Text>theme.atoms.bg_contrast_200</Text>
</View>
<View style={[t.atoms.bg_contrast_300, a.justify_center, a.p_md]}>
<Text>theme.atoms.bg_contrast_300</Text>
</View>
</View>
</View>
)
}

View file

@ -0,0 +1,30 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {Text, H1, H2, H3, H4, H5, H6, P} from '#/components/Typography'
export function Typography() {
return (
<View style={[a.gap_md]}>
<H1>H1 Heading</H1>
<H2>H2 Heading</H2>
<H3>H3 Heading</H3>
<H4>H4 Heading</H4>
<H5>H5 Heading</H5>
<H6>H6 Heading</H6>
<P>P Paragraph</P>
<Text style={[a.text_5xl]}>atoms.text_5xl</Text>
<Text style={[a.text_4xl]}>atoms.text_4xl</Text>
<Text style={[a.text_3xl]}>atoms.text_3xl</Text>
<Text style={[a.text_2xl]}>atoms.text_2xl</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_2xs]}>atoms.text_2xs</Text>
</View>
)
}

View file

@ -0,0 +1,78 @@
import React from 'react'
import {View} from 'react-native'
import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {atoms as a, useTheme, ThemeProvider} from '#/alf'
import {useSetColorMode} from '#/state/shell'
import {Button} from '#/components/Button'
import {Theming} from './Theming'
import {Typography} from './Typography'
import {Spacing} from './Spacing'
import {Buttons} from './Buttons'
import {Links} from './Links'
import {Forms} from './Forms'
import {Dialogs} from './Dialogs'
import {Breakpoints} from './Breakpoints'
import {Shadows} from './Shadows'
import {Icons} from './Icons'
export function Storybook() {
const t = useTheme()
const setColorMode = useSetColorMode()
return (
<ScrollView>
<CenteredView style={[t.atoms.bg]}>
<View style={[a.p_xl, a.gap_5xl, {paddingBottom: 200}]}>
<View style={[a.flex_row, a.align_start, a.gap_md]}>
<Button
variant="outline"
color="primary"
size="small"
label='Set theme to "system"'
onPress={() => setColorMode('system')}>
System
</Button>
<Button
variant="solid"
color="secondary"
size="small"
label='Set theme to "system"'
onPress={() => setColorMode('light')}>
Light
</Button>
<Button
variant="solid"
color="secondary"
size="small"
label='Set theme to "system"'
onPress={() => setColorMode('dark')}>
Dark
</Button>
</View>
<ThemeProvider theme="light">
<Theming />
</ThemeProvider>
<ThemeProvider theme="dim">
<Theming />
</ThemeProvider>
<ThemeProvider theme="dark">
<Theming />
</ThemeProvider>
<Typography />
<Spacing />
<Shadows />
<Buttons />
<Icons />
<Links />
<Forms />
<Dialogs />
<Breakpoints />
</View>
</CenteredView>
</ScrollView>
)
}