-
Notifications
You must be signed in to change notification settings - Fork 308
feat: add accessibility string for profile icon [WPB-21213] #21831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import {CSSObject} from '@emotion/react'; | |
| import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; | ||
| import {stringifyQualifiedId} from '@wireapp/core/lib/util/qualifiedIdUtil'; | ||
| import {container} from 'tsyringe'; | ||
| import {Maybe} from 'true-myth'; | ||
|
|
||
| import { | ||
| TabIndex, | ||
|
|
@@ -98,7 +99,7 @@ const getMLSStatuses = ({identities, user}: {identities?: WireIdentity[]; user?: | |
| }); | ||
| }; | ||
|
|
||
| export const UserVerificationBadges = ({ | ||
| export const useUserVerificationStatus = ({ | ||
| user, | ||
| groupId, | ||
| isSelfUser, | ||
|
|
@@ -115,12 +116,57 @@ export const UserVerificationBadges = ({ | |
| user, | ||
| }); | ||
|
|
||
| let status: MLSStatuses | undefined = undefined; | ||
| if (mlsStatuses && mlsStatuses.length > 0 && mlsStatuses.every(status => status === MLSStatuses.VALID)) { | ||
| status = MLSStatuses.VALID; | ||
| const mlsStatus = Maybe.of<MLSStatuses.VALID>( | ||
| mlsStatuses && mlsStatuses.length > 0 && mlsStatuses.every(status => status === MLSStatuses.VALID) | ||
| ? MLSStatuses.VALID | ||
| : undefined, | ||
| ); | ||
|
|
||
| return {mlsStatus, isProteusVerified}; | ||
| }; | ||
|
|
||
| export const getUserVerificationBadgeLabel = ( | ||
| translate: RootContextValue['translate'], | ||
| {mlsStatus, isProteusVerified}: {mlsStatus: Maybe<MLSStatuses.VALID>; isProteusVerified: boolean}, | ||
| ): string | undefined => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we avoid |
||
| const labels: string[] = []; | ||
|
|
||
| mlsStatus.map(() => labels.push(translate('E2EI.userDevicesVerified'))); | ||
|
|
||
| if (isProteusVerified) { | ||
| labels.push(translate('proteusDeviceVerified')); | ||
| } | ||
|
|
||
| return <VerificationBadges context="user" isProteusVerified={isProteusVerified} MLSStatus={status} />; | ||
| return labels.length > 0 ? labels.join(', ') : undefined; | ||
| }; | ||
|
|
||
| export const UserVerificationBadgesContent = ({ | ||
| mlsStatus, | ||
| isProteusVerified, | ||
| }: { | ||
| mlsStatus: Maybe<MLSStatuses.VALID>; | ||
| isProteusVerified: boolean; | ||
| }) => { | ||
| return ( | ||
| <VerificationBadges | ||
| context="user" | ||
| isProteusVerified={isProteusVerified} | ||
| MLSStatus={mlsStatus.unwrapOr(undefined)} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| export const UserVerificationBadges = ({ | ||
| user, | ||
| groupId, | ||
| isSelfUser, | ||
| }: { | ||
| user: User; | ||
| groupId?: string; | ||
| isSelfUser?: boolean; | ||
| }) => { | ||
| const {mlsStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser}); | ||
| return <UserVerificationBadgesContent mlsStatus={mlsStatus} isProteusVerified={isProteusVerified} />; | ||
| }; | ||
|
|
||
| export const DeviceVerificationBadges = ({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,12 +19,18 @@ | |
|
|
||
| import {memo} from 'react'; | ||
|
|
||
| import is from '@sindresorhus/is'; | ||
| import cx from 'classnames'; | ||
| import {Availability} from '@wireapp/protocol-messaging'; | ||
|
|
||
| import {TabIndex} from '@wireapp/react-ui-kit'; | ||
|
|
||
| import {Avatar, AVATAR_SIZE} from 'Components/avatar'; | ||
| import {UserVerificationBadges} from 'Components/badge'; | ||
| import { | ||
| getUserVerificationBadgeLabel, | ||
| useUserVerificationStatus, | ||
| UserVerificationBadgesContent, | ||
| } from 'Components/badge'; | ||
| import {LegalHoldDot} from 'Components/LegalHoldDot'; | ||
| import {User} from 'Repositories/entity/User'; | ||
| import {useApplicationContext} from 'src/script/page/rootProvider'; | ||
|
|
@@ -37,18 +43,38 @@ import {AvailabilityContextMenu} from '../../../ui/availabilityContextMenu'; | |
| interface AvailabilityStateButtonWrapperProps { | ||
| children: React.ReactElement; | ||
| isTeam: boolean; | ||
| ariaLabel: string; | ||
| showAvailabilityContextMenu: (event: MouseEvent) => void; | ||
| } | ||
|
|
||
| const isAvailabilityType = (value: unknown): value is Availability.Type => | ||
| Object.values(Availability.Type).some(availabilityType => availabilityType === value); | ||
|
|
||
| const getAvailabilityTranslationKey = (availability: Availability.Type) => { | ||
| switch (availability) { | ||
| case Availability.Type.AVAILABLE: | ||
| return 'availability.available'; | ||
| case Availability.Type.BUSY: | ||
| return 'availability.busy'; | ||
| case Availability.Type.AWAY: | ||
| return 'availability.away'; | ||
| case Availability.Type.NONE: | ||
| return 'availability.none'; | ||
| } | ||
| }; | ||
|
|
||
| const AvailabilityStateButtonWrapper = ({ | ||
| children, | ||
| isTeam = false, | ||
| ariaLabel, | ||
| showAvailabilityContextMenu, | ||
| }: AvailabilityStateButtonWrapperProps) => { | ||
| return isTeam ? ( | ||
| <button | ||
| onClick={event => showAvailabilityContextMenu(event.nativeEvent)} | ||
| className="button-reset-default user-details-avatar" | ||
| aria-label={ariaLabel} | ||
|
e-maad marked this conversation as resolved.
|
||
| aria-haspopup="menu" | ||
| > | ||
| {children} | ||
| </button> | ||
|
|
@@ -69,9 +95,11 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa | |
| const { | ||
| name: userName, | ||
| username: userHandle, | ||
| availability, | ||
| isOnLegalHold, | ||
| hasPendingLegalHold, | ||
| } = useKoSubscribableChildren(user, ['hasPendingLegalHold', 'isOnLegalHold', 'name', 'username']); | ||
| } = useKoSubscribableChildren(user, ['availability', 'hasPendingLegalHold', 'isOnLegalHold', 'name', 'username']); | ||
| const verificationStatus = useUserVerificationStatus({user, groupId, isSelfUser: isTeam}); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still causes Please calculate the verification status once and pass the resulting |
||
|
|
||
| const showLegalHold = isOnLegalHold || hasPendingLegalHold; | ||
|
|
||
|
|
@@ -84,28 +112,45 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa | |
| }); | ||
| }; | ||
|
|
||
| const avatarAriaLabel = [ | ||
| userName, | ||
| userHandle, | ||
| isTeam && isAvailabilityType(availability) ? translate(getAvailabilityTranslationKey(availability)) : undefined, | ||
| getUserVerificationBadgeLabel(translate, verificationStatus), | ||
| ] | ||
| .filter((label): label is string => is.nonEmptyString(label)) | ||
| .join(', '); | ||
|
|
||
| return ( | ||
| <div css={styles.wrapper(isSideBarOpen)}> | ||
| <AvailabilityStateButtonWrapper isTeam={isTeam} showAvailabilityContextMenu={showAvailabilityContextMenu}> | ||
| <AvailabilityStateButtonWrapper | ||
| isTeam={isTeam} | ||
| ariaLabel={avatarAriaLabel} | ||
| showAvailabilityContextMenu={showAvailabilityContextMenu} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other button that invokes |
||
| > | ||
| <Avatar | ||
| className={cx('see-through', {'user-details-avatar': !isTeam})} | ||
| participant={user} | ||
| avatarSize={AVATAR_SIZE.MEDIUM} | ||
| avatarAlt={translate('selfProfileImageAlt')} | ||
| avatarAlt={isTeam ? '' : translate('selfProfileImageAlt')} | ||
| /> | ||
| </AvailabilityStateButtonWrapper> | ||
|
|
||
| <div css={styles.userDetailsWrapper(isSideBarOpen)}> | ||
| {isTeam ? ( | ||
| <> | ||
| <div css={styles.userDetails} data-uie-name="status-availability"> | ||
| <button css={styles.userFullName} onClick={event => showAvailabilityContextMenu(event.nativeEvent)}> | ||
| <button | ||
| css={styles.userFullName} | ||
| onClick={event => showAvailabilityContextMenu(event.nativeEvent)} | ||
| aria-haspopup="menu" | ||
| > | ||
| <span data-uie-name="status-label" css={{...styles.userName, ...styles.textEllipsis}} title={userName}> | ||
| {userName} | ||
| </span> | ||
| </button> | ||
|
|
||
| <UserVerificationBadges user={user} isSelfUser groupId={groupId} /> | ||
| <UserVerificationBadgesContent {...verificationStatus} /> | ||
| </div> | ||
|
|
||
| {showLegalHold && ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests cover
getUserVerificationBadgeLabelin isolation, but they do not test the newly constructed accessible name assigned to the button inUserDetails.Please add component tests that render
UserDetailsand verify the button’s accessible name. At minimum, cover the availability status, combined MLS and Proteus verification, and theEXPIRES_SOONtranslation. Otherwise, the integration between the status values and the actual accessible name remains untested.