From 3f7f766e47af3eced2c14cc07bffbf1d18a012da Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 9 Sep 2025 19:44:43 +0200 Subject: [PATCH] [Glitch] Improve accessibility of visibility modal dropdowns Port 377e8703482df5630174a79b6b804b95a0cae789 to glitch-soc Signed-off-by: Claire --- .../glitch/components/dropdown/index.tsx | 48 ++++--- .../glitch/components/dropdown_selector.tsx | 54 ++++---- .../components/select_with_label.tsx | 18 ++- .../ui/components/visibility_modal.tsx | 131 ++++++++++-------- .../flavours/glitch/styles/components.scss | 19 +-- 5 files changed, 150 insertions(+), 120 deletions(-) diff --git a/app/javascript/flavours/glitch/components/dropdown/index.tsx b/app/javascript/flavours/glitch/components/dropdown/index.tsx index 487850fa70..b6a04b9027 100644 --- a/app/javascript/flavours/glitch/components/dropdown/index.tsx +++ b/app/javascript/flavours/glitch/components/dropdown/index.tsx @@ -1,7 +1,7 @@ import { useCallback, useId, useMemo, useRef, useState } from 'react'; import type { ComponentPropsWithoutRef, FC } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { useIntl } from 'react-intl'; import type { MessageDescriptor } from 'react-intl'; import classNames from 'classnames'; @@ -17,11 +17,12 @@ import { Icon } from '../icon'; import { matchWidth } from './utils'; interface DropdownProps { - title: string; disabled?: boolean; items: SelectItem[]; onChange: (value: string) => void; current: string; + labelId: string; + descriptionId?: string; emptyText?: MessageDescriptor; classPrefix: string; } @@ -29,39 +30,59 @@ interface DropdownProps { export const Dropdown: FC< DropdownProps & Omit, keyof DropdownProps> > = ({ - title, disabled, items, current, onChange, + labelId, + descriptionId, classPrefix, className, + id, ...buttonProps }) => { + const intl = useIntl(); const buttonRef = useRef(null); - const accessibilityId = useId(); + const uniqueId = useId(); + const buttonId = id ?? `${uniqueId}-button`; + const listboxId = `${uniqueId}-listbox`; const [open, setOpen] = useState(false); + const handleToggle = useCallback(() => { if (!disabled) { - setOpen((prevOpen) => !prevOpen); + setOpen((prevOpen) => { + buttonRef.current?.focus(); + return !prevOpen; + }); } }, [disabled]); + const handleClose = useCallback(() => { setOpen(false); + buttonRef.current?.focus(); }, []); + const currentText = useMemo( - () => items.find((i) => i.value === current)?.text, - [current, items], + () => + items.find((i) => i.value === current)?.text ?? + intl.formatMessage({ + id: 'dropdown.empty', + defaultMessage: 'Select an option', + }), + [current, intl, items], ); + return ( <>