[APP-724] Collection of accessibility fixes (#949)
* Fix: include alt text on the web lightbox image * a11y: Dont read the 'ALT' label * a11y: remove a wrapper behavior from posts This appears to have been introduced with the goal of creating meta actions on posts, but the behavior seems counter-productive. The accessibility inspector was unable to access individual items within the post and therefore most content was simply skipped. There may be a way to support the post actions without losing the ability to access the inner elements but I couldnt find it. -prf * a11y: apply alt tags to image wrappers so they get read * a11y: set Link accessibilityLabel to the title if none set * a11y: skip the SANDBOX watermark * a11y: improve post meta to not read UI and give a useful date * ally: improve post controls * a11y: add labels to lightbox images on mobile * fix typeszio/stable
parent
0163ba0af8
commit
bc55241c9a
|
@ -89,7 +89,9 @@ export const Gallery = observer(function ({gallery}: Props) {
|
||||||
openAltTextModal(store, image)
|
openAltTextModal(store, image)
|
||||||
}}
|
}}
|
||||||
style={[styles.altTextControl, altTextControlStyle]}>
|
style={[styles.altTextControl, altTextControlStyle]}>
|
||||||
<Text style={styles.altTextControlLabel}>ALT</Text>
|
<Text style={styles.altTextControlLabel} accessible={false}>
|
||||||
|
ALT
|
||||||
|
</Text>
|
||||||
{image.altText.length > 0 ? (
|
{image.altText.length > 0 ? (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon="check"
|
icon="check"
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ImageURISource, ImageRequireSource} from 'react-native'
|
|
||||||
|
|
||||||
export type Dimensions = {
|
export type Dimensions = {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
@ -18,4 +16,4 @@ export type Position = {
|
||||||
y: number
|
y: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImageSource = ImageURISource | ImageRequireSource
|
export type ImageSource = {uri: string; alt?: string}
|
||||||
|
|
|
@ -133,6 +133,8 @@ const ImageItem = ({
|
||||||
source={imageSrc}
|
source={imageSrc}
|
||||||
style={imageStylesWithOpacity}
|
style={imageStylesWithOpacity}
|
||||||
onLoad={onLoaded}
|
onLoad={onLoaded}
|
||||||
|
accessibilityLabel={imageSrc.alt}
|
||||||
|
accessibilityHint=""
|
||||||
/>
|
/>
|
||||||
{(!isLoaded || !imageDimensions) && <ImageLoading />}
|
{(!isLoaded || !imageDimensions) && <ImageLoading />}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
@ -128,7 +128,9 @@ const ImageItem = ({
|
||||||
onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined}
|
onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined}
|
||||||
onLongPress={onLongPressHandler}
|
onLongPress={onLongPressHandler}
|
||||||
delayLongPress={delayLongPress}
|
delayLongPress={delayLongPress}
|
||||||
accessibilityRole="image">
|
accessibilityRole="image"
|
||||||
|
accessibilityLabel={imageSrc.alt}
|
||||||
|
accessibilityHint="">
|
||||||
<Animated.Image
|
<Animated.Image
|
||||||
source={imageSrc}
|
source={imageSrc}
|
||||||
style={imageStylesWithOpacity}
|
style={imageStylesWithOpacity}
|
||||||
|
|
|
@ -109,7 +109,7 @@ export const Lightbox = observer(function Lightbox() {
|
||||||
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
|
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
|
||||||
return (
|
return (
|
||||||
<ImageView
|
<ImageView
|
||||||
images={[{uri: opts.profileView.avatar}]}
|
images={[{uri: opts.profileView.avatar || ''}]}
|
||||||
imageIndex={0}
|
imageIndex={0}
|
||||||
visible
|
visible
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
|
@ -120,7 +120,7 @@ export const Lightbox = observer(function Lightbox() {
|
||||||
const opts = store.shell.activeLightbox as models.ImagesLightbox
|
const opts = store.shell.activeLightbox as models.ImagesLightbox
|
||||||
return (
|
return (
|
||||||
<ImageView
|
<ImageView
|
||||||
images={opts.images.map(({uri}) => ({uri}))}
|
images={opts.images.map(img => ({...img}))}
|
||||||
imageIndex={opts.index}
|
imageIndex={opts.index}
|
||||||
visible
|
visible
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
|
|
|
@ -109,6 +109,8 @@ function LightboxInner({
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
source={imgs[index]}
|
source={imgs[index]}
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
|
accessibilityLabel={imgs[index].alt}
|
||||||
|
accessibilityHint=""
|
||||||
/>
|
/>
|
||||||
{canGoLeft && (
|
{canGoLeft && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {useCallback, useMemo} from 'react'
|
import React, {useMemo} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {AccessibilityActionEvent, Linking, StyleSheet, View} from 'react-native'
|
import {Linking, StyleSheet, View} from 'react-native'
|
||||||
import Clipboard from '@react-native-clipboard/clipboard'
|
import Clipboard from '@react-native-clipboard/clipboard'
|
||||||
import {AtUri, AppBskyFeedDefs} from '@atproto/api'
|
import {AtUri, AppBskyFeedDefs} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
|
@ -138,40 +138,6 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
)
|
)
|
||||||
}, [item, store])
|
}, [item, store])
|
||||||
|
|
||||||
const accessibilityActions = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
name: 'reply',
|
|
||||||
label: 'Reply',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'repost',
|
|
||||||
label: item.post.viewer?.repost ? 'Undo repost' : 'Repost',
|
|
||||||
},
|
|
||||||
{name: 'like', label: item.post.viewer?.like ? 'Unlike' : 'Like'},
|
|
||||||
],
|
|
||||||
[item.post.viewer?.like, item.post.viewer?.repost],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAccessibilityAction = useCallback(
|
|
||||||
(event: AccessibilityActionEvent) => {
|
|
||||||
switch (event.nativeEvent.actionName) {
|
|
||||||
case 'like':
|
|
||||||
onPressToggleLike()
|
|
||||||
break
|
|
||||||
case 'reply':
|
|
||||||
onPressReply()
|
|
||||||
break
|
|
||||||
case 'repost':
|
|
||||||
onPressToggleRepost()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onPressReply, onPressToggleLike, onPressToggleRepost],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
return <ErrorMessage message="Invalid or unsupported post record" />
|
return <ErrorMessage message="Invalid or unsupported post record" />
|
||||||
}
|
}
|
||||||
|
@ -193,9 +159,7 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
<PostHider
|
<PostHider
|
||||||
testID={`postThreadItem-by-${item.post.author.handle}`}
|
testID={`postThreadItem-by-${item.post.author.handle}`}
|
||||||
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
|
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
|
||||||
moderation={item.moderation.thread}
|
moderation={item.moderation.thread}>
|
||||||
accessibilityActions={accessibilityActions}
|
|
||||||
onAccessibilityAction={onAccessibilityAction}>
|
|
||||||
<PostSandboxWarning />
|
<PostSandboxWarning />
|
||||||
<View style={styles.layout}>
|
<View style={styles.layout}>
|
||||||
<View style={styles.layoutAvi}>
|
<View style={styles.layoutAvi}>
|
||||||
|
@ -369,9 +333,7 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
pal.view,
|
pal.view,
|
||||||
item._showParentReplyLine && styles.noTopBorder,
|
item._showParentReplyLine && styles.noTopBorder,
|
||||||
]}
|
]}
|
||||||
moderation={item.moderation.thread}
|
moderation={item.moderation.thread}>
|
||||||
accessibilityActions={accessibilityActions}
|
|
||||||
onAccessibilityAction={onAccessibilityAction}>
|
|
||||||
{item._showParentReplyLine && (
|
{item._showParentReplyLine && (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, {useCallback, useEffect, useMemo, useState} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {
|
import {
|
||||||
AccessibilityActionEvent,
|
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Linking,
|
Linking,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
|
@ -200,47 +199,11 @@ const PostLoaded = observer(
|
||||||
)
|
)
|
||||||
}, [item, setDeleted, store])
|
}, [item, setDeleted, store])
|
||||||
|
|
||||||
const accessibilityActions = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
name: 'reply',
|
|
||||||
label: 'Reply',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'repost',
|
|
||||||
label: item.post.viewer?.repost ? 'Undo repost' : 'Repost',
|
|
||||||
},
|
|
||||||
{name: 'like', label: item.post.viewer?.like ? 'Unlike' : 'Like'},
|
|
||||||
],
|
|
||||||
[item.post.viewer?.like, item.post.viewer?.repost],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAccessibilityAction = useCallback(
|
|
||||||
(event: AccessibilityActionEvent) => {
|
|
||||||
switch (event.nativeEvent.actionName) {
|
|
||||||
case 'like':
|
|
||||||
onPressToggleLike()
|
|
||||||
break
|
|
||||||
case 'reply':
|
|
||||||
onPressReply()
|
|
||||||
break
|
|
||||||
case 'repost':
|
|
||||||
onPressToggleRepost()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onPressReply, onPressToggleLike, onPressToggleRepost],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostHider
|
<PostHider
|
||||||
href={itemHref}
|
href={itemHref}
|
||||||
style={[styles.outer, pal.view, pal.border, style]}
|
style={[styles.outer, pal.view, pal.border, style]}
|
||||||
moderation={item.moderation.list}
|
moderation={item.moderation.list}>
|
||||||
accessibilityActions={accessibilityActions}
|
|
||||||
onAccessibilityAction={onAccessibilityAction}>
|
|
||||||
{showReplyLine && <View style={styles.replyLine} />}
|
{showReplyLine && <View style={styles.replyLine} />}
|
||||||
<View style={styles.layout}>
|
<View style={styles.layout}>
|
||||||
<View style={styles.layoutAvi}>
|
<View style={styles.layoutAvi}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {useCallback, useMemo, useState} from 'react'
|
import React, {useMemo, useState} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {AccessibilityActionEvent, Linking, StyleSheet, View} from 'react-native'
|
import {Linking, StyleSheet, View} from 'react-native'
|
||||||
import Clipboard from '@react-native-clipboard/clipboard'
|
import Clipboard from '@react-native-clipboard/clipboard'
|
||||||
import {AtUri} from '@atproto/api'
|
import {AtUri} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
|
@ -158,40 +158,6 @@ export const FeedItem = observer(function ({
|
||||||
moderation = {behavior: ModerationBehaviorCode.Show}
|
moderation = {behavior: ModerationBehaviorCode.Show}
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessibilityActions = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
name: 'reply',
|
|
||||||
label: 'Reply',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'repost',
|
|
||||||
label: item.post.viewer?.repost ? 'Undo repost' : 'Repost',
|
|
||||||
},
|
|
||||||
{name: 'like', label: item.post.viewer?.like ? 'Unlike' : 'Like'},
|
|
||||||
],
|
|
||||||
[item.post.viewer?.like, item.post.viewer?.repost],
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAccessibilityAction = useCallback(
|
|
||||||
(event: AccessibilityActionEvent) => {
|
|
||||||
switch (event.nativeEvent.actionName) {
|
|
||||||
case 'like':
|
|
||||||
onPressToggleLike()
|
|
||||||
break
|
|
||||||
case 'reply':
|
|
||||||
onPressReply()
|
|
||||||
break
|
|
||||||
case 'repost':
|
|
||||||
onPressToggleRepost()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onPressReply, onPressToggleLike, onPressToggleRepost],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!record || deleted) {
|
if (!record || deleted) {
|
||||||
return <View />
|
return <View />
|
||||||
}
|
}
|
||||||
|
@ -201,9 +167,7 @@ export const FeedItem = observer(function ({
|
||||||
testID={`feedItem-by-${item.post.author.handle}`}
|
testID={`feedItem-by-${item.post.author.handle}`}
|
||||||
style={outerStyles}
|
style={outerStyles}
|
||||||
href={itemHref}
|
href={itemHref}
|
||||||
moderation={moderation}
|
moderation={moderation}>
|
||||||
accessibilityActions={accessibilityActions}
|
|
||||||
onAccessibilityAction={onAccessibilityAction}>
|
|
||||||
{isThreadChild && (
|
{isThreadChild && (
|
||||||
<View
|
<View
|
||||||
style={[styles.topReplyLine, {borderColor: pal.colors.replyLine}]}
|
style={[styles.topReplyLine, {borderColor: pal.colors.replyLine}]}
|
||||||
|
|
|
@ -88,6 +88,10 @@ export const Link = observer(function Link({
|
||||||
props.dataSet.noUnderline = 1
|
props.dataSet.noUnderline = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (title && !props.accessibilityLabel) {
|
||||||
|
props.accessibilityLabel = title
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID={testID}
|
testID={testID}
|
||||||
|
@ -171,6 +175,7 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
||||||
text,
|
text,
|
||||||
numberOfLines,
|
numberOfLines,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
testID?: string
|
testID?: string
|
||||||
type?: TypographyVariant
|
type?: TypographyVariant
|
||||||
|
@ -179,6 +184,9 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
||||||
text: string | JSX.Element
|
text: string | JSX.Element
|
||||||
numberOfLines?: number
|
numberOfLines?: number
|
||||||
lineHeight?: number
|
lineHeight?: number
|
||||||
|
accessible?: boolean
|
||||||
|
accessibilityLabel?: string
|
||||||
|
accessibilityHint?: string
|
||||||
}) {
|
}) {
|
||||||
if (isDesktopWeb) {
|
if (isDesktopWeb) {
|
||||||
return (
|
return (
|
||||||
|
@ -190,6 +198,7 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
||||||
text={text}
|
text={text}
|
||||||
numberOfLines={numberOfLines}
|
numberOfLines={numberOfLines}
|
||||||
lineHeight={lineHeight}
|
lineHeight={lineHeight}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -199,7 +208,8 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
|
||||||
type={type}
|
type={type}
|
||||||
style={style}
|
style={style}
|
||||||
numberOfLines={numberOfLines}
|
numberOfLines={numberOfLines}
|
||||||
lineHeight={lineHeight}>
|
lineHeight={lineHeight}
|
||||||
|
{...props}>
|
||||||
{text}
|
{text}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
import {Text} from './text/Text'
|
import {Text} from './text/Text'
|
||||||
import {DesktopWebTextLink} from './Link'
|
import {DesktopWebTextLink} from './Link'
|
||||||
import {ago} from 'lib/strings/time'
|
import {ago, niceDate} from 'lib/strings/time'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {UserAvatar} from './UserAvatar'
|
import {UserAvatar} from './UserAvatar'
|
||||||
|
@ -57,7 +57,11 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
||||||
text={sanitizeDisplayName(displayName)}
|
text={sanitizeDisplayName(displayName)}
|
||||||
href={`/profile/${opts.authorHandle}`}
|
href={`/profile/${opts.authorHandle}`}
|
||||||
/>
|
/>
|
||||||
<Text type="md" style={pal.textLight} lineHeight={1.2}>
|
<Text
|
||||||
|
type="md"
|
||||||
|
style={pal.textLight}
|
||||||
|
lineHeight={1.2}
|
||||||
|
accessible={false}>
|
||||||
·
|
·
|
||||||
</Text>
|
</Text>
|
||||||
<DesktopWebTextLink
|
<DesktopWebTextLink
|
||||||
|
@ -65,6 +69,8 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
||||||
style={[styles.metaItem, pal.textLight]}
|
style={[styles.metaItem, pal.textLight]}
|
||||||
lineHeight={1.2}
|
lineHeight={1.2}
|
||||||
text={ago(opts.timestamp)}
|
text={ago(opts.timestamp)}
|
||||||
|
accessibilityLabel={niceDate(opts.timestamp)}
|
||||||
|
accessibilityHint=""
|
||||||
href={opts.postHref}
|
href={opts.postHref}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -122,7 +128,7 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
||||||
href={`/profile/${opts.authorHandle}`}
|
href={`/profile/${opts.authorHandle}`}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text type="md" style={pal.textLight} lineHeight={1.2}>
|
<Text type="md" style={pal.textLight} lineHeight={1.2} accessible={false}>
|
||||||
·
|
·
|
||||||
</Text>
|
</Text>
|
||||||
<DesktopWebTextLink
|
<DesktopWebTextLink
|
||||||
|
@ -130,6 +136,8 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
|
||||||
style={[styles.metaItem, pal.textLight]}
|
style={[styles.metaItem, pal.textLight]}
|
||||||
lineHeight={1.2}
|
lineHeight={1.2}
|
||||||
text={ago(opts.timestamp)}
|
text={ago(opts.timestamp)}
|
||||||
|
accessibilityLabel={niceDate(opts.timestamp)}
|
||||||
|
accessibilityHint=""
|
||||||
href={opts.postHref}
|
href={opts.postHref}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -10,7 +10,10 @@ export function PostSandboxWarning() {
|
||||||
if (store.session.isSandbox) {
|
if (store.session.isSandbox) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text type="title-2xl" style={[pal.text, styles.text]}>
|
<Text
|
||||||
|
type="title-2xl"
|
||||||
|
style={[pal.text, styles.text]}
|
||||||
|
accessible={false}>
|
||||||
SANDBOX
|
SANDBOX
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -50,6 +50,8 @@ interface DropdownButtonProps {
|
||||||
openToRight?: boolean
|
openToRight?: boolean
|
||||||
rightOffset?: number
|
rightOffset?: number
|
||||||
bottomOffset?: number
|
bottomOffset?: number
|
||||||
|
accessibilityLabel?: string
|
||||||
|
accessibilityHint?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DropdownButton({
|
export function DropdownButton({
|
||||||
|
@ -63,6 +65,7 @@ export function DropdownButton({
|
||||||
openToRight = false,
|
openToRight = false,
|
||||||
rightOffset = 0,
|
rightOffset = 0,
|
||||||
bottomOffset = 0,
|
bottomOffset = 0,
|
||||||
|
accessibilityLabel,
|
||||||
}: PropsWithChildren<DropdownButtonProps>) {
|
}: PropsWithChildren<DropdownButtonProps>) {
|
||||||
const ref1 = useRef<TouchableOpacity>(null)
|
const ref1 = useRef<TouchableOpacity>(null)
|
||||||
const ref2 = useRef<View>(null)
|
const ref2 = useRef<View>(null)
|
||||||
|
@ -128,8 +131,8 @@ export function DropdownButton({
|
||||||
hitSlop={HITSLOP}
|
hitSlop={HITSLOP}
|
||||||
ref={ref1}
|
ref={ref1}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={`Opens ${numItems} options`}
|
accessibilityLabel={accessibilityLabel || `Opens ${numItems} options`}
|
||||||
accessibilityHint={`Opens ${numItems} options`}>
|
accessibilityHint="">
|
||||||
{children}
|
{children}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
|
@ -246,7 +249,9 @@ export function PostDropdownBtn({
|
||||||
testID={testID}
|
testID={testID}
|
||||||
style={style}
|
style={style}
|
||||||
items={dropdownItems}
|
items={dropdownItems}
|
||||||
menuWidth={isWeb ? 220 : 200}>
|
menuWidth={isWeb ? 220 : 200}
|
||||||
|
accessibilityLabel="Additional post actions"
|
||||||
|
accessibilityHint="">
|
||||||
{children}
|
{children}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
)
|
)
|
||||||
|
@ -335,6 +340,7 @@ const DropdownItems = ({
|
||||||
key={index}
|
key={index}
|
||||||
style={[styles.menuItem]}
|
style={[styles.menuItem]}
|
||||||
onPress={() => onPressItem(index)}
|
onPress={() => onPressItem(index)}
|
||||||
|
accessibilityRole="button"
|
||||||
accessibilityLabel={item.label}
|
accessibilityLabel={item.label}
|
||||||
accessibilityHint={`Option ${index + 1} of ${numItems}`}>
|
accessibilityHint={`Option ${index + 1} of ${numItems}`}>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
|
|
|
@ -64,15 +64,14 @@ export function AutoSizedImage({
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
delayPressIn={DELAY_PRESS_IN}
|
||||||
style={[styles.container, style]}
|
style={[styles.container, style]}
|
||||||
accessible={true}
|
accessible={true}
|
||||||
accessibilityLabel="Share image"
|
accessibilityRole="button"
|
||||||
accessibilityHint="Opens ways of sharing image">
|
accessibilityLabel={alt || 'Image'}
|
||||||
|
accessibilityHint="Tap to view fully">
|
||||||
<Image
|
<Image
|
||||||
style={[styles.image, {aspectRatio}]}
|
style={[styles.image, {aspectRatio}]}
|
||||||
source={uri}
|
source={uri}
|
||||||
accessible={true} // Must set for `accessibilityLabel` to work
|
accessible={false} // Must set for `accessibilityLabel` to work
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
accessibilityLabel={alt}
|
|
||||||
accessibilityHint=""
|
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
onPressIn={onPressIn ? () => onPressIn(index) : undefined}
|
onPressIn={onPressIn ? () => onPressIn(index) : undefined}
|
||||||
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
|
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel="View image"
|
accessibilityLabel={image.alt || 'Image'}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
<Image
|
<Image
|
||||||
source={{uri: image.thumb}}
|
source={{uri: image.thumb}}
|
||||||
|
@ -47,7 +47,9 @@ export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{image.alt === '' ? null : (
|
{image.alt === '' ? null : (
|
||||||
<View style={styles.altContainer}>
|
<View style={styles.altContainer}>
|
||||||
<Text style={styles.alt}>ALT</Text>
|
<Text style={styles.alt} accessible={false}>
|
||||||
|
ALT
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -72,8 +72,7 @@ export function PostHider({
|
||||||
style={style}
|
style={style}
|
||||||
href={href}
|
href={href}
|
||||||
noFeedback
|
noFeedback
|
||||||
accessible={true}
|
accessible={false}
|
||||||
accessibilityRole="none"
|
|
||||||
{...props}>
|
{...props}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {Text} from '../text/Text'
|
||||||
import {PostDropdownBtn} from '../forms/DropdownButton'
|
import {PostDropdownBtn} from '../forms/DropdownButton'
|
||||||
import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
|
import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
|
import {pluralize} from 'lib/strings/helpers'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {RepostButton} from './RepostButton'
|
import {RepostButton} from './RepostButton'
|
||||||
|
@ -170,7 +171,9 @@ export function PostCtrls(opts: PostCtrlsOpts) {
|
||||||
hitSlop={HITSLOP}
|
hitSlop={HITSLOP}
|
||||||
onPress={opts.onPressReply}
|
onPress={opts.onPressReply}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel="Reply"
|
accessibilityLabel={`Reply (${opts.replyCount} ${
|
||||||
|
opts.replyCount === 1 ? 'reply' : 'replies'
|
||||||
|
})`}
|
||||||
accessibilityHint="reply composer">
|
accessibilityHint="reply composer">
|
||||||
<CommentBottomArrow
|
<CommentBottomArrow
|
||||||
style={[defaultCtrlColor, opts.big ? s.mt2 : styles.mt1]}
|
style={[defaultCtrlColor, opts.big ? s.mt2 : styles.mt1]}
|
||||||
|
@ -190,7 +193,9 @@ export function PostCtrls(opts: PostCtrlsOpts) {
|
||||||
hitSlop={HITSLOP}
|
hitSlop={HITSLOP}
|
||||||
onPress={onPressToggleLikeWrapper}
|
onPress={onPressToggleLikeWrapper}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={opts.isLiked ? 'Unlike' : 'Like'}
|
accessibilityLabel={`${opts.isLiked ? 'Unlike' : 'Like'} (${
|
||||||
|
opts.likeCount
|
||||||
|
} ${pluralize(opts.likeCount || 0, 'like')})`}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
{opts.isLiked ? (
|
{opts.isLiked ? (
|
||||||
<HeartIconSolid
|
<HeartIconSolid
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {RepostIcon} from 'lib/icons'
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
|
import {pluralize} from 'lib/strings/helpers'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
|
|
||||||
const HITSLOP = {top: 5, left: 5, bottom: 5, right: 5}
|
const HITSLOP = {top: 5, left: 5, bottom: 5, right: 5}
|
||||||
|
@ -49,7 +50,9 @@ export const RepostButton = ({
|
||||||
onPress={onPressToggleRepostWrapper}
|
onPress={onPressToggleRepostWrapper}
|
||||||
style={styles.control}
|
style={styles.control}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={isReposted ? 'Undo repost' : 'Repost'}
|
accessibilityLabel={`${
|
||||||
|
isReposted ? 'Undo repost' : 'Repost'
|
||||||
|
} (${repostCount} ${pluralize(repostCount || 0, 'repost')})`}
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
<RepostIcon
|
<RepostIcon
|
||||||
style={
|
style={
|
||||||
|
|
|
@ -129,7 +129,9 @@ export function PostEmbeds({
|
||||||
style={styles.singleImage}>
|
style={styles.singleImage}>
|
||||||
{alt === '' ? null : (
|
{alt === '' ? null : (
|
||||||
<View style={styles.altContainer}>
|
<View style={styles.altContainer}>
|
||||||
<Text style={styles.alt}>ALT</Text>
|
<Text style={styles.alt} accessible={false}>
|
||||||
|
ALT
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</AutoSizedImage>
|
</AutoSizedImage>
|
||||||
|
|
Loading…
Reference in New Issue