Skip to content

Commit bc55241

Browse files
authored
[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
1 parent 0163ba0 commit bc55241

File tree

19 files changed

+80
-148
lines changed

19 files changed

+80
-148
lines changed

src/view/com/composer/photos/Gallery.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ export const Gallery = observer(function ({gallery}: Props) {
8989
openAltTextModal(store, image)
9090
}}
9191
style={[styles.altTextControl, altTextControlStyle]}>
92-
<Text style={styles.altTextControlLabel}>ALT</Text>
92+
<Text style={styles.altTextControlLabel} accessible={false}>
93+
ALT
94+
</Text>
9395
{image.altText.length > 0 ? (
9496
<FontAwesomeIcon
9597
icon="check"

src/view/com/lightbox/ImageViewing/@types/index.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
*
77
*/
88

9-
import {ImageURISource, ImageRequireSource} from 'react-native'
10-
119
export type Dimensions = {
1210
width: number
1311
height: number
@@ -18,4 +16,4 @@ export type Position = {
1816
y: number
1917
}
2018

21-
export type ImageSource = ImageURISource | ImageRequireSource
19+
export type ImageSource = {uri: string; alt?: string}

src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ const ImageItem = ({
133133
source={imageSrc}
134134
style={imageStylesWithOpacity}
135135
onLoad={onLoaded}
136+
accessibilityLabel={imageSrc.alt}
137+
accessibilityHint=""
136138
/>
137139
{(!isLoaded || !imageDimensions) && <ImageLoading />}
138140
</ScrollView>

src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ const ImageItem = ({
128128
onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined}
129129
onLongPress={onLongPressHandler}
130130
delayLongPress={delayLongPress}
131-
accessibilityRole="image">
131+
accessibilityRole="image"
132+
accessibilityLabel={imageSrc.alt}
133+
accessibilityHint="">
132134
<Animated.Image
133135
source={imageSrc}
134136
style={imageStylesWithOpacity}

src/view/com/lightbox/Lightbox.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const Lightbox = observer(function Lightbox() {
109109
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
110110
return (
111111
<ImageView
112-
images={[{uri: opts.profileView.avatar}]}
112+
images={[{uri: opts.profileView.avatar || ''}]}
113113
imageIndex={0}
114114
visible
115115
onRequestClose={onClose}
@@ -120,7 +120,7 @@ export const Lightbox = observer(function Lightbox() {
120120
const opts = store.shell.activeLightbox as models.ImagesLightbox
121121
return (
122122
<ImageView
123-
images={opts.images.map(({uri}) => ({uri}))}
123+
images={opts.images.map(img => ({...img}))}
124124
imageIndex={opts.index}
125125
visible
126126
onRequestClose={onClose}

src/view/com/lightbox/Lightbox.web.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ function LightboxInner({
109109
accessibilityIgnoresInvertColors
110110
source={imgs[index]}
111111
style={styles.image}
112+
accessibilityLabel={imgs[index].alt}
113+
accessibilityHint=""
112114
/>
113115
{canGoLeft && (
114116
<TouchableOpacity

src/view/com/post-thread/PostThreadItem.tsx

+4-42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, {useCallback, useMemo} from 'react'
1+
import React, {useMemo} from 'react'
22
import {observer} from 'mobx-react-lite'
3-
import {AccessibilityActionEvent, Linking, StyleSheet, View} from 'react-native'
3+
import {Linking, StyleSheet, View} from 'react-native'
44
import Clipboard from '@react-native-clipboard/clipboard'
55
import {AtUri, AppBskyFeedDefs} from '@atproto/api'
66
import {
@@ -138,40 +138,6 @@ export const PostThreadItem = observer(function PostThreadItem({
138138
)
139139
}, [item, store])
140140

141-
const accessibilityActions = useMemo(
142-
() => [
143-
{
144-
name: 'reply',
145-
label: 'Reply',
146-
},
147-
{
148-
name: 'repost',
149-
label: item.post.viewer?.repost ? 'Undo repost' : 'Repost',
150-
},
151-
{name: 'like', label: item.post.viewer?.like ? 'Unlike' : 'Like'},
152-
],
153-
[item.post.viewer?.like, item.post.viewer?.repost],
154-
)
155-
156-
const onAccessibilityAction = useCallback(
157-
(event: AccessibilityActionEvent) => {
158-
switch (event.nativeEvent.actionName) {
159-
case 'like':
160-
onPressToggleLike()
161-
break
162-
case 'reply':
163-
onPressReply()
164-
break
165-
case 'repost':
166-
onPressToggleRepost()
167-
break
168-
default:
169-
break
170-
}
171-
},
172-
[onPressReply, onPressToggleLike, onPressToggleRepost],
173-
)
174-
175141
if (!record) {
176142
return <ErrorMessage message="Invalid or unsupported post record" />
177143
}
@@ -193,9 +159,7 @@ export const PostThreadItem = observer(function PostThreadItem({
193159
<PostHider
194160
testID={`postThreadItem-by-${item.post.author.handle}`}
195161
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
196-
moderation={item.moderation.thread}
197-
accessibilityActions={accessibilityActions}
198-
onAccessibilityAction={onAccessibilityAction}>
162+
moderation={item.moderation.thread}>
199163
<PostSandboxWarning />
200164
<View style={styles.layout}>
201165
<View style={styles.layoutAvi}>
@@ -369,9 +333,7 @@ export const PostThreadItem = observer(function PostThreadItem({
369333
pal.view,
370334
item._showParentReplyLine && styles.noTopBorder,
371335
]}
372-
moderation={item.moderation.thread}
373-
accessibilityActions={accessibilityActions}
374-
onAccessibilityAction={onAccessibilityAction}>
336+
moderation={item.moderation.thread}>
375337
{item._showParentReplyLine && (
376338
<View
377339
style={[

src/view/com/post/Post.tsx

+2-39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import React, {useCallback, useEffect, useMemo, useState} from 'react'
1+
import React, {useEffect, useState} from 'react'
22
import {
3-
AccessibilityActionEvent,
43
ActivityIndicator,
54
Linking,
65
StyleProp,
@@ -200,47 +199,11 @@ const PostLoaded = observer(
200199
)
201200
}, [item, setDeleted, store])
202201

203-
const accessibilityActions = useMemo(
204-
() => [
205-
{
206-
name: 'reply',
207-
label: 'Reply',
208-
},
209-
{
210-
name: 'repost',
211-
label: item.post.viewer?.repost ? 'Undo repost' : 'Repost',
212-
},
213-
{name: 'like', label: item.post.viewer?.like ? 'Unlike' : 'Like'},
214-
],
215-
[item.post.viewer?.like, item.post.viewer?.repost],
216-
)
217-
218-
const onAccessibilityAction = useCallback(
219-
(event: AccessibilityActionEvent) => {
220-
switch (event.nativeEvent.actionName) {
221-
case 'like':
222-
onPressToggleLike()
223-
break
224-
case 'reply':
225-
onPressReply()
226-
break
227-
case 'repost':
228-
onPressToggleRepost()
229-
break
230-
default:
231-
break
232-
}
233-
},
234-
[onPressReply, onPressToggleLike, onPressToggleRepost],
235-
)
236-
237202
return (
238203
<PostHider
239204
href={itemHref}
240205
style={[styles.outer, pal.view, pal.border, style]}
241-
moderation={item.moderation.list}
242-
accessibilityActions={accessibilityActions}
243-
onAccessibilityAction={onAccessibilityAction}>
206+
moderation={item.moderation.list}>
244207
{showReplyLine && <View style={styles.replyLine} />}
245208
<View style={styles.layout}>
246209
<View style={styles.layoutAvi}>

src/view/com/posts/FeedItem.tsx

+3-39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, {useCallback, useMemo, useState} from 'react'
1+
import React, {useMemo, useState} from 'react'
22
import {observer} from 'mobx-react-lite'
3-
import {AccessibilityActionEvent, Linking, StyleSheet, View} from 'react-native'
3+
import {Linking, StyleSheet, View} from 'react-native'
44
import Clipboard from '@react-native-clipboard/clipboard'
55
import {AtUri} from '@atproto/api'
66
import {
@@ -158,40 +158,6 @@ export const FeedItem = observer(function ({
158158
moderation = {behavior: ModerationBehaviorCode.Show}
159159
}
160160

161-
const accessibilityActions = useMemo(
162-
() => [
163-
{
164-
name: 'reply',
165-
label: 'Reply',
166-
},
167-
{
168-
name: 'repost',
169-
label: item.post.viewer?.repost ? 'Undo repost' : 'Repost',
170-
},
171-
{name: 'like', label: item.post.viewer?.like ? 'Unlike' : 'Like'},
172-
],
173-
[item.post.viewer?.like, item.post.viewer?.repost],
174-
)
175-
176-
const onAccessibilityAction = useCallback(
177-
(event: AccessibilityActionEvent) => {
178-
switch (event.nativeEvent.actionName) {
179-
case 'like':
180-
onPressToggleLike()
181-
break
182-
case 'reply':
183-
onPressReply()
184-
break
185-
case 'repost':
186-
onPressToggleRepost()
187-
break
188-
default:
189-
break
190-
}
191-
},
192-
[onPressReply, onPressToggleLike, onPressToggleRepost],
193-
)
194-
195161
if (!record || deleted) {
196162
return <View />
197163
}
@@ -201,9 +167,7 @@ export const FeedItem = observer(function ({
201167
testID={`feedItem-by-${item.post.author.handle}`}
202168
style={outerStyles}
203169
href={itemHref}
204-
moderation={moderation}
205-
accessibilityActions={accessibilityActions}
206-
onAccessibilityAction={onAccessibilityAction}>
170+
moderation={moderation}>
207171
{isThreadChild && (
208172
<View
209173
style={[styles.topReplyLine, {borderColor: pal.colors.replyLine}]}

src/view/com/util/Link.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export const Link = observer(function Link({
8888
props.dataSet.noUnderline = 1
8989
}
9090

91+
if (title && !props.accessibilityLabel) {
92+
props.accessibilityLabel = title
93+
}
94+
9195
return (
9296
<TouchableOpacity
9397
testID={testID}
@@ -171,6 +175,7 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
171175
text,
172176
numberOfLines,
173177
lineHeight,
178+
...props
174179
}: {
175180
testID?: string
176181
type?: TypographyVariant
@@ -179,6 +184,9 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
179184
text: string | JSX.Element
180185
numberOfLines?: number
181186
lineHeight?: number
187+
accessible?: boolean
188+
accessibilityLabel?: string
189+
accessibilityHint?: string
182190
}) {
183191
if (isDesktopWeb) {
184192
return (
@@ -190,6 +198,7 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
190198
text={text}
191199
numberOfLines={numberOfLines}
192200
lineHeight={lineHeight}
201+
{...props}
193202
/>
194203
)
195204
}
@@ -199,7 +208,8 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
199208
type={type}
200209
style={style}
201210
numberOfLines={numberOfLines}
202-
lineHeight={lineHeight}>
211+
lineHeight={lineHeight}
212+
{...props}>
203213
{text}
204214
</Text>
205215
)

src/view/com/util/PostMeta.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react'
22
import {StyleSheet, View} from 'react-native'
33
import {Text} from './text/Text'
44
import {DesktopWebTextLink} from './Link'
5-
import {ago} from 'lib/strings/time'
5+
import {ago, niceDate} from 'lib/strings/time'
66
import {usePalette} from 'lib/hooks/usePalette'
77
import {useStores} from 'state/index'
88
import {UserAvatar} from './UserAvatar'
@@ -57,14 +57,20 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
5757
text={sanitizeDisplayName(displayName)}
5858
href={`/profile/${opts.authorHandle}`}
5959
/>
60-
<Text type="md" style={pal.textLight} lineHeight={1.2}>
60+
<Text
61+
type="md"
62+
style={pal.textLight}
63+
lineHeight={1.2}
64+
accessible={false}>
6165
&nbsp;&middot;&nbsp;
6266
</Text>
6367
<DesktopWebTextLink
6468
type="md"
6569
style={[styles.metaItem, pal.textLight]}
6670
lineHeight={1.2}
6771
text={ago(opts.timestamp)}
72+
accessibilityLabel={niceDate(opts.timestamp)}
73+
accessibilityHint=""
6874
href={opts.postHref}
6975
/>
7076
</View>
@@ -122,14 +128,16 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
122128
href={`/profile/${opts.authorHandle}`}
123129
/>
124130
</View>
125-
<Text type="md" style={pal.textLight} lineHeight={1.2}>
131+
<Text type="md" style={pal.textLight} lineHeight={1.2} accessible={false}>
126132
&middot;&nbsp;
127133
</Text>
128134
<DesktopWebTextLink
129135
type="md"
130136
style={[styles.metaItem, pal.textLight]}
131137
lineHeight={1.2}
132138
text={ago(opts.timestamp)}
139+
accessibilityLabel={niceDate(opts.timestamp)}
140+
accessibilityHint=""
133141
href={opts.postHref}
134142
/>
135143
</View>

src/view/com/util/PostSandboxWarning.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export function PostSandboxWarning() {
1010
if (store.session.isSandbox) {
1111
return (
1212
<View style={styles.container}>
13-
<Text type="title-2xl" style={[pal.text, styles.text]}>
13+
<Text
14+
type="title-2xl"
15+
style={[pal.text, styles.text]}
16+
accessible={false}>
1417
SANDBOX
1518
</Text>
1619
</View>

0 commit comments

Comments
 (0)