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