Add aria-label to all <Select /> instances

This commit is contained in:
Josh Perez 2022-04-07 17:40:57 -04:00 committed by GitHub
parent 9ffcd44e6d
commit b96b02593b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 17 deletions

View file

@ -6332,6 +6332,14 @@
"message": "Custom time", "message": "Custom time",
"description": "Text for an option in Conversation Details Disappearing Messages setting when user previously selected custom time" "description": "Text for an option in Conversation Details Disappearing Messages setting when user previously selected custom time"
}, },
"DisappearingTimeDialog__label--value": {
"message": "Number",
"description": "aria-label for the number select box"
},
"DisappearingTimeDialog__label--units": {
"message": "Unit of time",
"description": "aria-label for the units of time select box"
},
"DisappearingTimeDialog__title": { "DisappearingTimeDialog__title": {
"message": "Custom Time", "message": "Custom Time",
"description": "Title for the custom disappearing message timeout dialog" "description": "Title for the custom disappearing message timeout dialog"

View file

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react'; import React, { useState } from 'react';
@ -91,12 +91,14 @@ export function DisappearingTimeDialog(props: PropsType): JSX.Element {
<p>{i18n('DisappearingTimeDialog__body')}</p> <p>{i18n('DisappearingTimeDialog__body')}</p>
<section className={`${CSS_MODULE}__time-boxes`}> <section className={`${CSS_MODULE}__time-boxes`}>
<Select <Select
ariaLabel={i18n('DisappearingTimeDialog__label--value')}
moduleClassName={`${CSS_MODULE}__time-boxes__value`} moduleClassName={`${CSS_MODULE}__time-boxes__value`}
value={unitValue} value={unitValue}
onChange={newValue => setUnitValue(parseInt(newValue, 10))} onChange={newValue => setUnitValue(parseInt(newValue, 10))}
options={values.map(value => ({ value, text: value.toString() }))} options={values.map(value => ({ value, text: value.toString() }))}
/> />
<Select <Select
ariaLabel={i18n('DisappearingTimeDialog__label--units')}
moduleClassName={`${CSS_MODULE}__time-boxes__units`} moduleClassName={`${CSS_MODULE}__time-boxes__units`}
value={unit} value={unit}
onChange={newUnit => { onChange={newUnit => {

View file

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
@ -36,6 +36,7 @@ import {
format as formatExpirationTimer, format as formatExpirationTimer,
} from '../util/expirationTimer'; } from '../util/expirationTimer';
import { useEscapeHandling } from '../hooks/useEscapeHandling'; import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useUniqueId } from '../hooks/useUniqueId';
type CheckboxChangeHandlerType = (value: boolean) => unknown; type CheckboxChangeHandlerType = (value: boolean) => unknown;
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown; type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
@ -263,6 +264,9 @@ export const Preferences = ({
whoCanSeeMe, whoCanSeeMe,
zoomFactor, zoomFactor,
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
const themeSelectId = useUniqueId();
const zoomSelectId = useUniqueId();
const [confirmDelete, setConfirmDelete] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false);
const [page, setPage] = useState<Page>(Page.General); const [page, setPage] = useState<Page>(Page.General);
const [showSyncFailed, setShowSyncFailed] = useState(false); const [showSyncFailed, setShowSyncFailed] = useState(false);
@ -410,9 +414,14 @@ export const Preferences = ({
</div> </div>
<SettingsRow> <SettingsRow>
<Control <Control
left={i18n('Preferences--theme')} left={
<label htmlFor={themeSelectId}>
{i18n('Preferences--theme')}
</label>
}
right={ right={
<Select <Select
id={themeSelectId}
onChange={onThemeChange} onChange={onThemeChange}
options={[ options={[
{ {
@ -449,9 +458,12 @@ export const Preferences = ({
} }
/> />
<Control <Control
left={i18n('Preferences--zoom')} left={
<label htmlFor={zoomSelectId}>{i18n('Preferences--zoom')}</label>
}
right={ right={
<Select <Select
id={zoomSelectId}
onChange={onZoomSelectChange} onChange={onZoomSelectChange}
options={zoomFactors} options={zoomFactors}
value={zoomFactor} value={zoomFactor}
@ -576,6 +588,7 @@ export const Preferences = ({
{i18n('callingDeviceSelection__label--video')} {i18n('callingDeviceSelection__label--video')}
</label> </label>
<Select <Select
ariaLabel={i18n('callingDeviceSelection__label--video')}
disabled={!availableCameras.length} disabled={!availableCameras.length}
moduleClassName="Preferences__select" moduleClassName="Preferences__select"
name="video" name="video"
@ -611,6 +624,7 @@ export const Preferences = ({
{i18n('callingDeviceSelection__label--audio-input')} {i18n('callingDeviceSelection__label--audio-input')}
</label> </label>
<Select <Select
ariaLabel={i18n('callingDeviceSelection__label--audio-input')}
disabled={!availableMicrophones.length} disabled={!availableMicrophones.length}
moduleClassName="Preferences__select" moduleClassName="Preferences__select"
name="audio-input" name="audio-input"
@ -646,6 +660,9 @@ export const Preferences = ({
{i18n('callingDeviceSelection__label--audio-output')} {i18n('callingDeviceSelection__label--audio-output')}
</label> </label>
<Select <Select
ariaLabel={i18n(
'callingDeviceSelection__label--audio-output'
)}
disabled={!availableSpeakers.length} disabled={!availableSpeakers.length}
moduleClassName="Preferences__select" moduleClassName="Preferences__select"
name="audio-output" name="audio-output"
@ -738,6 +755,7 @@ export const Preferences = ({
left={i18n('Preferences--notification-content')} left={i18n('Preferences--notification-content')}
right={ right={
<Select <Select
ariaLabel={i18n('Preferences--notification-content')}
disabled={!hasNotifications} disabled={!hasNotifications}
onChange={onNotificationContentChange} onChange={onNotificationContentChange}
options={[ options={[
@ -792,6 +810,7 @@ export const Preferences = ({
left={i18n('Preferences--see-me')} left={i18n('Preferences--see-me')}
right={ right={
<Select <Select
ariaLabel={i18n('Preferences--see-me')}
disabled disabled
onChange={noop} onChange={noop}
options={[ options={[
@ -816,6 +835,7 @@ export const Preferences = ({
left={i18n('Preferences--find-me')} left={i18n('Preferences--find-me')}
right={ right={
<Select <Select
ariaLabel={i18n('Preferences--find-me')}
disabled disabled
onChange={noop} onChange={noop}
options={[ options={[
@ -884,6 +904,7 @@ export const Preferences = ({
} }
right={ right={
<Select <Select
ariaLabel={i18n('settings__DisappearingMessages__timer__label')}
onChange={value => { onChange={value => {
if ( if (
value === String(universalExpireTimer) || value === String(universalExpireTimer) ||

View file

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
@ -12,7 +12,9 @@ export type Option = Readonly<{
}>; }>;
export type PropsType = Readonly<{ export type PropsType = Readonly<{
ariaLabel?: string;
disabled?: boolean; disabled?: boolean;
id?: string;
moduleClassName?: string; moduleClassName?: string;
name?: string; name?: string;
options: ReadonlyArray<Option>; options: ReadonlyArray<Option>;
@ -22,7 +24,16 @@ export type PropsType = Readonly<{
export const Select = React.forwardRef( export const Select = React.forwardRef(
( (
{ disabled, moduleClassName, name, onChange, options, value }: PropsType, {
ariaLabel,
disabled,
id,
moduleClassName,
name,
onChange,
options,
value,
}: PropsType,
ref: React.Ref<HTMLSelectElement> ref: React.Ref<HTMLSelectElement>
): JSX.Element => { ): JSX.Element => {
const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => { const onSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
@ -32,7 +43,9 @@ export const Select = React.forwardRef(
return ( return (
<div className={classNames(['module-select', moduleClassName])}> <div className={classNames(['module-select', moduleClassName])}>
<select <select
aria-label={ariaLabel}
disabled={disabled} disabled={disabled}
id={id}
name={name} name={name}
value={value} value={value}
onChange={onSelectChange} onChange={onSelectChange}

View file

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react'; import type { FunctionComponent } from 'react';
@ -13,6 +13,7 @@ import { Select } from '../../Select';
import { isMuted } from '../../../util/isMuted'; import { isMuted } from '../../../util/isMuted';
import { getMuteOptions } from '../../../util/getMuteOptions'; import { getMuteOptions } from '../../../util/getMuteOptions';
import { parseIntOrThrow } from '../../../util/parseIntOrThrow'; import { parseIntOrThrow } from '../../../util/parseIntOrThrow';
import { useUniqueId } from '../../../hooks/useUniqueId';
type PropsType = { type PropsType = {
conversationType: ConversationTypeType; conversationType: ConversationTypeType;
@ -35,6 +36,8 @@ export const ConversationNotificationsSettings: FunctionComponent<
setMuteExpiration, setMuteExpiration,
setDontNotifyForMentionsIfMuted, setDontNotifyForMentionsIfMuted,
}) => { }) => {
const muteNotificationsSelectId = useUniqueId();
const mentionsSelectId = useUniqueId();
const muteOptions = useMemo( const muteOptions = useMemo(
() => [ () => [
...(isMuted(muteExpiresAt) ...(isMuted(muteExpiresAt)
@ -79,9 +82,18 @@ export const ConversationNotificationsSettings: FunctionComponent<
icon={IconType.mute} icon={IconType.mute}
/> />
} }
label={i18n('muteNotificationsTitle')} label={
<label htmlFor={muteNotificationsSelectId}>
{i18n('muteNotificationsTitle')}
</label>
}
right={ right={
<Select options={muteOptions} onChange={onMuteChange} value={-1} /> <Select
id={muteNotificationsSelectId}
options={muteOptions}
onChange={onMuteChange}
value={-1}
/>
} }
/> />
{conversationType === 'group' && ( {conversationType === 'group' && (
@ -94,10 +106,15 @@ export const ConversationNotificationsSettings: FunctionComponent<
icon={IconType.mention} icon={IconType.mention}
/> />
} }
label={i18n('ConversationNotificationsSettings__mentions__label')} label={
<label htmlFor={mentionsSelectId}>
{i18n('ConversationNotificationsSettings__mentions__label')}
</label>
}
info={i18n('ConversationNotificationsSettings__mentions__info')} info={i18n('ConversationNotificationsSettings__mentions__info')}
right={ right={
<Select <Select
id={mentionsSelectId}
options={[ options={[
{ {
text: i18n( text: i18n(

View file

@ -1,4 +1,4 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React from 'react';
@ -12,6 +12,7 @@ import { PanelSection } from './PanelSection';
import { Select } from '../../Select'; import { Select } from '../../Select';
import { useDelayedRestoreFocus } from '../../../hooks/useRestoreFocus'; import { useDelayedRestoreFocus } from '../../../hooks/useRestoreFocus';
import { useUniqueId } from '../../../hooks/useUniqueId';
const AccessControlEnum = Proto.AccessControl.AccessRequired; const AccessControlEnum = Proto.AccessControl.AccessRequired;
@ -34,6 +35,9 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
isAdmin, isAdmin,
setAccessControlAddFromInviteLinkSetting, setAccessControlAddFromInviteLinkSetting,
}) => { }) => {
const groupLinkSelectId = useUniqueId();
const approveSelectId = useUniqueId();
if (conversation === undefined) { if (conversation === undefined) {
throw new Error('GroupLinkManagement rendered without a conversation'); throw new Error('GroupLinkManagement rendered without a conversation');
} }
@ -61,10 +65,15 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
<PanelSection> <PanelSection>
<PanelRow <PanelRow
info={groupLinkInfo} info={groupLinkInfo}
label={i18n('ConversationDetails--group-link')} label={
<label htmlFor={groupLinkSelectId}>
{i18n('ConversationDetails--group-link')}
</label>
}
right={ right={
isAdmin ? ( isAdmin ? (
<Select <Select
id={groupLinkSelectId}
onChange={createEventHandler(changeHasGroupLink)} onChange={createEventHandler(changeHasGroupLink)}
options={[ options={[
{ {
@ -120,9 +129,14 @@ export const GroupLinkManagement: React.ComponentType<PropsType> = ({
<PanelSection> <PanelSection>
<PanelRow <PanelRow
info={i18n('GroupLinkManagement--approve-info')} info={i18n('GroupLinkManagement--approve-info')}
label={i18n('GroupLinkManagement--approve-label')} label={
<label htmlFor={approveSelectId}>
{i18n('GroupLinkManagement--approve-label')}
</label>
}
right={ right={
<Select <Select
id={approveSelectId}
onChange={createEventHandler( onChange={createEventHandler(
setAccessControlAddFromInviteLinkSetting setAccessControlAddFromInviteLinkSetting
)} )}

View file

@ -1,4 +1,4 @@
// Copyright 2020 Signal Messenger, LLC // Copyright 2020-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React from 'react';
@ -11,6 +11,7 @@ import { SignalService as Proto } from '../../../protobuf';
import { PanelRow } from './PanelRow'; import { PanelRow } from './PanelRow';
import { PanelSection } from './PanelSection'; import { PanelSection } from './PanelSection';
import { Select } from '../../Select'; import { Select } from '../../Select';
import { useUniqueId } from '../../../hooks/useUniqueId';
export type PropsType = { export type PropsType = {
conversation?: ConversationType; conversation?: ConversationType;
@ -27,6 +28,10 @@ export const GroupV2Permissions = ({
setAccessControlMembersSetting, setAccessControlMembersSetting,
setAnnouncementsOnly, setAnnouncementsOnly,
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
const addMembersSelectId = useUniqueId();
const groupInfoSelectId = useUniqueId();
const announcementSelectId = useUniqueId();
if (conversation === undefined) { if (conversation === undefined) {
throw new Error('GroupV2Permissions rendered without a conversation'); throw new Error('GroupV2Permissions rendered without a conversation');
} }
@ -55,10 +60,15 @@ export const GroupV2Permissions = ({
return ( return (
<PanelSection> <PanelSection>
<PanelRow <PanelRow
label={i18n('ConversationDetails--add-members-label')} label={
<label htmlFor={addMembersSelectId}>
{i18n('ConversationDetails--add-members-label')}
</label>
}
info={i18n('ConversationDetails--add-members-info')} info={i18n('ConversationDetails--add-members-info')}
right={ right={
<Select <Select
id={addMembersSelectId}
onChange={updateAccessControlMembers} onChange={updateAccessControlMembers}
options={accessControlOptions} options={accessControlOptions}
value={String(conversation.accessControlMembers)} value={String(conversation.accessControlMembers)}
@ -66,10 +76,15 @@ export const GroupV2Permissions = ({
} }
/> />
<PanelRow <PanelRow
label={i18n('ConversationDetails--group-info-label')} label={
<label htmlFor={groupInfoSelectId}>
{i18n('ConversationDetails--group-info-label')}
</label>
}
info={i18n('ConversationDetails--group-info-info')} info={i18n('ConversationDetails--group-info-info')}
right={ right={
<Select <Select
id={groupInfoSelectId}
onChange={updateAccessControlAttributes} onChange={updateAccessControlAttributes}
options={accessControlOptions} options={accessControlOptions}
value={String(conversation.accessControlAttributes)} value={String(conversation.accessControlAttributes)}
@ -78,10 +93,15 @@ export const GroupV2Permissions = ({
/> />
{showAnnouncementsOnlyPermission && ( {showAnnouncementsOnlyPermission && (
<PanelRow <PanelRow
label={i18n('ConversationDetails--announcement-label')} label={
<label htmlFor={announcementSelectId}>
{i18n('ConversationDetails--announcement-label')}
</label>
}
info={i18n('ConversationDetails--announcement-info')} info={i18n('ConversationDetails--announcement-info')}
right={ right={
<Select <Select
id={announcementSelectId}
onChange={updateAnnouncementsOnly} onChange={updateAnnouncementsOnly}
options={accessControlOptions} options={accessControlOptions}
value={announcementsOnlyValue} value={announcementsOnlyValue}