Bluesky app fork with some witchin' additions 💫

Clean up dialogs (#8934)

authored by samuel.fm and committed by

GitHub 4a1b1f17 7574a745

+221 -195
+110 -100
src/components/NewskieDialog.tsx
··· 1 - import React from 'react' 1 + import {useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' ··· 27 27 disabled?: boolean 28 28 }) { 29 29 const {_} = useLingui() 30 - const t = useTheme() 31 - const moderationOpts = useModerationOpts() 32 - const {currentAccount} = useSession() 33 - const timeAgo = useGetTimeAgo() 34 30 const control = useDialogControl() 35 31 36 - const isMe = profile.did === currentAccount?.did 37 32 const createdAt = profile.createdAt as string | undefined 38 33 39 - const profileName = React.useMemo(() => { 40 - const name = profile.displayName || profile.handle 41 - 42 - if (isMe) { 43 - return _(msg`You`) 44 - } 45 - 46 - if (!moderationOpts) return name 47 - const moderation = moderateProfile(profile, moderationOpts) 48 - 49 - return sanitizeDisplayName(name, moderation.ui('displayName')) 50 - }, [_, isMe, moderationOpts, profile]) 51 - 52 - const [now] = React.useState(() => Date.now()) 53 - const daysOld = React.useMemo(() => { 34 + const [now] = useState(() => Date.now()) 35 + const daysOld = useMemo(() => { 54 36 if (!createdAt) return Infinity 55 37 return differenceInSeconds(now, new Date(createdAt)) / 86400 56 38 }, [createdAt, now]) ··· 77 59 )} 78 60 </Button> 79 61 80 - <Dialog.Outer control={control}> 62 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 81 63 <Dialog.Handle /> 82 - <Dialog.ScrollableInner 83 - label={_(msg`New user info dialog`)} 84 - style={web({width: 'auto', maxWidth: 400, minWidth: 200})}> 85 - <View style={[a.gap_md]}> 86 - <View style={[a.align_center]}> 87 - <View 88 - style={[ 89 - { 90 - height: 60, 91 - width: 64, 92 - }, 93 - ]}> 94 - <Newskie 95 - width={64} 96 - height={64} 97 - fill="#FFC404" 98 - style={[a.absolute, a.inset_0]} 99 - /> 100 - </View> 101 - <Text style={[a.font_bold, a.text_xl]}> 102 - {isMe ? ( 103 - <Trans>Welcome, friend!</Trans> 104 - ) : ( 105 - <Trans>Say hello!</Trans> 106 - )} 107 - </Text> 108 - </View> 109 - <Text style={[a.text_md, a.text_center, a.leading_snug]}> 110 - {profile.joinedViaStarterPack ? ( 111 - <Trans> 112 - {profileName} joined Bluesky using a starter pack{' '} 113 - {timeAgo(createdAt, now, {format: 'long'})} ago 114 - </Trans> 115 - ) : ( 116 - <Trans> 117 - {profileName} joined Bluesky{' '} 118 - {timeAgo(createdAt, now, {format: 'long'})} ago 119 - </Trans> 120 - )} 121 - </Text> 122 - {profile.joinedViaStarterPack ? ( 123 - <StarterPackCard.Link 124 - starterPack={profile.joinedViaStarterPack} 125 - onPress={() => { 126 - control.close() 127 - }}> 128 - <View 129 - style={[ 130 - a.w_full, 131 - a.mt_sm, 132 - a.p_lg, 133 - a.border, 134 - a.rounded_sm, 135 - t.atoms.border_contrast_low, 136 - ]}> 137 - <StarterPackCard.Card 138 - starterPack={profile.joinedViaStarterPack} 139 - /> 140 - </View> 141 - </StarterPackCard.Link> 142 - ) : null} 64 + <DialogInner profile={profile} createdAt={createdAt} now={now} /> 65 + </Dialog.Outer> 66 + </View> 67 + ) 68 + } 143 69 144 - {isNative && ( 145 - <Button 146 - label={_(msg`Close`)} 147 - variant="solid" 148 - color="secondary" 149 - size="small" 150 - style={[a.mt_sm]} 151 - onPress={() => control.close()}> 152 - <ButtonText> 153 - <Trans>Close</Trans> 154 - </ButtonText> 155 - </Button> 156 - )} 70 + function DialogInner({ 71 + profile, 72 + createdAt, 73 + now, 74 + }: { 75 + profile: AppBskyActorDefs.ProfileViewDetailed 76 + createdAt: string 77 + now: number 78 + }) { 79 + const control = Dialog.useDialogContext() 80 + const {_} = useLingui() 81 + const t = useTheme() 82 + const moderationOpts = useModerationOpts() 83 + const {currentAccount} = useSession() 84 + const timeAgo = useGetTimeAgo() 85 + const isMe = profile.did === currentAccount?.did 86 + 87 + const profileName = useMemo(() => { 88 + const name = profile.displayName || profile.handle 89 + 90 + if (isMe) { 91 + return _(msg`You`) 92 + } 93 + 94 + if (!moderationOpts) return name 95 + const moderation = moderateProfile(profile, moderationOpts) 96 + 97 + return sanitizeDisplayName(name, moderation.ui('displayName')) 98 + }, [_, isMe, moderationOpts, profile]) 99 + 100 + return ( 101 + <Dialog.ScrollableInner 102 + label={_(msg`New user info dialog`)} 103 + style={web({maxWidth: 400})}> 104 + <View style={[a.gap_md]}> 105 + <View style={[a.align_center]}> 106 + <View 107 + style={[ 108 + { 109 + height: 60, 110 + width: 64, 111 + }, 112 + ]}> 113 + <Newskie 114 + width={64} 115 + height={64} 116 + fill="#FFC404" 117 + style={[a.absolute, a.inset_0]} 118 + /> 157 119 </View> 120 + <Text style={[a.font_bold, a.text_xl]}> 121 + {isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>} 122 + </Text> 123 + </View> 124 + <Text style={[a.text_md, a.text_center, a.leading_snug]}> 125 + {profile.joinedViaStarterPack ? ( 126 + <Trans> 127 + {profileName} joined Bluesky using a starter pack{' '} 128 + {timeAgo(createdAt, now, {format: 'long'})} ago 129 + </Trans> 130 + ) : ( 131 + <Trans> 132 + {profileName} joined Bluesky{' '} 133 + {timeAgo(createdAt, now, {format: 'long'})} ago 134 + </Trans> 135 + )} 136 + </Text> 137 + {profile.joinedViaStarterPack ? ( 138 + <StarterPackCard.Link 139 + starterPack={profile.joinedViaStarterPack} 140 + onPress={() => control.close()}> 141 + <View 142 + style={[ 143 + a.w_full, 144 + a.mt_sm, 145 + a.p_lg, 146 + a.border, 147 + a.rounded_sm, 148 + t.atoms.border_contrast_low, 149 + ]}> 150 + <StarterPackCard.Card 151 + starterPack={profile.joinedViaStarterPack} 152 + /> 153 + </View> 154 + </StarterPackCard.Link> 155 + ) : null} 158 156 159 - <Dialog.Close /> 160 - </Dialog.ScrollableInner> 161 - </Dialog.Outer> 162 - </View> 157 + {isNative && ( 158 + <Button 159 + label={_(msg`Close`)} 160 + color="secondary" 161 + size="small" 162 + style={[a.mt_sm]} 163 + onPress={() => control.close()}> 164 + <ButtonText> 165 + <Trans>Close</Trans> 166 + </ButtonText> 167 + </Button> 168 + )} 169 + </View> 170 + 171 + <Dialog.Close /> 172 + </Dialog.ScrollableInner> 163 173 ) 164 174 }
+10 -10
src/components/StarterPack/QrCode.tsx
··· 1 - import React from 'react' 1 + import {lazy} from 'react' 2 2 import {View} from 'react-native' 3 3 // @ts-expect-error missing types 4 4 import QRCode from 'react-native-qrcode-styled' ··· 15 15 import {Text} from '#/components/Typography' 16 16 import * as bsky from '#/types/bsky' 17 17 18 - const LazyViewShot = React.lazy( 18 + const LazyViewShot = lazy( 19 19 // @ts-expect-error dynamic import 20 20 () => import('react-native-view-shot/src/index'), 21 21 ) 22 22 23 - interface Props { 23 + export function QrCode({ 24 + starterPack, 25 + link, 26 + ref, 27 + }: { 24 28 starterPack: AppBskyGraphDefs.StarterPackView 25 29 link: string 26 - } 27 - 28 - export const QrCode = React.forwardRef<ViewShot, Props>(function QrCode( 29 - {starterPack, link}, 30 - ref, 31 - ) { 30 + ref: React.Ref<ViewShot> 31 + }) { 32 32 const {record} = starterPack 33 33 34 34 if ( ··· 93 93 </LinearGradientBackground> 94 94 </LazyViewShot> 95 95 ) 96 - }) 96 + } 97 97 98 98 export function QrCodeInner({link}: {link: string}) { 99 99 const t = useTheme()
+62 -51
src/components/StarterPack/QrCodeDialog.tsx
··· 1 - import React from 'react' 1 + import {Suspense, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import type ViewShot from 'react-native-view-shot' 4 4 import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' ··· 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 10 11 - import {logEvent} from '#/lib/statsig/statsig' 12 11 import {logger} from '#/logger' 13 12 import {isNative, isWeb} from '#/platform/detection' 14 - import * as Toast from '#/view/com/util/Toast' 15 - import {atoms as a} from '#/alf' 16 - import {Button, ButtonText} from '#/components/Button' 13 + import {atoms as a, useBreakpoints} from '#/alf' 14 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 17 15 import * as Dialog from '#/components/Dialog' 18 16 import {type DialogControlProps} from '#/components/Dialog' 17 + import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' 18 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 19 + import {FloppyDisk_Stroke2_Corner0_Rounded as FloppyDiskIcon} from '#/components/icons/FloppyDisk' 19 20 import {Loader} from '#/components/Loader' 20 21 import {QrCode} from '#/components/StarterPack/QrCode' 22 + import * as Toast from '#/components/Toast' 21 23 import * as bsky from '#/types/bsky' 22 24 23 25 export function QrCodeDialog({ ··· 30 32 control: DialogControlProps 31 33 }) { 32 34 const {_} = useLingui() 33 - const [isProcessing, setIsProcessing] = React.useState(false) 35 + const {gtMobile} = useBreakpoints() 36 + const [isSaveProcessing, setIsSaveProcessing] = useState(false) 37 + const [isCopyProcessing, setIsCopyProcessing] = useState(false) 34 38 35 - const ref = React.useRef<ViewShot>(null) 39 + const ref = useRef<ViewShot>(null) 36 40 37 41 const getCanvas = (base64: string): Promise<HTMLCanvasElement> => { 38 42 return new Promise(resolve => { ··· 68 72 try { 69 73 await createAssetAsync(`file://${uri}`) 70 74 } catch (e: unknown) { 71 - Toast.show( 72 - _(msg`An error occurred while saving the QR code!`), 73 - 'xmark', 74 - ) 75 + Toast.show(_(msg`An error occurred while saving the QR code!`), { 76 + type: 'error', 77 + }) 75 78 logger.error('Failed to save QR code', {error: e}) 76 79 return 77 80 } 78 81 } else { 79 - setIsProcessing(true) 82 + setIsSaveProcessing(true) 80 83 81 84 if ( 82 85 !bsky.validate( ··· 101 104 link.click() 102 105 } 103 106 104 - logEvent('starterPack:share', { 107 + logger.metric('starterPack:share', { 105 108 starterPack: starterPack.uri, 106 109 shareType: 'qrcode', 107 110 qrShareType: 'save', 108 111 }) 109 - setIsProcessing(false) 112 + setIsSaveProcessing(false) 110 113 Toast.show( 111 114 isWeb 112 115 ? _(msg`QR code has been downloaded!`) ··· 117 120 } 118 121 119 122 const onCopyPress = async () => { 120 - setIsProcessing(true) 123 + setIsCopyProcessing(true) 121 124 ref.current?.capture?.().then(async (uri: string) => { 122 125 const canvas = await getCanvas(uri) 123 126 // @ts-expect-error web only ··· 126 129 navigator.clipboard.write([item]) 127 130 }) 128 131 129 - logEvent('starterPack:share', { 132 + logger.metric('starterPack:share', { 130 133 starterPack: starterPack.uri, 131 134 shareType: 'qrcode', 132 135 qrShareType: 'copy', 133 136 }) 134 137 Toast.show(_(msg`QR code copied to your clipboard!`)) 135 - setIsProcessing(false) 138 + setIsCopyProcessing(false) 136 139 control.close() 137 140 }) 138 141 } ··· 142 145 control.close(() => { 143 146 Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then( 144 147 () => { 145 - logEvent('starterPack:share', { 148 + logger.metric('starterPack:share', { 146 149 starterPack: starterPack.uri, 147 150 shareType: 'qrcode', 148 151 qrShareType: 'share', ··· 154 157 } 155 158 156 159 return ( 157 - <Dialog.Outer control={control}> 160 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 158 161 <Dialog.Handle /> 159 162 <Dialog.ScrollableInner 160 163 label={_(msg`Create a QR code for a starter pack`)}> 161 164 <View style={[a.flex_1, a.align_center, a.gap_5xl]}> 162 - <React.Suspense fallback={<Loading />}> 165 + <Suspense fallback={<Loading />}> 163 166 {!link ? ( 164 167 <Loading /> 165 168 ) : ( 166 169 <> 167 170 <QrCode starterPack={starterPack} link={link} ref={ref} /> 168 - {isProcessing ? ( 169 - <View> 170 - <Loader size="xl" /> 171 - </View> 172 - ) : ( 173 - <View 174 - style={[a.w_full, a.gap_md, isWeb && [a.flex_row_reverse]]}> 175 - <Button 176 - label={_(msg`Copy QR code`)} 177 - variant="solid" 178 - color="secondary" 179 - size="small" 180 - onPress={isWeb ? onCopyPress : onSharePress}> 181 - <ButtonText> 182 - {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 183 - </ButtonText> 184 - </Button> 185 - <Button 186 - label={_(msg`Save QR code`)} 187 - variant="solid" 188 - color="secondary" 189 - size="small" 190 - onPress={onSavePress}> 191 - <ButtonText> 192 - <Trans>Save</Trans> 193 - </ButtonText> 194 - </Button> 195 - </View> 196 - )} 171 + <View 172 + style={[ 173 + a.w_full, 174 + a.gap_md, 175 + gtMobile && [a.flex_row, a.justify_center, a.flex_wrap], 176 + ]}> 177 + <Button 178 + label={_(msg`Copy QR code`)} 179 + color="primary_subtle" 180 + size="large" 181 + onPress={isWeb ? onCopyPress : onSharePress}> 182 + <ButtonIcon 183 + icon={ 184 + isCopyProcessing 185 + ? Loader 186 + : isWeb 187 + ? ChainLinkIcon 188 + : ShareIcon 189 + } 190 + /> 191 + <ButtonText> 192 + {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 193 + </ButtonText> 194 + </Button> 195 + <Button 196 + label={_(msg`Save QR code`)} 197 + color="secondary" 198 + size="large" 199 + onPress={onSavePress}> 200 + <ButtonIcon 201 + icon={isSaveProcessing ? Loader : FloppyDiskIcon} 202 + /> 203 + <ButtonText> 204 + <Trans>Save</Trans> 205 + </ButtonText> 206 + </Button> 207 + </View> 197 208 </> 198 209 )} 199 - </React.Suspense> 210 + </Suspense> 200 211 </View> 201 212 <Dialog.Close /> 202 213 </Dialog.ScrollableInner> ··· 206 217 207 218 function Loading() { 208 219 return ( 209 - <View style={[a.align_center, a.p_xl]}> 220 + <View style={[a.align_center, a.justify_center, {minHeight: 400}]}> 210 221 <Loader size="xl" /> 211 222 </View> 212 223 )
+30 -24
src/components/StarterPack/ShareDialog.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 8 7 import {useSaveImageToMediaLibrary} from '#/lib/media/save-image' 9 8 import {shareUrl} from '#/lib/sharing' 10 - import {logEvent} from '#/lib/statsig/statsig' 11 9 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 10 + import {logger} from '#/logger' 12 11 import {isNative, isWeb} from '#/platform/detection' 13 - import {atoms as a, useTheme} from '#/alf' 14 - import {Button, ButtonText} from '#/components/Button' 12 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import {type DialogControlProps} from '#/components/Dialog' 16 15 import * as Dialog from '#/components/Dialog' 16 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 17 + import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' 18 + import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' 17 19 import {Loader} from '#/components/Loader' 18 20 import {Text} from '#/components/Typography' 19 21 ··· 27 29 28 30 export function ShareDialog(props: Props) { 29 31 return ( 30 - <Dialog.Outer control={props.control}> 32 + <Dialog.Outer 33 + control={props.control} 34 + nativeOptions={{preventExpansion: true}}> 31 35 <Dialog.Handle /> 32 36 <ShareDialogInner {...props} /> 33 37 </Dialog.Outer> ··· 43 47 }: Props) { 44 48 const {_} = useLingui() 45 49 const t = useTheme() 46 - const {isTabletOrDesktop} = useWebMediaQueries() 50 + const {gtMobile} = useBreakpoints() 47 51 48 52 const imageUrl = getStarterPackOgCard(starterPack) 49 53 50 54 const onShareLink = async () => { 51 55 if (!link) return 52 56 shareUrl(link) 53 - logEvent('starterPack:share', { 57 + logger.metric('starterPack:share', { 54 58 starterPack: starterPack.uri, 55 59 shareType: 'link', 56 60 }) ··· 67 71 <> 68 72 <Dialog.ScrollableInner label={_(msg`Share link dialog`)}> 69 73 {!imageLoaded || !link ? ( 70 - <View style={[a.p_xl, a.align_center]}> 74 + <View style={[a.align_center, a.justify_center, {minHeight: 350}]}> 71 75 <Loader size="xl" /> 72 76 </View> 73 77 ) : ( 74 - <View style={[!isTabletOrDesktop && a.gap_lg]}> 75 - <View style={[a.gap_sm, isTabletOrDesktop && a.pb_lg]}> 78 + <View style={[!gtMobile && a.gap_lg]}> 79 + <View style={[a.gap_sm, gtMobile && a.pb_lg]}> 76 80 <Text style={[a.font_bold, a.text_2xl]}> 77 81 <Trans>Invite people to this starter pack!</Trans> 78 82 </Text> ··· 89 93 a.rounded_sm, 90 94 { 91 95 aspectRatio: 1200 / 630, 92 - transform: [{scale: isTabletOrDesktop ? 0.85 : 1}], 93 - marginTop: isTabletOrDesktop ? -20 : 0, 96 + transform: [{scale: gtMobile ? 0.85 : 1}], 97 + marginTop: gtMobile ? -20 : 0, 94 98 }, 95 99 ]} 96 100 accessibilityIgnoresInvertColors={true} ··· 98 102 <View 99 103 style={[ 100 104 a.gap_md, 101 - isWeb && [a.gap_sm, a.flex_row_reverse, {marginLeft: 'auto'}], 105 + gtMobile && [ 106 + a.gap_sm, 107 + a.justify_center, 108 + a.flex_row, 109 + a.flex_wrap, 110 + ], 102 111 ]}> 103 112 <Button 104 113 label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)} 105 - variant="solid" 106 - color="secondary" 107 - size="small" 108 - style={[isWeb && a.self_center]} 114 + color="primary_subtle" 115 + size="large" 109 116 onPress={onShareLink}> 117 + <ButtonIcon icon={ChainLinkIcon} /> 110 118 <ButtonText> 111 119 {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>} 112 120 </ButtonText> 113 121 </Button> 114 122 <Button 115 123 label={_(msg`Share QR code`)} 116 - variant="solid" 117 - color="secondary" 118 - size="small" 119 - style={[isWeb && a.self_center]} 124 + color="primary_subtle" 125 + size="large" 120 126 onPress={() => { 121 127 control.close(() => { 122 128 qrDialogControl.open() 123 129 }) 124 130 }}> 131 + <ButtonIcon icon={QrCodeIcon} /> 125 132 <ButtonText> 126 133 <Trans>Share QR code</Trans> 127 134 </ButtonText> ··· 129 136 {isNative && ( 130 137 <Button 131 138 label={_(msg`Save image`)} 132 - variant="ghost" 133 139 color="secondary" 134 - size="small" 135 - style={[isWeb && a.self_center]} 140 + size="large" 136 141 onPress={onSave}> 142 + <ButtonIcon icon={DownloadIcon} /> 137 143 <ButtonText> 138 144 <Trans>Save image</Trans> 139 145 </ButtonText>
+3 -3
src/components/dialogs/EmbedConsent.tsx
··· 10 10 } from '#/lib/strings/embed-player' 11 11 import {useSetExternalEmbedPref} from '#/state/preferences' 12 12 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 + import {Button, ButtonText} from '#/components/Button' 13 14 import * as Dialog from '#/components/Dialog' 14 - import {Button, ButtonText} from '../Button' 15 - import {Text} from '../Typography' 15 + import {Text} from '#/components/Typography' 16 16 17 17 export function EmbedConsentDialog({ 18 18 control, ··· 48 48 }, [control, setExternalEmbedPref, source]) 49 49 50 50 return ( 51 - <Dialog.Outer control={control}> 51 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 52 52 <Dialog.Handle /> 53 53 <Dialog.ScrollableInner 54 54 label={_(msg`External Media`)}
+6 -7
src/screens/Settings/components/ExportCarDialog.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' ··· 18 18 export function ExportCarDialog({ 19 19 control, 20 20 }: { 21 - control: Dialog.DialogOuterProps['control'] 21 + control: Dialog.DialogControlProps 22 22 }) { 23 23 const {_} = useLingui() 24 24 const t = useTheme() 25 25 const agent = useAgent() 26 - const [loading, setLoading] = React.useState(false) 26 + const [loading, setLoading] = useState(false) 27 27 28 - const download = React.useCallback(async () => { 28 + const download = useCallback(async () => { 29 29 if (!agent.session) { 30 30 return // shouldnt ever happen 31 31 } ··· 52 52 }, [_, control, agent]) 53 53 54 54 return ( 55 - <Dialog.Outer control={control}> 55 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 56 56 <Dialog.Handle /> 57 57 <Dialog.ScrollableInner 58 58 accessibilityDescribedBy="dialog-description" ··· 63 63 </Text> 64 64 <Text 65 65 nativeID="dialog-description" 66 - style={[a.text_sm, a.leading_normal, t.atoms.text_contrast_high]}> 66 + style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_high]}> 67 67 <Trans> 68 68 Your account repository, containing all public data records, can 69 69 be downloaded as a "CAR" file. This file does not include media ··· 73 73 </Text> 74 74 75 75 <Button 76 - variant="solid" 77 76 color="primary" 78 77 size="large" 79 78 label={_(msg`Download CAR file`)}