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