Focus first selectable element in preferences pane

This commit is contained in:
Josh Perez 2023-04-25 17:54:05 -04:00 committed by GitHub
parent 078b176ec6
commit e8a3dc5db6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 48 deletions

View file

@ -1,7 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useMemo } from 'react';
import React, { forwardRef, useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import { getClassNamesFor } from '../util/getClassNamesFor';
@ -24,18 +24,21 @@ export type PropsType = {
onClick?: () => unknown;
};
export function Checkbox({
checked,
children,
description,
disabled,
isRadio,
label,
moduleClassName,
name,
onChange,
onClick,
}: PropsType): JSX.Element {
export const Checkbox = forwardRef(function CheckboxInner(
{
checked,
children,
description,
disabled,
isRadio,
label,
moduleClassName,
name,
onChange,
onClick,
}: PropsType,
ref: React.Ref<HTMLInputElement>
): JSX.Element {
const getClassName = getClassNamesFor('Checkbox', moduleClassName);
const id = useMemo(() => `${name}::${uuid()}`, [name]);
@ -48,6 +51,7 @@ export function Checkbox({
name={name}
onChange={ev => onChange(ev.target.checked)}
onClick={onClick}
ref={ref}
type={isRadio ? 'radio' : 'checkbox'}
/>
</div>
@ -76,4 +80,4 @@ export function Checkbox({
</div>
</div>
);
}
});

View file

@ -3,7 +3,14 @@
import type { AudioDevice } from '@signalapp/ringrtc';
import type { ReactNode } from 'react';
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import focusableSelectors from 'focusable-selectors';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { noop } from 'lodash';
import classNames from 'classnames';
import uuid from 'uuid';
@ -354,6 +361,27 @@ export function Preferences({
[onSelectedMicrophoneChange, availableMicrophones]
);
const selectors = useMemo(() => focusableSelectors.join(','), []);
const settingsPaneRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const settingsPane = settingsPaneRef.current;
if (!settingsPane) {
return;
}
const elements = settingsPane.querySelectorAll<
| HTMLAnchorElement
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement
>(selectors);
if (!elements.length) {
return;
}
elements[0]?.focus();
}, [page, selectors]);
const onAudioOutputSelectChange = useCallback(
(value: string) => {
if (value === 'undefined') {
@ -1278,7 +1306,9 @@ export function Preferences({
{i18n('icu:Preferences__button--privacy')}
</button>
</div>
<div className="Preferences__settings-pane">{settings}</div>
<div className="Preferences__settings-pane" ref={settingsPaneRef}>
{settings}
</div>
</div>
</TitleBarContainer>
);