forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {createContext, useContext} from 'react'
2import {type StyleProp, View, type ViewStyle} from 'react-native'
3
4import {atoms as a, useBreakpoints, useTheme} from '#/alf'
5import {Button as BaseButton, type ButtonProps} from '#/components/Button'
6import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
7import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
8import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
9import {Text as BaseText, type TextProps} from '#/components/Typography'
10import {EmojiSad_Stroke2_Corner0_Rounded as EmojiSadIcon} from './icons/Emoji'
11
12export const colors = {
13 warning: '#FFC404',
14}
15
16type Context = {
17 type: 'info' | 'tip' | 'warning' | 'error' | 'apology'
18}
19
20const Context = createContext<Context>({
21 type: 'info',
22})
23Context.displayName = 'AdmonitionContext'
24
25export function Icon() {
26 const t = useTheme()
27 const {type} = useContext(Context)
28 const Icon = {
29 info: CircleInfoIcon,
30 tip: CircleInfoIcon,
31 warning: WarningIcon,
32 error: CircleXIcon,
33 apology: EmojiSadIcon,
34 }[type]
35 const fill = {
36 info: t.atoms.text_contrast_medium.color,
37 tip: t.palette.primary_500,
38 warning: colors.warning,
39 error: t.palette.negative_500,
40 apology: t.atoms.text_contrast_medium.color,
41 }[type]
42 return <Icon fill={fill} size="md" />
43}
44
45export function Content({
46 children,
47 style,
48 ...rest
49}: {
50 children: React.ReactNode
51 style?: StyleProp<ViewStyle>
52}) {
53 return (
54 <View
55 style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]}
56 {...rest}>
57 {children}
58 </View>
59 )
60}
61
62export function Text({
63 children,
64 style,
65 ...rest
66}: Pick<TextProps, 'children' | 'style'>) {
67 return (
68 <BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}>
69 {children}
70 </BaseText>
71 )
72}
73
74export function Button({
75 children,
76 ...props
77}: Omit<ButtonProps, 'size' | 'variant'>) {
78 return (
79 <BaseButton size="tiny" {...props}>
80 {children}
81 </BaseButton>
82 )
83}
84
85export function Row({
86 children,
87 style,
88}: {
89 children: React.ReactNode
90 style?: StyleProp<ViewStyle>
91}) {
92 return (
93 <View style={[a.w_full, a.flex_row, a.align_start, a.gap_sm, style]}>
94 {children}
95 </View>
96 )
97}
98
99export function Outer({
100 children,
101 type = 'info',
102 style,
103}: {
104 children: React.ReactNode
105 type?: Context['type']
106 style?: StyleProp<ViewStyle>
107}) {
108 const t = useTheme()
109 const {gtMobile} = useBreakpoints()
110 const borderColor = {
111 info: t.atoms.border_contrast_high.borderColor,
112 tip: t.palette.primary_500,
113 warning: colors.warning,
114 error: t.palette.negative_500,
115 apology: t.atoms.border_contrast_high.borderColor,
116 }[type]
117 return (
118 <Context.Provider value={{type}}>
119 <View
120 style={[
121 gtMobile ? a.p_md : a.p_sm,
122 a.p_md,
123 a.rounded_sm,
124 a.border,
125 t.atoms.bg,
126 {borderColor},
127 style,
128 ]}>
129 {children}
130 </View>
131 </Context.Provider>
132 )
133}
134
135export function Admonition({
136 children,
137 type,
138 style,
139}: {
140 children: TextProps['children']
141 type?: Context['type']
142 style?: StyleProp<ViewStyle>
143}) {
144 return (
145 <Outer type={type} style={style}>
146 <Row>
147 <Icon />
148 <Content>
149 <Text>{children}</Text>
150 </Content>
151 </Row>
152 </Outer>
153 )
154}