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