diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 9bf1fb35ea..0edc9f2ad2 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -50,7 +50,7 @@ export const MAX_DM_GRAPHEME_LENGTH = 1000
// Recommended is 100 per: https://www.w3.org/WAI/GL/WCAG20/tests/test3.html
// but increasing limit per user feedback
-export const MAX_ALT_TEXT = 1000
+export const MAX_ALT_TEXT = 2000
export function IS_TEST_USER(handle?: string) {
return handle && handle?.endsWith('.test')
diff --git a/src/view/com/composer/AltTextCounterWrapper.tsx b/src/view/com/composer/AltTextCounterWrapper.tsx
new file mode 100644
index 0000000000..d69252f4b1
--- /dev/null
+++ b/src/view/com/composer/AltTextCounterWrapper.tsx
@@ -0,0 +1,33 @@
+import React from 'react'
+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 (
+
+
+ {children}
+
+ )
+}
diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx
index 3479fb973c..90d20d94f7 100644
--- a/src/view/com/composer/GifAltText.tsx
+++ b/src/view/com/composer/GifAltText.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useState} from 'react'
+import React, {useState} from 'react'
import {TouchableOpacity, View} from 'react-native'
import {AppBskyEmbedExternal} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
@@ -11,14 +11,16 @@ import {
EmbedPlayerParams,
parseEmbedPlayerFromUrl,
} from '#/lib/strings/embed-player'
-import {enforceLen} from '#/lib/strings/helpers'
import {isAndroid} from '#/platform/detection'
import {Gif} from '#/state/queries/tenor'
+import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
import {atoms as a, native, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
+import {DialogControlProps} from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
@@ -52,18 +54,11 @@ export function GifAltText({
}
}, [linkProp])
- const onPressSubmit = useCallback(
- (alt: string) => {
- control.close(() => {
- onSubmit(alt)
- })
- },
- [onSubmit, control],
- )
+ const parsedAlt = parseAltFromGIFDescription(link.description)
+ const [altText, setAltText] = useState(parsedAlt.alt)
if (!gif || !params) return null
- const parsedAlt = parseAltFromGIFDescription(link.description)
return (
<>
-
+ {
+ onSubmit(altText)
+ }}
+ Portal={Portal}>
@@ -114,61 +115,83 @@ export function GifAltText({
}
function AltTextInner({
- onSubmit,
+ altText,
+ setAltText,
+ control,
link,
params,
- initialValue: initalValue,
}: {
- onSubmit: (text: string) => void
+ altText: string
+ setAltText: (text: string) => void
+ control: DialogControlProps
link: AppBskyEmbedExternal.ViewExternal
params: EmbedPlayerParams
- initialValue: string
}) {
- const {_} = useLingui()
- const [altText, setAltText] = useState(initalValue)
- const control = Dialog.useDialogContext()
-
- const onPressSubmit = useCallback(() => {
- onSubmit(altText)
- }, [onSubmit, altText])
+ const t = useTheme()
+ const {_, i18n} = useLingui()
return (
-
-
- Descriptive alt text
-
-
-
- setAltText(enforceLen(text, MAX_ALT_TEXT))
- }
- value={altText}
- multiline
- numberOfLines={3}
- autoFocus
- onKeyPress={({nativeEvent}) => {
- if (nativeEvent.key === 'Escape') {
- control.close()
- }
- }}
- />
-
+
+
+
+ Descriptive alt text
+
+
+ {
+ setAltText(text)
+ }}
+ defaultValue={altText}
+ multiline
+ numberOfLines={3}
+ autoFocus
+ onKeyPress={({nativeEvent}) => {
+ if (nativeEvent.key === 'Escape') {
+ control.close()
+ }
+ }}
+ />
+
+
+
+ {altText.length > MAX_ALT_TEXT && (
+
+
+
+
+ Alt text will be truncated. Limit:{' '}
+ {i18n.number(MAX_ALT_TEXT)} characters.
+
+
+
+ )}
-
+
+
+
+
{/* below the text input to force tab order */}
diff --git a/src/view/com/composer/char-progress/CharProgress.tsx b/src/view/com/composer/char-progress/CharProgress.tsx
index a205fe0963..c61f753f26 100644
--- a/src/view/com/composer/char-progress/CharProgress.tsx
+++ b/src/view/com/composer/char-progress/CharProgress.tsx
@@ -1,48 +1,56 @@
import React from 'react'
-import {View} from 'react-native'
+import {StyleProp, TextStyle, View, 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 {usePalette} from 'lib/hooks/usePalette'
-import {s} from 'lib/styles'
+import {MAX_GRAPHEME_LENGTH} from '#/lib/constants'
+import {usePalette} from '#/lib/hooks/usePalette'
+import {s} from '#/lib/styles'
import {Text} from '../../util/text/Text'
-const DANGER_LENGTH = MAX_GRAPHEME_LENGTH
-
-export function CharProgress({count}: {count: number}) {
+export function CharProgress({
+ count,
+ max,
+ style,
+ textStyle,
+ size,
+}: {
+ count: number
+ max?: number
+ style?: StyleProp
+ textStyle?: StyleProp
+ size?: number
+}) {
+ const maxLength = max || MAX_GRAPHEME_LENGTH
const pal = usePalette('default')
- const textColor = count > DANGER_LENGTH ? '#e60000' : pal.colors.text
- const circleColor = count > DANGER_LENGTH ? '#e60000' : pal.colors.link
+ const textColor = count > maxLength ? '#e60000' : pal.colors.text
+ const circleColor = count > maxLength ? '#e60000' : pal.colors.link
return (
- <>
-
- {MAX_GRAPHEME_LENGTH - count}
+
+
+ {maxLength - count}
- {count > DANGER_LENGTH ? (
+ {count > maxLength ? (
) : (
)}
- >
+
)
}
diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx
index 16ce4351af..e9e8d42224 100644
--- a/src/view/com/composer/photos/ImageAltTextDialog.tsx
+++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx
@@ -5,12 +5,16 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {MAX_ALT_TEXT} from '#/lib/constants'
+import {enforceLen} from '#/lib/strings/helpers'
import {isAndroid, isWeb} from '#/platform/detection'
import {ComposerImage} from '#/state/gallery'
+import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
+import {DialogControlProps} from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {PortalComponent} from '#/components/Portal'
import {Text} from '#/components/Typography'
@@ -21,32 +25,50 @@ type Props = {
Portal: PortalComponent
}
-export const ImageAltTextDialog = (props: Props): React.ReactNode => {
+export const ImageAltTextDialog = ({
+ control,
+ image,
+ onChange,
+ Portal,
+}: Props): React.ReactNode => {
+ const [altText, setAltText] = React.useState(image.alt)
+
return (
-
+ {
+ onChange({
+ ...image,
+ alt: enforceLen(altText, MAX_ALT_TEXT, true),
+ })
+ }}
+ Portal={Portal}>
-
+
)
}
const ImageAltTextInner = ({
+ altText,
+ setAltText,
control,
image,
- onChange,
-}: Props): React.ReactNode => {
- const {_} = useLingui()
+}: {
+ altText: string
+ setAltText: (text: string) => void
+ control: DialogControlProps
+ image: Props['image']
+}): React.ReactNode => {
+ const {_, i18n} = useLingui()
const t = useTheme()
-
const windim = useWindowDimensions()
- const [altText, setAltText] = React.useState(image.alt)
-
- const onPressSubmit = React.useCallback(() => {
- control.close()
- onChange({...image, alt: altText.trim()})
- }, [control, image, altText, onChange])
-
const imageStyle = React.useMemo(() => {
const maxWidth = isWeb ? 450 : windim.width
const source = image.transformed ?? image.source
@@ -90,32 +112,59 @@ const ImageAltTextInner = ({
-
-
- Descriptive alt text
-
-
- setAltText(text)}
- value={altText}
- multiline
- numberOfLines={3}
- autoFocus
- />
-
+
+
+
+ Descriptive alt text
+
+
+ {
+ setAltText(text)
+ }}
+ defaultValue={altText}
+ multiline
+ numberOfLines={3}
+ autoFocus
+ />
+
+
+
+ {altText.length > MAX_ALT_TEXT && (
+
+
+
+
+ Alt text will be truncated. Limit: {i18n.number(MAX_ALT_TEXT)}{' '}
+ characters.
+
+
+
+ )}
-
+
+
+
+
{/* Maybe fix this later -h */}
{isAndroid ? : null}