fix(frontend): make inbound forms accessible to screen readers (#7025)

Expose selectable options and selected values, associate validation guidance with controls, and contain keyboard focus in inbound dialogs. Preserve create and edit serialization and add Chromium keyboard and accessibility checks.
This commit is contained in:
Hamed
2026-11-05 20:38:49 +03:30
committed by GitHub
parent dbb6212988
commit f4030c889c
29 changed files with 875 additions and 110 deletions
@@ -0,0 +1,70 @@
import type { ComponentPropsWithRef } from 'react';
import { theme } from 'antd';
import './NativeSelect.css';
export interface NativeSelectProps<Value extends string | number> extends Omit<
ComponentPropsWithRef<'select'>,
'value' | 'defaultValue' | 'onChange' | 'multiple' | 'children' | 'size'
> {
value?: Value | null;
onChange?: (value: Value) => void;
options: readonly { value: Value; label: string | number; disabled?: boolean }[];
placeholder?: string;
size?: 'small' | 'middle' | 'large';
}
export function NativeSelect<Value extends string | number>({
value,
options,
onChange,
placeholder,
disabled,
className,
style,
size,
...props
}: NativeSelectProps<Value>) {
const { token } = theme.useToken();
const selected = options.findIndex((option) => Object.is(option.value, value));
const hasUnlistedValue = selected < 0 && value != null;
return (
<select
{...props}
className={['native-select', className].filter(Boolean).join(' ')}
disabled={disabled}
value={selected < 0 ? '' : String(selected)}
onChange={(event) => {
const option = options[Number(event.target.value)];
if (event.target.value !== '' && option && !option.disabled) onChange?.(option.value);
}}
style={{
width: '100%',
minWidth: 0,
height:
size === 'small'
? token.controlHeightSM
: size === 'large'
? token.controlHeightLG
: token.controlHeight,
border: `1px solid ${props['aria-invalid'] === true || props['aria-invalid'] === 'true' ? token.colorError : token.colorBorder}`,
borderRadius: token.borderRadius,
backgroundColor: disabled ? token.colorBgContainerDisabled : token.colorBgContainer,
color: disabled ? token.colorTextDisabled : token.colorText,
fontSize: token.fontSize,
...style,
}}
>
{selected < 0 && (
<option value="" disabled={!hasUnlistedValue} hidden={!hasUnlistedValue}>
{hasUnlistedValue ? String(value) : (placeholder ?? '')}
</option>
)}
{options.map((option, index) => (
<option key={String(index)} value={String(index)} disabled={option.disabled}>
{option.label}
</option>
))}
</select>
);
}