Skip to content

Commit

Permalink
[APP-724] Collection of accessibility fixes (#949)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pfrazee authored Jul 3, 2023
1 parent 0163ba0 commit bc55241
Show file tree
Hide file tree
Showing 19 changed files with 80 additions and 148 deletions.
4 changes: 3 additions & 1 deletion src/view/com/composer/photos/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ export const Gallery = observer(function ({gallery}: Props) {
openAltTextModal(store, image)
}}
style={[styles.altTextControl, altTextControlStyle]}>
<Text style={styles.altTextControlLabel}>ALT</Text>
<Text style={styles.altTextControlLabel} accessible={false}>
ALT
</Text>
{image.altText.length > 0 ? (
<FontAwesomeIcon
icon="check"
Expand Down
4 changes: 1 addition & 3 deletions src/view/com/lightbox/ImageViewing/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
*
*/

import {ImageURISource, ImageRequireSource} from 'react-native'

export type Dimensions = {
width: number
height: number
Expand All @@ -18,4 +16,4 @@ export type Position = {
y: number
}

export type ImageSource = ImageURISource | ImageRequireSource
export type ImageSource = {uri: string; alt?: string}
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ const ImageItem = ({
source={imageSrc}
style={imageStylesWithOpacity}
onLoad={onLoaded}
accessibilityLabel={imageSrc.alt}
accessibilityHint=""
/>
{(!isLoaded || !imageDimensions) && <ImageLoading />}
</ScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ const ImageItem = ({
onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined}
onLongPress={onLongPressHandler}
delayLongPress={delayLongPress}
accessibilityRole="image">
accessibilityRole="image"
accessibilityLabel={imageSrc.alt}
accessibilityHint="">
<Animated.Image
source={imageSrc}
style={imageStylesWithOpacity}
Expand Down
4 changes: 2 additions & 2 deletions src/view/com/lightbox/Lightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const Lightbox = observer(function Lightbox() {
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
return (
<ImageView
images={[{uri: opts.profileView.avatar}]}
images={[{uri: opts.profileView.avatar || ''}]}
imageIndex={0}
visible
onRequestClose={onClose}
Expand All @@ -120,7 +120,7 @@ export const Lightbox = observer(function Lightbox() {
const opts = store.shell.activeLightbox as models.ImagesLightbox
return (
<ImageView
images={opts.images.map(({uri}) => ({uri}))}
images={opts.images.map(img => ({...img}))}
imageIndex={opts.index}
visible
onRequestClose={onClose}
Expand Down
2 changes: 2 additions & 0 deletions src/view/com/lightbox/Lightbox.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ function LightboxInner({
accessibilityIgnoresInvertColors
source={imgs[index]}
style={styles.image}
accessibilityLabel={imgs[index].alt}
accessibilityHint=""
/>
{canGoLeft && (
<TouchableOpacity
Expand Down
46 changes: 4 additions & 42 deletions src/view/com/post-thread/PostThreadItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useCallback, useMemo} from 'react'
import React, {useMemo} 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, AppBskyFeedDefs} from '@atproto/api'
import {
Expand Down Expand Up @@ -138,40 +138,6 @@ export const PostThreadItem = observer(function PostThreadItem({
)
}, [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) {
return <ErrorMessage message="Invalid or unsupported post record" />
}
Expand All @@ -193,9 +159,7 @@ export const PostThreadItem = observer(function PostThreadItem({
<PostHider
testID={`postThreadItem-by-${item.post.author.handle}`}
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
moderation={item.moderation.thread}
accessibilityActions={accessibilityActions}
onAccessibilityAction={onAccessibilityAction}>
moderation={item.moderation.thread}>
<PostSandboxWarning />
<View style={styles.layout}>
<View style={styles.layoutAvi}>
Expand Down Expand Up @@ -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 && (
<View
style={[
Expand Down
41 changes: 2 additions & 39 deletions src/view/com/post/Post.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {useCallback, useEffect, useMemo, useState} from 'react'
import React, {useEffect, useState} from 'react'
import {
AccessibilityActionEvent,
ActivityIndicator,
Linking,
StyleProp,
Expand Down Expand Up @@ -200,47 +199,11 @@ const PostLoaded = observer(
)
}, [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 (
<PostHider
href={itemHref}
style={[styles.outer, pal.view, pal.border, style]}
moderation={item.moderation.list}
accessibilityActions={accessibilityActions}
onAccessibilityAction={onAccessibilityAction}>
moderation={item.moderation.list}>
{showReplyLine && <View style={styles.replyLine} />}
<View style={styles.layout}>
<View style={styles.layoutAvi}>
Expand Down
42 changes: 3 additions & 39 deletions src/view/com/posts/FeedItem.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 <View />
}
Expand All @@ -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 && (
<View
style={[styles.topReplyLine, {borderColor: pal.colors.replyLine}]}
Expand Down
12 changes: 11 additions & 1 deletion src/view/com/util/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export const Link = observer(function Link({
props.dataSet.noUnderline = 1
}

if (title && !props.accessibilityLabel) {
props.accessibilityLabel = title
}

return (
<TouchableOpacity
testID={testID}
Expand Down Expand Up @@ -171,6 +175,7 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
text,
numberOfLines,
lineHeight,
...props
}: {
testID?: string
type?: TypographyVariant
Expand All @@ -179,6 +184,9 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
text: string | JSX.Element
numberOfLines?: number
lineHeight?: number
accessible?: boolean
accessibilityLabel?: string
accessibilityHint?: string
}) {
if (isDesktopWeb) {
return (
Expand All @@ -190,6 +198,7 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
text={text}
numberOfLines={numberOfLines}
lineHeight={lineHeight}
{...props}
/>
)
}
Expand All @@ -199,7 +208,8 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
type={type}
style={style}
numberOfLines={numberOfLines}
lineHeight={lineHeight}>
lineHeight={lineHeight}
{...props}>
{text}
</Text>
)
Expand Down
14 changes: 11 additions & 3 deletions src/view/com/util/PostMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -57,14 +57,20 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
text={sanitizeDisplayName(displayName)}
href={`/profile/${opts.authorHandle}`}
/>
<Text type="md" style={pal.textLight} lineHeight={1.2}>
<Text
type="md"
style={pal.textLight}
lineHeight={1.2}
accessible={false}>
&nbsp;&middot;&nbsp;
</Text>
<DesktopWebTextLink
type="md"
style={[styles.metaItem, pal.textLight]}
lineHeight={1.2}
text={ago(opts.timestamp)}
accessibilityLabel={niceDate(opts.timestamp)}
accessibilityHint=""
href={opts.postHref}
/>
</View>
Expand Down Expand Up @@ -122,14 +128,16 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
href={`/profile/${opts.authorHandle}`}
/>
</View>
<Text type="md" style={pal.textLight} lineHeight={1.2}>
<Text type="md" style={pal.textLight} lineHeight={1.2} accessible={false}>
&middot;&nbsp;
</Text>
<DesktopWebTextLink
type="md"
style={[styles.metaItem, pal.textLight]}
lineHeight={1.2}
text={ago(opts.timestamp)}
accessibilityLabel={niceDate(opts.timestamp)}
accessibilityHint=""
href={opts.postHref}
/>
</View>
Expand Down
5 changes: 4 additions & 1 deletion src/view/com/util/PostSandboxWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export function PostSandboxWarning() {
if (store.session.isSandbox) {
return (
<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
</Text>
</View>
Expand Down
Loading

0 comments on commit bc55241

Please sign in to comment.