From bc55241c9ae731f633f8b93a9b7eac7635070148 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 3 Jul 2023 15:57:53 -0500 Subject: [PATCH] [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 types --- src/view/com/composer/photos/Gallery.tsx | 4 +- .../com/lightbox/ImageViewing/@types/index.ts | 4 +- .../ImageItem/ImageItem.android.tsx | 2 + .../components/ImageItem/ImageItem.ios.tsx | 4 +- src/view/com/lightbox/Lightbox.tsx | 4 +- src/view/com/lightbox/Lightbox.web.tsx | 2 + src/view/com/post-thread/PostThreadItem.tsx | 46 ++----------------- src/view/com/post/Post.tsx | 41 +---------------- src/view/com/posts/FeedItem.tsx | 42 ++--------------- src/view/com/util/Link.tsx | 12 ++++- src/view/com/util/PostMeta.tsx | 14 ++++-- src/view/com/util/PostSandboxWarning.tsx | 5 +- src/view/com/util/forms/DropdownButton.tsx | 12 +++-- src/view/com/util/images/AutoSizedImage.tsx | 9 ++-- src/view/com/util/images/Gallery.tsx | 6 ++- src/view/com/util/moderation/PostHider.tsx | 3 +- src/view/com/util/post-ctrls/PostCtrls.tsx | 9 +++- src/view/com/util/post-ctrls/RepostButton.tsx | 5 +- src/view/com/util/post-embeds/index.tsx | 4 +- 19 files changed, 80 insertions(+), 148 deletions(-) diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index c226d25c..6dba2f01 100644 --- a/src/view/com/composer/photos/Gallery.tsx +++ b/src/view/com/composer/photos/Gallery.tsx @@ -89,7 +89,9 @@ export const Gallery = observer(function ({gallery}: Props) { openAltTextModal(store, image) }} style={[styles.altTextControl, altTextControlStyle]}> - ALT + + ALT + {image.altText.length > 0 ? ( {(!isLoaded || !imageDimensions) && } diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx index 65873572..ebf0b1d2 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -128,7 +128,9 @@ const ImageItem = ({ onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined} onLongPress={onLongPressHandler} delayLongPress={delayLongPress} - accessibilityRole="image"> + accessibilityRole="image" + accessibilityLabel={imageSrc.alt} + accessibilityHint=""> ({uri}))} + images={opts.images.map(img => ({...img}))} imageIndex={opts.index} visible onRequestClose={onClose} diff --git a/src/view/com/lightbox/Lightbox.web.tsx b/src/view/com/lightbox/Lightbox.web.tsx index f6aa26a3..6d79dad3 100644 --- a/src/view/com/lightbox/Lightbox.web.tsx +++ b/src/view/com/lightbox/Lightbox.web.tsx @@ -109,6 +109,8 @@ function LightboxInner({ accessibilityIgnoresInvertColors source={imgs[index]} style={styles.image} + accessibilityLabel={imgs[index].alt} + accessibilityHint="" /> {canGoLeft && ( [ - { - 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) { return } @@ -193,9 +159,7 @@ export const PostThreadItem = observer(function PostThreadItem({ + moderation={item.moderation.thread}> @@ -369,9 +333,7 @@ export const PostThreadItem = observer(function PostThreadItem({ pal.view, item._showParentReplyLine && styles.noTopBorder, ]} - moderation={item.moderation.thread} - accessibilityActions={accessibilityActions} - onAccessibilityAction={onAccessibilityAction}> + moderation={item.moderation.thread}> {item._showParentReplyLine && ( [ - { - 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 ( + moderation={item.moderation.list}> {showReplyLine && } diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 7a292378..6ec2c80f 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -1,6 +1,6 @@ -import React, {useCallback, useMemo, useState} from 'react' +import React, {useMemo, useState} from 'react' 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 {AtUri} from '@atproto/api' import { @@ -158,40 +158,6 @@ export const FeedItem = observer(function ({ 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) { return } @@ -201,9 +167,7 @@ export const FeedItem = observer(function ({ testID={`feedItem-by-${item.post.author.handle}`} style={outerStyles} href={itemHref} - moderation={moderation} - accessibilityActions={accessibilityActions} - onAccessibilityAction={onAccessibilityAction}> + moderation={moderation}> {isThreadChild && ( ) } @@ -199,7 +208,8 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({ type={type} style={style} numberOfLines={numberOfLines} - lineHeight={lineHeight}> + lineHeight={lineHeight} + {...props}> {text} ) diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 45651e4e..628c8872 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -2,7 +2,7 @@ import React from 'react' import {StyleSheet, View} from 'react-native' import {Text} from './text/Text' import {DesktopWebTextLink} from './Link' -import {ago} from 'lib/strings/time' +import {ago, niceDate} from 'lib/strings/time' import {usePalette} from 'lib/hooks/usePalette' import {useStores} from 'state/index' import {UserAvatar} from './UserAvatar' @@ -57,7 +57,11 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { text={sanitizeDisplayName(displayName)} href={`/profile/${opts.authorHandle}`} /> - +  ·  @@ -122,7 +128,7 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { href={`/profile/${opts.authorHandle}`} /> - + ·  diff --git a/src/view/com/util/PostSandboxWarning.tsx b/src/view/com/util/PostSandboxWarning.tsx index 54495aa9..21f5f7b9 100644 --- a/src/view/com/util/PostSandboxWarning.tsx +++ b/src/view/com/util/PostSandboxWarning.tsx @@ -10,7 +10,10 @@ export function PostSandboxWarning() { if (store.session.isSandbox) { return ( - + SANDBOX diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index c6e65007..ad216d97 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -50,6 +50,8 @@ interface DropdownButtonProps { openToRight?: boolean rightOffset?: number bottomOffset?: number + accessibilityLabel?: string + accessibilityHint?: string } export function DropdownButton({ @@ -63,6 +65,7 @@ export function DropdownButton({ openToRight = false, rightOffset = 0, bottomOffset = 0, + accessibilityLabel, }: PropsWithChildren) { const ref1 = useRef(null) const ref2 = useRef(null) @@ -128,8 +131,8 @@ export function DropdownButton({ hitSlop={HITSLOP} ref={ref1} accessibilityRole="button" - accessibilityLabel={`Opens ${numItems} options`} - accessibilityHint={`Opens ${numItems} options`}> + accessibilityLabel={accessibilityLabel || `Opens ${numItems} options`} + accessibilityHint=""> {children} ) @@ -246,7 +249,9 @@ export function PostDropdownBtn({ testID={testID} style={style} items={dropdownItems} - menuWidth={isWeb ? 220 : 200}> + menuWidth={isWeb ? 220 : 200} + accessibilityLabel="Additional post actions" + accessibilityHint=""> {children} ) @@ -335,6 +340,7 @@ const DropdownItems = ({ key={index} style={[styles.menuItem]} onPress={() => onPressItem(index)} + accessibilityRole="button" accessibilityLabel={item.label} accessibilityHint={`Option ${index + 1} of ${numItems}`}> {item.icon && ( diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index e6aba46f..9c6f25ca 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -64,15 +64,14 @@ export function AutoSizedImage({ delayPressIn={DELAY_PRESS_IN} style={[styles.container, style]} accessible={true} - accessibilityLabel="Share image" - accessibilityHint="Opens ways of sharing image"> + accessibilityRole="button" + accessibilityLabel={alt || 'Image'} + accessibilityHint="Tap to view fully"> {children} diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx index a7a64b17..01a7d574 100644 --- a/src/view/com/util/images/Gallery.tsx +++ b/src/view/com/util/images/Gallery.tsx @@ -34,7 +34,7 @@ export const GalleryItem: FC = ({ onPressIn={onPressIn ? () => onPressIn(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined} accessibilityRole="button" - accessibilityLabel="View image" + accessibilityLabel={image.alt || 'Image'} accessibilityHint=""> = ({ {image.alt === '' ? null : ( - ALT + + ALT + )} diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/view/com/util/moderation/PostHider.tsx index 50ccf595..f2b6dbdd 100644 --- a/src/view/com/util/moderation/PostHider.tsx +++ b/src/view/com/util/moderation/PostHider.tsx @@ -72,8 +72,7 @@ export function PostHider({ style={style} href={href} noFeedback - accessible={true} - accessibilityRole="none" + accessible={false} {...props}> {children} diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 12d4c48c..cd6db408 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -19,6 +19,7 @@ import {Text} from '../text/Text' import {PostDropdownBtn} from '../forms/DropdownButton' import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons' import {s, colors} from 'lib/styles' +import {pluralize} from 'lib/strings/helpers' import {useTheme} from 'lib/ThemeContext' import {useStores} from 'state/index' import {RepostButton} from './RepostButton' @@ -170,7 +171,9 @@ export function PostCtrls(opts: PostCtrlsOpts) { hitSlop={HITSLOP} onPress={opts.onPressReply} accessibilityRole="button" - accessibilityLabel="Reply" + accessibilityLabel={`Reply (${opts.replyCount} ${ + opts.replyCount === 1 ? 'reply' : 'replies' + })`} accessibilityHint="reply composer"> {opts.isLiked ? ( {alt === '' ? null : ( - ALT + + ALT + )}