this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at e28f6d2f370b4e882ed6f23d08ca0f8d94dbac5f 234 lines 6.9 kB view raw
1import {useState} from 'react' 2import {TouchableOpacity, View} from 'react-native' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Plural, Trans} from '@lingui/react/macro' 6 7import {HITSLOP_10, MAX_ALT_TEXT} from '#/lib/constants' 8import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' 9import { 10 type EmbedPlayerParams, 11 parseEmbedPlayerFromUrl, 12} from '#/lib/strings/embed-player' 13import {useResolveGifQuery} from '#/state/queries/resolve-link' 14import {type Gif} from '#/state/queries/tenor' 15import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' 16import {atoms as a, useTheme} from '#/alf' 17import {Admonition} from '#/components/Admonition' 18import {Button, ButtonText} from '#/components/Button' 19import * as Dialog from '#/components/Dialog' 20import {type DialogControlProps} from '#/components/Dialog' 21import * as TextField from '#/components/forms/TextField' 22import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 23import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 24import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 25import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif' 26import {Text} from '#/components/Typography' 27 28export function GifAltTextDialog({ 29 gif, 30 altText, 31 onSubmit, 32}: { 33 gif: Gif 34 altText: string 35 onSubmit: (alt: string) => void 36}) { 37 const {data} = useResolveGifQuery(gif) 38 const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt 39 const params = data ? parseEmbedPlayerFromUrl(data.uri) : undefined 40 if (!data || !params) { 41 return null 42 } 43 return ( 44 <GifAltTextDialogLoaded 45 altText={altText} 46 vendorAltText={vendorAltText} 47 thumb={data.thumb?.source.path} 48 params={params} 49 onSubmit={onSubmit} 50 /> 51 ) 52} 53 54export function GifAltTextDialogLoaded({ 55 vendorAltText, 56 altText, 57 onSubmit, 58 params, 59 thumb, 60}: { 61 vendorAltText: string 62 altText: string 63 onSubmit: (alt: string) => void 64 params: EmbedPlayerParams 65 thumb: string | undefined 66}) { 67 const control = Dialog.useDialogControl() 68 const {_} = useLingui() 69 const t = useTheme() 70 const [altTextDraft, setAltTextDraft] = useState(altText || vendorAltText) 71 return ( 72 <> 73 <TouchableOpacity 74 testID="altTextButton" 75 accessibilityRole="button" 76 accessibilityLabel={_(msg`Add alt text`)} 77 accessibilityHint="" 78 hitSlop={HITSLOP_10} 79 onPress={control.open} 80 style={[ 81 a.absolute, 82 {top: 8, left: 8}, 83 {borderRadius: 6}, 84 a.pl_xs, 85 a.pr_sm, 86 a.py_2xs, 87 a.flex_row, 88 a.gap_xs, 89 a.align_center, 90 {backgroundColor: 'rgba(0, 0, 0, 0.75)'}, 91 ]}> 92 {altText ? ( 93 <Check size="xs" fill={t.palette.white} style={a.ml_xs} /> 94 ) : ( 95 <Plus size="sm" fill={t.palette.white} /> 96 )} 97 <Text 98 style={[a.font_semi_bold, {color: t.palette.white}]} 99 accessible={false}> 100 <Trans>ALT</Trans> 101 </Text> 102 </TouchableOpacity> 103 104 <Admonition type="tip" style={[a.mt_sm]}> 105 <Trans> 106 Alt text describes images for blind and low-vision users, and helps 107 give context to everyone. 108 </Trans> 109 </Admonition> 110 111 <Dialog.Outer 112 control={control} 113 onClose={() => { 114 onSubmit(altTextDraft) 115 }} 116 nativeOptions={{fullHeight: true}}> 117 <Dialog.Handle /> 118 <AltTextInner 119 vendorAltText={vendorAltText} 120 altText={altTextDraft} 121 onChange={setAltTextDraft} 122 thumb={thumb} 123 control={control} 124 params={params} 125 /> 126 </Dialog.Outer> 127 </> 128 ) 129} 130 131function AltTextInner({ 132 vendorAltText, 133 altText, 134 onChange, 135 control, 136 params, 137 thumb, 138}: { 139 vendorAltText: string 140 altText: string 141 onChange: (text: string) => void 142 control: DialogControlProps 143 params: EmbedPlayerParams 144 thumb: string | undefined 145}) { 146 const t = useTheme() 147 const {_, i18n} = useLingui() 148 149 return ( 150 <Dialog.ScrollableInner label={_(msg`Add alt text`)}> 151 <View style={a.flex_col_reverse}> 152 <View style={[a.mt_md, a.gap_md]}> 153 <View style={[a.gap_sm]}> 154 <View style={[a.relative]}> 155 <TextField.LabelText> 156 <Trans>Descriptive alt text</Trans> 157 </TextField.LabelText> 158 <TextField.Root> 159 <Dialog.Input 160 label={_(msg`Alt text`)} 161 placeholder={vendorAltText} 162 onChangeText={onChange} 163 defaultValue={altText} 164 multiline 165 numberOfLines={3} 166 autoFocus 167 onKeyPress={({nativeEvent}) => { 168 if (nativeEvent.key === 'Escape') { 169 control.close() 170 } 171 }} 172 /> 173 </TextField.Root> 174 </View> 175 176 {altText.length > MAX_ALT_TEXT && ( 177 <View style={[a.pb_sm, a.flex_row, a.gap_xs]}> 178 <CircleInfo fill={t.palette.negative_500} /> 179 <Text 180 style={[ 181 a.italic, 182 a.leading_snug, 183 t.atoms.text_contrast_medium, 184 ]}> 185 <Trans> 186 Alt text will be truncated.{' '} 187 <Plural 188 value={MAX_ALT_TEXT} 189 other={`Limit: ${i18n.number(MAX_ALT_TEXT)} characters.`} 190 /> 191 </Trans> 192 </Text> 193 </View> 194 )} 195 </View> 196 197 <AltTextCounterWrapper altText={altText}> 198 <Button 199 label={_(msg`Save`)} 200 size="large" 201 color="primary" 202 variant="solid" 203 onPress={() => { 204 control.close() 205 }} 206 style={[a.flex_grow]}> 207 <ButtonText> 208 <Trans>Save</Trans> 209 </ButtonText> 210 </Button> 211 </AltTextCounterWrapper> 212 </View> 213 {/* below the text input to force tab order */} 214 <View> 215 <Text 216 style={[a.text_2xl, a.font_semi_bold, a.leading_tight, a.pb_sm]}> 217 <Trans>Add alt text</Trans> 218 </Text> 219 <View style={[a.align_center]}> 220 <GifEmbed 221 thumb={thumb} 222 altText={altText} 223 isPreferredAltText={true} 224 params={params} 225 hideAlt 226 style={[{height: 225}]} 227 /> 228 </View> 229 </View> 230 </View> 231 <Dialog.Close /> 232 </Dialog.ScrollableInner> 233 ) 234}