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 elsewherezio/stable
parent
b51640fbc0
commit
708a80e7a7
|
@ -194,10 +194,36 @@ let FeedItem = ({
|
||||||
]}
|
]}
|
||||||
href={itemHref}
|
href={itemHref}
|
||||||
noFeedback
|
noFeedback
|
||||||
accessible={
|
accessible={!isAuthorsExpanded}
|
||||||
(item.type === 'post-like' && authors.length === 1) ||
|
accessibilityActions={
|
||||||
item.type === 'repost'
|
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}>
|
onBeforePress={onBeforePress}>
|
||||||
<View style={[styles.layoutIcon, a.pr_sm]}>
|
<View style={[styles.layoutIcon, a.pr_sm]}>
|
||||||
{/* TODO: Prevent conditional rendering and move toward composable
|
{/* TODO: Prevent conditional rendering and move toward composable
|
||||||
|
@ -332,16 +358,14 @@ function CondensedAuthorsList({
|
||||||
profile={authors[0].profile}
|
profile={authors[0].profile}
|
||||||
moderation={authors[0].moderation.ui('avatar')}
|
moderation={authors[0].moderation.ui('avatar')}
|
||||||
type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
|
type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
|
||||||
|
accessible={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
accessibilityLabel={_(msg`Show users`)}
|
accessibilityRole="none"
|
||||||
accessibilityHint={_(
|
|
||||||
msg`Opens an expanded list of users in this notification`,
|
|
||||||
)}
|
|
||||||
onPress={onToggleAuthorsExpanded}>
|
onPress={onToggleAuthorsExpanded}>
|
||||||
<View style={styles.avis}>
|
<View style={styles.avis}>
|
||||||
{authors.slice(0, MAX_AUTHORS).map(author => (
|
{authors.slice(0, MAX_AUTHORS).map(author => (
|
||||||
|
@ -351,6 +375,7 @@ function CondensedAuthorsList({
|
||||||
profile={author.profile}
|
profile={author.profile}
|
||||||
moderation={author.moderation.ui('avatar')}
|
moderation={author.moderation.ui('avatar')}
|
||||||
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
||||||
|
accessible={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
@ -392,48 +417,45 @@ function ExpandedAuthorsList({
|
||||||
}, [heightInterp, visible])
|
}, [heightInterp, visible])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View style={[heightStyle, styles.overflowHidden]}>
|
||||||
style={[
|
{visible &&
|
||||||
heightStyle,
|
authors.map(author => (
|
||||||
styles.overflowHidden,
|
<NewLink
|
||||||
visible ? s.mb10 : undefined,
|
key={author.profile.did}
|
||||||
]}>
|
label={author.profile.displayName || author.profile.handle}
|
||||||
{authors.map(author => (
|
accessibilityHint={_(msg`Opens this profile`)}
|
||||||
<NewLink
|
to={makeProfileLink({
|
||||||
key={author.profile.did}
|
did: author.profile.did,
|
||||||
label={_(msg`See profile`)}
|
handle: author.profile.handle,
|
||||||
to={makeProfileLink({
|
})}
|
||||||
did: author.profile.did,
|
style={styles.expandedAuthor}>
|
||||||
handle: author.profile.handle,
|
<View style={styles.expandedAuthorAvi}>
|
||||||
})}
|
<ProfileHoverCard did={author.profile.did}>
|
||||||
style={styles.expandedAuthor}>
|
<UserAvatar
|
||||||
<View style={styles.expandedAuthorAvi}>
|
size={35}
|
||||||
<ProfileHoverCard did={author.profile.did}>
|
avatar={author.profile.avatar}
|
||||||
<UserAvatar
|
moderation={author.moderation.ui('avatar')}
|
||||||
size={35}
|
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
||||||
avatar={author.profile.avatar}
|
/>
|
||||||
moderation={author.moderation.ui('avatar')}
|
</ProfileHoverCard>
|
||||||
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
</View>
|
||||||
/>
|
<View style={s.flex1}>
|
||||||
</ProfileHoverCard>
|
<Text
|
||||||
</View>
|
type="lg-bold"
|
||||||
<View style={s.flex1}>
|
numberOfLines={1}
|
||||||
<Text
|
style={pal.text}
|
||||||
type="lg-bold"
|
lineHeight={1.2}>
|
||||||
numberOfLines={1}
|
{sanitizeDisplayName(
|
||||||
style={pal.text}
|
author.profile.displayName || author.profile.handle,
|
||||||
lineHeight={1.2}>
|
)}
|
||||||
{sanitizeDisplayName(
|
|
||||||
author.profile.displayName || author.profile.handle,
|
<Text style={[pal.textLight]} lineHeight={1.2}>
|
||||||
)}
|
{sanitizeHandle(author.profile.handle)}
|
||||||
|
</Text>
|
||||||
<Text style={[pal.textLight]} lineHeight={1.2}>
|
|
||||||
{sanitizeHandle(author.profile.handle)}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</View>
|
||||||
</View>
|
</NewLink>
|
||||||
</NewLink>
|
))}
|
||||||
))}
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,8 @@ export const Link = memo(function Link({
|
||||||
anchorNoUnderline,
|
anchorNoUnderline,
|
||||||
navigationAction,
|
navigationAction,
|
||||||
onBeforePress,
|
onBeforePress,
|
||||||
|
accessibilityActions,
|
||||||
|
onAccessibilityAction,
|
||||||
...props
|
...props
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
@ -89,6 +91,11 @@ export const Link = memo(function Link({
|
||||||
[closeModal, navigation, navigationAction, href, openLink, onBeforePress],
|
[closeModal, navigation, navigationAction, href, openLink, onBeforePress],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const accessibilityActionsWithActivate = [
|
||||||
|
...(accessibilityActions || []),
|
||||||
|
{name: 'activate', label: title},
|
||||||
|
]
|
||||||
|
|
||||||
if (noFeedback) {
|
if (noFeedback) {
|
||||||
return (
|
return (
|
||||||
<WebAuxClickWrapper>
|
<WebAuxClickWrapper>
|
||||||
|
@ -97,6 +104,14 @@ export const Link = memo(function Link({
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessible={accessible}
|
accessible={accessible}
|
||||||
accessibilityRole="link"
|
accessibilityRole="link"
|
||||||
|
accessibilityActions={accessibilityActionsWithActivate}
|
||||||
|
onAccessibilityAction={e => {
|
||||||
|
if (e.nativeEvent.actionName === 'activate') {
|
||||||
|
onPress()
|
||||||
|
} else {
|
||||||
|
onAccessibilityAction?.(e)
|
||||||
|
}
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
android_ripple={{
|
android_ripple={{
|
||||||
color: t.atoms.bg_contrast_25.backgroundColor,
|
color: t.atoms.bg_contrast_25.backgroundColor,
|
||||||
|
|
|
@ -53,6 +53,7 @@ interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
|
||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
profile: AppBskyActorDefs.ProfileViewBasic
|
||||||
disableHoverCard?: boolean
|
disableHoverCard?: boolean
|
||||||
onBeforePress?: () => void
|
onBeforePress?: () => void
|
||||||
|
accessible?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLUR_AMOUNT = isWeb ? 5 : 100
|
const BLUR_AMOUNT = isWeb ? 5 : 100
|
||||||
|
@ -386,6 +387,7 @@ let PreviewableUserAvatar = ({
|
||||||
profile,
|
profile,
|
||||||
disableHoverCard,
|
disableHoverCard,
|
||||||
onBeforePress,
|
onBeforePress,
|
||||||
|
accessible = true,
|
||||||
...rest
|
...rest
|
||||||
}: PreviewableUserAvatarProps): React.ReactNode => {
|
}: PreviewableUserAvatarProps): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
@ -399,7 +401,12 @@ let PreviewableUserAvatar = ({
|
||||||
return (
|
return (
|
||||||
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
|
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
|
||||||
<Link
|
<Link
|
||||||
label={_(msg`See profile`)}
|
label={
|
||||||
|
accessible
|
||||||
|
? _(msg`${profile.displayName || profile.handle}'s avatar`)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
accessibilityHint={accessible ? _(msg`Opens this profile`) : undefined}
|
||||||
to={makeProfileLink({
|
to={makeProfileLink({
|
||||||
did: profile.did,
|
did: profile.did,
|
||||||
handle: profile.handle,
|
handle: profile.handle,
|
||||||
|
|
Loading…
Reference in New Issue