fix accessibility label in notifications (#4305)

* fix accessibility label in notifications

* add accessibility options to expand post

* inherit from outside, but always include `activate`

* include option to disable label/hint on previewable avatar

* fix hidden elements still being read on voiceover

* make it work for followers too

* extract variable

* fix hint

* update wording elsewhere
zio/stable
Hailey 2024-05-31 13:02:18 -07:00 committed by GitHub
parent b51640fbc0
commit 708a80e7a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 93 additions and 49 deletions

View File

@ -194,10 +194,36 @@ let FeedItem = ({
]}
href={itemHref}
noFeedback
accessible={
(item.type === 'post-like' && authors.length === 1) ||
item.type === 'repost'
accessible={!isAuthorsExpanded}
accessibilityActions={
authors.length > 1
? [
{
name: 'toggleAuthorsExpanded',
label: isAuthorsExpanded
? _(msg`Collapse list of users`)
: _(msg`Expand list of users`),
},
]
: [
{
name: 'viewProfile',
label: _(
msg`View ${
authors[0].profile.displayName || authors[0].profile.handle
}'s profile`,
),
},
]
}
onAccessibilityAction={e => {
if (e.nativeEvent.actionName === 'activate') {
onBeforePress()
}
if (e.nativeEvent.actionName === 'toggleAuthorsExpanded') {
onToggleAuthorsExpanded()
}
}}
onBeforePress={onBeforePress}>
<View style={[styles.layoutIcon, a.pr_sm]}>
{/* TODO: Prevent conditional rendering and move toward composable
@ -332,16 +358,14 @@ function CondensedAuthorsList({
profile={authors[0].profile}
moderation={authors[0].moderation.ui('avatar')}
type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
accessible={false}
/>
</View>
)
}
return (
<TouchableOpacity
accessibilityLabel={_(msg`Show users`)}
accessibilityHint={_(
msg`Opens an expanded list of users in this notification`,
)}
accessibilityRole="none"
onPress={onToggleAuthorsExpanded}>
<View style={styles.avis}>
{authors.slice(0, MAX_AUTHORS).map(author => (
@ -351,6 +375,7 @@ function CondensedAuthorsList({
profile={author.profile}
moderation={author.moderation.ui('avatar')}
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
accessible={false}
/>
</View>
))}
@ -392,48 +417,45 @@ function ExpandedAuthorsList({
}, [heightInterp, visible])
return (
<Animated.View
style={[
heightStyle,
styles.overflowHidden,
visible ? s.mb10 : undefined,
]}>
{authors.map(author => (
<NewLink
key={author.profile.did}
label={_(msg`See profile`)}
to={makeProfileLink({
did: author.profile.did,
handle: author.profile.handle,
})}
style={styles.expandedAuthor}>
<View style={styles.expandedAuthorAvi}>
<ProfileHoverCard did={author.profile.did}>
<UserAvatar
size={35}
avatar={author.profile.avatar}
moderation={author.moderation.ui('avatar')}
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
/>
</ProfileHoverCard>
</View>
<View style={s.flex1}>
<Text
type="lg-bold"
numberOfLines={1}
style={pal.text}
lineHeight={1.2}>
{sanitizeDisplayName(
author.profile.displayName || author.profile.handle,
)}
&nbsp;
<Text style={[pal.textLight]} lineHeight={1.2}>
{sanitizeHandle(author.profile.handle)}
<Animated.View style={[heightStyle, styles.overflowHidden]}>
{visible &&
authors.map(author => (
<NewLink
key={author.profile.did}
label={author.profile.displayName || author.profile.handle}
accessibilityHint={_(msg`Opens this profile`)}
to={makeProfileLink({
did: author.profile.did,
handle: author.profile.handle,
})}
style={styles.expandedAuthor}>
<View style={styles.expandedAuthorAvi}>
<ProfileHoverCard did={author.profile.did}>
<UserAvatar
size={35}
avatar={author.profile.avatar}
moderation={author.moderation.ui('avatar')}
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
/>
</ProfileHoverCard>
</View>
<View style={s.flex1}>
<Text
type="lg-bold"
numberOfLines={1}
style={pal.text}
lineHeight={1.2}>
{sanitizeDisplayName(
author.profile.displayName || author.profile.handle,
)}
&nbsp;
<Text style={[pal.textLight]} lineHeight={1.2}>
{sanitizeHandle(author.profile.handle)}
</Text>
</Text>
</Text>
</View>
</NewLink>
))}
</View>
</NewLink>
))}
</Animated.View>
)
}

View File

@ -64,6 +64,8 @@ export const Link = memo(function Link({
anchorNoUnderline,
navigationAction,
onBeforePress,
accessibilityActions,
onAccessibilityAction,
...props
}: Props) {
const t = useTheme()
@ -89,6 +91,11 @@ export const Link = memo(function Link({
[closeModal, navigation, navigationAction, href, openLink, onBeforePress],
)
const accessibilityActionsWithActivate = [
...(accessibilityActions || []),
{name: 'activate', label: title},
]
if (noFeedback) {
return (
<WebAuxClickWrapper>
@ -97,6 +104,14 @@ export const Link = memo(function Link({
onPress={onPress}
accessible={accessible}
accessibilityRole="link"
accessibilityActions={accessibilityActionsWithActivate}
onAccessibilityAction={e => {
if (e.nativeEvent.actionName === 'activate') {
onPress()
} else {
onAccessibilityAction?.(e)
}
}}
{...props}
android_ripple={{
color: t.atoms.bg_contrast_25.backgroundColor,

View File

@ -53,6 +53,7 @@ interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
profile: AppBskyActorDefs.ProfileViewBasic
disableHoverCard?: boolean
onBeforePress?: () => void
accessible?: boolean
}
const BLUR_AMOUNT = isWeb ? 5 : 100
@ -386,6 +387,7 @@ let PreviewableUserAvatar = ({
profile,
disableHoverCard,
onBeforePress,
accessible = true,
...rest
}: PreviewableUserAvatarProps): React.ReactNode => {
const {_} = useLingui()
@ -399,7 +401,12 @@ let PreviewableUserAvatar = ({
return (
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
<Link
label={_(msg`See profile`)}
label={
accessible
? _(msg`${profile.displayName || profile.handle}'s avatar`)
: undefined
}
accessibilityHint={accessible ? _(msg`Opens this profile`) : undefined}
to={makeProfileLink({
did: profile.did,
handle: profile.handle,