Compare commits

...

4 Commits

Author SHA1 Message Date
Samuel Newman 1dd047fb85 fix up other charprogress usage locations 2026-05-21 15:02:01 +03:00
Samuel Newman 58cdc6589a get rid of AltTextCounterWrapper 2026-05-21 14:41:05 +03:00
Samuel Newman 4529d13558 change charprogress appearance 2026-05-21 14:31:28 +03:00
Samuel Newman 16699d98a5 vendor progress circle 2026-05-21 14:31:01 +03:00
18 changed files with 315 additions and 169 deletions
+1 -1
View File
@@ -36,7 +36,7 @@
},
"src/components/Dialog/index.web.tsx": {
"@typescript-eslint/no-explicit-any": {
"count": 4
"count": 2
}
},
"src/components/DraggableList/index.web.tsx": {
-1
View File
@@ -222,7 +222,6 @@
"react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "^1.21.7",
"react-native-pager-view": "6.8.0",
"react-native-progress": "bluesky-social/react-native-progress",
"react-native-qrcode-styled": "^0.3.3",
"react-native-reanimated": "3.19.1",
"react-native-safe-area-context": "~5.6.0",
-14
View File
@@ -454,9 +454,6 @@ importers:
react-native-pager-view:
specifier: 6.8.0
version: 6.8.0(patch_hash=0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-progress:
specifier: bluesky-social/react-native-progress
version: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))
react-native-qrcode-styled:
specifier: ^0.3.3
version: 0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -8686,12 +8683,6 @@ packages:
react: '*'
react-native: '*'
react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4:
resolution: {gitHosted: true, integrity: sha512-gDJJirvdhK5uUuI2OkiuleTbmeCyjkf1G6kPd4HAMc1pW8pUUeA0wKyPxZaI+s9htw8IDH6mFlmBB1ob8QMB4g==, tarball: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4}
version: 5.0.0
peerDependencies:
react-native-svg: '*'
react-native-qrcode-styled@0.3.3:
resolution: {integrity: sha512-DjxFhFAJBY3y2FEBbWSpwtPaSjsEc5RutTHejxzfRNuyTzg1f5thplGyON8yx0kbi6h0h+57s8cH4aSFGj1mQg==}
engines: {node: '>= 16.0.0'}
@@ -19954,11 +19945,6 @@ snapshots:
react: 19.1.0
react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)):
dependencies:
prop-types: 15.8.1
react-native-svg: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-qrcode-styled@0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
qrcode: 1.5.4
+7 -4
View File
@@ -15,7 +15,6 @@ import {
Pressable,
ScrollView,
type StyleProp,
TextInput,
View,
type ViewStyle,
} from 'react-native'
@@ -42,7 +41,7 @@ import {
type DialogInnerProps,
type DialogOuterProps,
} from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField'
import {Input} from '#/components/forms/TextField'
import {useOnKeyboard} from '#/components/hooks/useOnKeyboard'
import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env'
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
@@ -51,13 +50,17 @@ import {
type BottomSheetStateChangeEvent,
} from '../../../modules/bottom-sheet/src/BottomSheet.types'
import {type BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent'
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
export * from '#/components/Dialog/shared'
export * from '#/components/Dialog/types'
export * from '#/components/Dialog/utils'
export const Input = createInput(TextInput)
export {
/**
* @deprecated use `TextField.Input` from `#/components/forms/TextField` instead
*/
Input,
}
export function Outer({
children,
+11 -3
View File
@@ -32,6 +32,7 @@ import {
type DialogInnerProps,
type DialogOuterProps,
} from '#/components/Dialog/types'
import {Input} from '#/components/forms/TextField'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Portal} from '#/components/Portal'
@@ -39,13 +40,20 @@ export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
export * from '#/components/Dialog/shared'
export * from '#/components/Dialog/types'
export * from '#/components/Dialog/utils'
export {Input} from '#/components/forms/TextField'
export {
/**
* @deprecated use `TextField.Input` from `#/components/forms/TextField` instead
*/
Input,
}
// 100 minus 10vh of paddingVertical
export const WEB_DIALOG_HEIGHT = '80vh'
const stopPropagation = (e: any) => e.stopPropagation()
const preventDefault = (e: any) => e.preventDefault()
const stopPropagation = (e: {stopPropagation: () => void}) =>
e.stopPropagation()
const preventDefault = (e: {preventDefault: () => void}) => e.preventDefault()
export function Outer({
children,
+85
View File
@@ -0,0 +1,85 @@
import {useEffect} from 'react'
import Animated, {
Easing,
useAnimatedProps,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import Svg, {Circle as SvgCircle} from 'react-native-svg'
import {clamp01} from './shared'
const AnimatedCircle = Animated.createAnimatedComponent(SvgCircle)
export function ProgressCircle({
progress,
size,
color,
thickness = 3,
borderWidth = 1,
borderColor,
unfilledColor,
}: {
progress: number
size: number
color: string
thickness?: number
borderWidth?: number
borderColor?: string
unfilledColor?: string
}) {
const stroke = borderColor ?? color
const r = (size - Math.max(borderWidth, thickness)) / 2
const cx = size / 2
const circumference = 2 * Math.PI * r
const sv = useSharedValue(clamp01(progress))
useEffect(() => {
sv.set(
withTiming(clamp01(progress), {
duration: 200,
easing: Easing.out(Easing.quad),
}),
)
}, [progress, sv])
const animatedProps = useAnimatedProps(() => ({
strokeDashoffset: circumference * (1 - sv.get()),
}))
return (
<Svg width={size} height={size}>
{borderWidth > 0 && (
<SvgCircle
cx={cx}
cy={cx}
r={r}
stroke={stroke}
strokeWidth={borderWidth}
fill="none"
/>
)}
{unfilledColor && (
<SvgCircle
cx={cx}
cy={cx}
r={r}
stroke={unfilledColor}
strokeWidth={thickness}
fill="none"
/>
)}
<AnimatedCircle
cx={cx}
cy={cx}
r={r}
stroke={color}
strokeWidth={thickness}
fill="none"
strokeDasharray={circumference}
animatedProps={animatedProps}
transform={`rotate(-90 ${cx} ${cx})`}
/>
</Svg>
)
}
+70
View File
@@ -0,0 +1,70 @@
import {useEffect} from 'react'
import Animated, {
Easing,
useAnimatedProps,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import Svg, {Circle as SvgCircle, Path} from 'react-native-svg'
import {clamp01} from './shared'
const AnimatedPath = Animated.createAnimatedComponent(Path)
function describeWedge(cx: number, cy: number, r: number, progress: number) {
'worklet'
// avoid degenerate path at 360deg where start and end points collide
const p = progress >= 1 ? 0.9999 : progress
if (p <= 0) return ''
const angle = p * 2 * Math.PI - Math.PI / 2
const x = cx + r * Math.cos(angle)
const y = cy + r * Math.sin(angle)
const largeArc = p > 0.5 ? 1 : 0
return `M ${cx} ${cy} L ${cx} ${cy - r} A ${r} ${r} 0 ${largeArc} 1 ${x} ${y} Z`
}
export function ProgressPie({
progress,
size,
color,
borderWidth = 1,
borderColor,
}: {
progress: number
size: number
color: string
borderWidth?: number
borderColor?: string
}) {
const stroke = borderColor ?? color
const r = (size - borderWidth) / 2
const cx = size / 2
const sv = useSharedValue(clamp01(progress))
useEffect(() => {
sv.set(
withTiming(clamp01(progress), {
duration: 200,
easing: Easing.out(Easing.quad),
}),
)
}, [progress, sv])
const animatedProps = useAnimatedProps(() => ({
d: describeWedge(cx, cx, r, sv.get()),
}))
return (
<Svg width={size} height={size}>
<SvgCircle
cx={cx}
cy={cx}
r={r}
stroke={stroke}
strokeWidth={borderWidth}
fill="none"
/>
<AnimatedPath fill={color} animatedProps={animatedProps} />
</Svg>
)
}
+2
View File
@@ -0,0 +1,2 @@
export {ProgressCircle} from './Circle'
export {ProgressPie} from './Pie'
+6
View File
@@ -0,0 +1,6 @@
export function clamp01(n: number) {
'worklet'
if (n < 0) return 0
if (n > 1) return 1
return n
}
+2 -2
View File
@@ -1,8 +1,8 @@
import {View} from 'react-native'
import * as Progress from 'react-native-progress'
import {atoms as a, useTheme} from '#/alf'
import {AnimatedCheck} from '../anim/AnimatedCheck'
import {ProgressCircle} from '../Progress'
import {Text} from '../Typography'
export function ProgressGuideTask({
@@ -25,7 +25,7 @@ export function ProgressGuideTask({
{current === total ? (
<AnimatedCheck playOnMount fill={t.palette.primary_500} width={20} />
) : (
<Progress.Circle
<ProgressCircle
progress={current / total}
color={t.palette.primary_400}
size={20}
@@ -24,6 +24,7 @@ import * as Admonition from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
import * as TextField from '#/components/forms/TextField'
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotate'
import {
@@ -540,30 +541,34 @@ function Inner(props: ReportDialogProps) {
{state.detailsOpen && (
<View>
<Dialog.Input
testID="report:details"
multiline
value={state.details}
onChangeText={details => {
dispatch({type: 'setDetails', details})
}}
label={_(msg`Additional details (limit 300 characters)`)}
style={{paddingRight: 60}}
numberOfLines={4}
/>
<TextField.Root>
<TextField.Input
testID="report:details"
multiline
value={state.details}
onChangeText={details => {
dispatch({type: 'setDetails', details})
}}
label={_(
msg`Additional details (limit 300 characters)`,
)}
style={{paddingRight: 50}}
numberOfLines={4}
/>
</TextField.Root>
<View
style={[
a.absolute,
a.flex_row,
a.align_center,
a.pr_md,
a.pb_sm,
{
bottom: 0,
right: 0,
},
a.bottom_0,
a.right_0,
]}>
<CharProgress count={state.details?.length || 0} />
<CharProgress
size={30}
count={state.details?.length || 0}
/>
</View>
</View>
)}
+2 -6
View File
@@ -177,14 +177,10 @@ export function Takendown() {
<View
style={[
a.absolute,
a.flex_row,
a.align_center,
a.pr_md,
a.pb_sm,
{
bottom: 0,
right: 0,
},
a.bottom_0,
a.right_0,
]}>
<CharProgress
count={reasonGraphemeLength}
@@ -1,32 +0,0 @@
import {View} from 'react-native'
import {MAX_ALT_TEXT} from '#/lib/constants'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import {atoms as a, useTheme} from '#/alf'
export function AltTextCounterWrapper({
altText,
children,
}: {
altText?: string
children: React.ReactNode
}) {
const t = useTheme()
return (
<View style={[a.flex_row]}>
<CharProgress
style={[
a.flex_col_reverse,
a.align_center,
a.mr_xs,
{minWidth: 50, gap: 1},
]}
textStyle={[{marginRight: 0}, a.text_sm, t.atoms.text_contrast_medium]}
size={26}
count={altText?.length || 0}
max={MAX_ALT_TEXT}
/>
{children}
</View>
)
}
+7 -11
View File
@@ -21,8 +21,6 @@ import {
View,
type ViewStyle,
} from 'react-native'
// @ts-expect-error no type definition
import ProgressCircle from 'react-native-progress/Circle'
import Animated, {
type AnimatedRef,
Easing,
@@ -129,6 +127,7 @@ import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/i
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
import {ProgressCircle} from '#/components/Progress'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
@@ -2072,10 +2071,7 @@ function ComposerFooter({
onSelectLanguage={onSelectLanguage}
nudgeAt={languageNudgeAt}
/>
<CharProgress
count={post.shortenedGraphemeLength}
style={{width: 65}}
/>
<CharProgress count={post.shortenedGraphemeLength} />
</View>
</View>
)
@@ -2413,7 +2409,7 @@ function ToolbarWrapper({
if (IS_WEB) return children
return (
<Animated.View
style={style}
style={[style, {minHeight: 40}]}
entering={FadeIn.duration(400)}
exiting={FadeOut.duration(400)}>
{children}
@@ -2488,12 +2484,12 @@ function VideoUploadToolbar({state}: {state: VideoState}) {
}
return (
<ToolbarWrapper style={[a.flex_row, a.align_center, {paddingVertical: 5}]}>
<ToolbarWrapper style={[a.flex_row, a.align_center]}>
<Animated.View style={[animatedStyle]}>
<ProgressCircle
size={30}
borderWidth={1}
borderColor={t.atoms.border_contrast_low.borderColor}
size={32}
borderWidth={3}
borderColor={t.palette.contrast_50}
color={
state.status === 'error'
? t.palette.negative_500
+19 -19
View File
@@ -3,6 +3,7 @@ import {TouchableOpacity, View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {countGraphemes} from 'unicode-segmenter/grapheme'
import {HITSLOP_10, MAX_ALT_TEXT} from '#/lib/constants'
import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
@@ -11,7 +12,6 @@ import {
parseEmbedPlayerFromUrl,
} from '#/lib/strings/embed-player'
import {useResolveGifQuery} from '#/state/queries/resolve-link'
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
import {atoms as a, useTheme} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonText} from '#/components/Button'
@@ -146,6 +146,8 @@ function AltTextInner({
const t = useTheme()
const {_, i18n} = useLingui()
const altTextLength = countGraphemes(altText)
return (
<Dialog.ScrollableInner label={_(msg`Add alt text`)}>
<View style={a.flex_col_reverse}>
@@ -153,7 +155,9 @@ function AltTextInner({
<View style={[a.gap_sm]}>
<View style={[a.relative]}>
<TextField.LabelText>
<Trans>Descriptive alt text</Trans>
<Trans>
Descriptive alt text ({altTextLength}/{MAX_ALT_TEXT})
</Trans>
</TextField.LabelText>
<TextField.Root>
<Dialog.Input
@@ -173,9 +177,9 @@ function AltTextInner({
</TextField.Root>
</View>
{altText.length > MAX_ALT_TEXT && (
{altTextLength > MAX_ALT_TEXT && (
<View style={[a.pb_sm, a.flex_row, a.gap_xs]}>
<CircleInfo fill={t.palette.negative_500} />
<CircleInfo fill={t.palette.negative_500} size="sm" />
<Text
style={[
a.italic,
@@ -194,21 +198,17 @@ function AltTextInner({
)}
</View>
<AltTextCounterWrapper altText={altText}>
<Button
label={_(msg`Save`)}
size="large"
color="primary"
variant="solid"
onPress={() => {
control.close()
}}
style={[a.flex_grow]}>
<ButtonText>
<Trans>Save</Trans>
</ButtonText>
</Button>
</AltTextCounterWrapper>
<Button
label={_(msg`Save`)}
size="large"
color="primary"
onPress={() => {
control.close()
}}>
<ButtonText>
<Trans>Save</Trans>
</ButtonText>
</Button>
</View>
{/* below the text input to force tab order */}
<View>
@@ -4,18 +4,16 @@ import {
View,
type ViewStyle,
} from 'react-native'
// @ts-ignore no type definition -prf
import ProgressCircle from 'react-native-progress/Circle'
// @ts-ignore no type definition -prf
import ProgressPie from 'react-native-progress/Pie'
import {MAX_GRAPHEME_LENGTH} from '#/lib/constants'
import {atoms as a, useTheme} from '#/alf'
import {ProgressCircle} from '#/components/Progress'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
export function CharProgress({
count,
max,
max = MAX_GRAPHEME_LENGTH,
style,
textStyle,
size,
@@ -26,40 +24,63 @@ export function CharProgress({
textStyle?: StyleProp<TextStyle>
size?: number
}) {
const maxLength = max || MAX_GRAPHEME_LENGTH
const t = useTheme()
const textColor = count > maxLength ? '#e60000' : t.atoms.text.color
const circleColor = count > maxLength ? '#e60000' : t.palette.primary_500
const textColor = count > max ? '#e60000' : t.atoms.text.color
const circleColor = count > max ? '#e60000' : t.palette.primary_500
//
if (IS_WEB && count > max) {
return (
<View style={[style, {minHeight: size}]}>
<Text
style={[
{color: textColor, fontVariant: ['tabular-nums']},
a.text_center,
a.text_xs,
{maxWidth: '100%'},
textStyle,
]}
maxFontSizeMultiplier={1}
numberOfLines={1}>
{max - count}
</Text>
</View>
)
}
return (
<View
style={[a.flex_row, a.align_center, a.justify_between, a.gap_sm, style]}>
<Text
style={[
{color: textColor, fontVariant: ['tabular-nums']},
a.flex_grow,
a.text_right,
textStyle,
]}
maxFontSizeMultiplier={1}>
{maxLength - count}
</Text>
{count > maxLength ? (
<ProgressPie
size={size ?? 30}
borderWidth={4}
borderColor={circleColor}
color={circleColor}
progress={Math.min((count - maxLength) / maxLength, 1)}
/>
) : (
<ProgressCircle
size={size ?? 30}
borderWidth={1}
borderColor={t.atoms.border_contrast_low.borderColor}
color={circleColor}
progress={count / maxLength}
/>
<View style={[style]}>
{max - count < 100 && (
<View
style={[
a.absolute,
a.inset_0,
a.justify_center,
a.align_center,
a.px_xs,
]}>
<Text
style={[
{color: textColor, fontVariant: ['tabular-nums']},
a.text_center,
a.text_xs,
{maxWidth: '100%'},
textStyle,
]}
maxFontSizeMultiplier={1}
adjustsFontSizeToFit
numberOfLines={1}>
{max - count}
</Text>
</View>
)}
<ProgressCircle
size={size ?? 32}
borderWidth={3}
borderColor={t.palette.contrast_50}
color={circleColor}
progress={Math.min(count / max, 1)}
/>
</View>
)
}
@@ -4,11 +4,11 @@ import {Image} from 'expo-image'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {countGraphemes} from 'unicode-segmenter'
import {MAX_ALT_TEXT} from '#/lib/constants'
import {enforceLen} from '#/lib/strings/helpers'
import {type ComposerImage} from '#/state/gallery'
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
import {atoms as a, tokens, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -91,6 +91,8 @@ const ImageAltTextInner = ({
}
}, [image, screenWidth])
const altTextLength = countGraphemes(altText)
return (
<Dialog.ScrollableInner label={_(msg`Add alt text`)}>
<Dialog.Close />
@@ -121,7 +123,9 @@ const ImageAltTextInner = ({
<View style={[a.gap_sm]}>
<View style={[a.relative, {width: '100%'}]}>
<TextField.LabelText>
<Trans>Descriptive alt text</Trans>
<Trans>
Descriptive alt text ({altTextLength}/{MAX_ALT_TEXT})
</Trans>
</TextField.LabelText>
<TextField.Root>
<Dialog.Input
@@ -137,9 +141,9 @@ const ImageAltTextInner = ({
</TextField.Root>
</View>
{altText.length > MAX_ALT_TEXT && (
{altTextLength > MAX_ALT_TEXT && (
<View style={[a.pb_sm, a.flex_row, a.gap_xs]}>
<CircleInfo fill={t.palette.negative_500} />
<CircleInfo fill={t.palette.negative_500} size="sm" />
<Text
style={[
a.italic,
@@ -158,22 +162,20 @@ const ImageAltTextInner = ({
)}
</View>
<AltTextCounterWrapper altText={altText}>
<Button
label={_(msg`Save`)}
disabled={altText === image.alt}
size="large"
color="primary"
variant="solid"
onPress={() => {
control.close()
}}
style={[a.flex_grow]}>
<ButtonText>
<Trans>Save</Trans>
</ButtonText>
</Button>
</AltTextCounterWrapper>
<Button
label={_(msg`Save`)}
disabled={altText === image.alt}
size="large"
color="primary"
variant="solid"
onPress={() => {
control.close()
}}
style={[a.flex_grow]}>
<ButtonText>
<Trans>Save</Trans>
</ButtonText>
</Button>
</View>
</Dialog.ScrollableInner>
)
@@ -1,10 +1,9 @@
import {View} from 'react-native'
// @ts-expect-error no type definition
import ProgressPie from 'react-native-progress/Pie'
import {type ImagePickerAsset} from 'expo-image-picker'
import {atoms as a, useTheme} from '#/alf'
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
import {ProgressPie} from '#/components/Progress'
import {IS_WEB} from '#/env'
import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn'
import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'