Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 250 lines 6.2 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 {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}