Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {createContext, useCallback, useContext, useId, useMemo} from 'react'
2import {type GestureResponderEvent, View} from 'react-native'
3import {msg} from '@lingui/core/macro'
4import {useLingui} from '@lingui/react'
5
6import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
7import {atoms as a, useTheme, type ViewStyleProp, web} from '#/alf'
8import {
9 Button,
10 type ButtonColor,
11 ButtonIcon,
12 ButtonText,
13} from '#/components/Button'
14import * as Dialog from '#/components/Dialog'
15import {type Props as SVGIconProps} from '#/components/icons/common'
16import {Text} from '#/components/Typography'
17import {type BottomSheetViewProps} from '../../modules/bottom-sheet'
18
19export {
20 type DialogControlProps as PromptControlProps,
21 useDialogControl as usePromptControl,
22} from '#/components/Dialog'
23
24const Context = createContext<{
25 titleId: string
26 descriptionId: string
27}>({
28 titleId: '',
29 descriptionId: '',
30})
31Context.displayName = 'PromptContext'
32
33export function Outer({
34 children,
35 control,
36 testID,
37 nativeOptions,
38}: React.PropsWithChildren<{
39 control: Dialog.DialogControlProps
40 testID?: string
41 /**
42 * Native-specific options for the prompt. Extends `BottomSheetViewProps`
43 */
44 nativeOptions?: Omit<BottomSheetViewProps, 'children'>
45}>) {
46 const titleId = useId()
47 const descriptionId = useId()
48
49 const context = useMemo(
50 () => ({titleId, descriptionId}),
51 [titleId, descriptionId],
52 )
53
54 const enableSquareButtons = useEnableSquareButtons()
55
56 return (
57 <Dialog.Outer
58 control={control}
59 testID={testID}
60 webOptions={{alignCenter: true}}
61 nativeOptions={{preventExpansion: true, ...nativeOptions}}>
62 <Dialog.Handle />
63 <Context.Provider value={context}>
64 <Dialog.ScrollableInner
65 accessibilityLabelledBy={titleId}
66 accessibilityDescribedBy={descriptionId}
67 style={web([{maxWidth: 320, borderRadius: enableSquareButtons ? 18 : 36}])}>
68 {children}
69 </Dialog.ScrollableInner>
70 </Context.Provider>
71 </Dialog.Outer>
72 )
73}
74
75export function TitleText({
76 children,
77 style,
78}: React.PropsWithChildren<ViewStyleProp>) {
79 const {titleId} = useContext(Context)
80 return (
81 <Text
82 nativeID={titleId}
83 style={[
84 a.flex_1,
85 a.text_2xl,
86 a.font_semi_bold,
87 a.pb_xs,
88 a.leading_snug,
89 style,
90 ]}>
91 {children}
92 </Text>
93 )
94}
95
96export function DescriptionText({
97 children,
98 selectable,
99}: React.PropsWithChildren<{selectable?: boolean}>) {
100 const t = useTheme()
101 const {descriptionId} = useContext(Context)
102 return (
103 <Text
104 nativeID={descriptionId}
105 selectable={selectable}
106 style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high, a.pb_lg]}>
107 {children}
108 </Text>
109 )
110}
111
112export function Actions({children}: {children: React.ReactNode}) {
113 return <View style={[a.w_full, a.gap_sm, a.justify_end]}>{children}</View>
114}
115
116export function Content({children}: {children: React.ReactNode}) {
117 return <View style={[a.pb_sm]}>{children}</View>
118}
119
120export function Cancel({
121 cta,
122}: {
123 /**
124 * Optional i18n string. If undefined, it will default to "Cancel".
125 */
126 cta?: string
127}) {
128 const {_} = useLingui()
129 const {close} = Dialog.useDialogContext()
130 const onPress = useCallback(() => {
131 close()
132 }, [close])
133
134 return (
135 <Button
136 variant="solid"
137 color="secondary"
138 size="large"
139 label={cta || _(msg`Cancel`)}
140 onPress={onPress}>
141 <ButtonText>{cta || _(msg`Cancel`)}</ButtonText>
142 </Button>
143 )
144}
145
146export function Action({
147 onPress,
148 color = 'primary',
149 cta,
150 disabled = false,
151 icon,
152 shouldCloseOnPress = true,
153 testID,
154}: {
155 /**
156 * Callback to run when the action is pressed. The method is called _after_
157 * the dialog closes.
158 *
159 * Note: The dialog will close automatically when the action is pressed, you
160 * should NOT close the dialog as a side effect of this method.
161 */
162 onPress: (e: GestureResponderEvent) => void
163 color?: ButtonColor
164 /**
165 * Optional i18n string. If undefined, it will default to "Confirm".
166 */
167 cta?: string
168 /**
169 * If undefined, it will default to false.
170 */
171 disabled?: boolean
172 icon?: React.ComponentType<SVGIconProps>
173 /**
174 * Optionally close dialog automatically on press. If undefined, it will
175 * default to true.
176 */
177 shouldCloseOnPress?: boolean
178 testID?: string
179}) {
180 const {_} = useLingui()
181 const {close} = Dialog.useDialogContext()
182 const handleOnPress = useCallback(
183 (e: GestureResponderEvent) => {
184 if (shouldCloseOnPress) {
185 close(() => onPress?.(e))
186 } else {
187 onPress?.(e)
188 }
189 },
190 [close, onPress, shouldCloseOnPress],
191 )
192
193 return (
194 <Button
195 color={color}
196 disabled={disabled}
197 size="large"
198 label={cta || _(msg`Confirm`)}
199 onPress={handleOnPress}
200 testID={testID}>
201 <ButtonText>{cta || _(msg`Confirm`)}</ButtonText>
202 {icon && <ButtonIcon icon={icon} />}
203 </Button>
204 )
205}
206
207export function Basic({
208 control,
209 title,
210 description,
211 cancelButtonCta,
212 confirmButtonCta,
213 onConfirm,
214 confirmButtonColor,
215 showCancel = true,
216}: React.PropsWithChildren<{
217 control: Dialog.DialogOuterProps['control']
218 title: string
219 description?: string
220 cancelButtonCta?: string
221 confirmButtonCta?: string
222 /**
223 * Callback to run when the Confirm button is pressed. The method is called
224 * _after_ the dialog closes.
225 *
226 * Note: The dialog will close automatically when the action is pressed, you
227 * should NOT close the dialog as a side effect of this method.
228 */
229 onConfirm: (e: GestureResponderEvent) => void
230 confirmButtonColor?: ButtonColor
231 showCancel?: boolean
232}>) {
233 return (
234 <Outer control={control} testID="confirmModal">
235 <Content>
236 <TitleText>{title}</TitleText>
237 {description && <DescriptionText>{description}</DescriptionText>}
238 </Content>
239 <Actions>
240 <Action
241 cta={confirmButtonCta}
242 onPress={onConfirm}
243 color={confirmButtonColor}
244 testID="confirmBtn"
245 />
246 {showCancel && <Cancel cta={cancelButtonCta} />}
247 </Actions>
248 </Outer>
249 )
250}