mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-12 16:15:02 +03:00
fix(a11y): silence live stats and make Select tags removable for screen readers
Ant Design's Spin wraps its content in aria-live="polite", so every WebSocket tick on the dashboard, inbounds, clients and nodes pages was announced, and only the changed number was read, with no label. Pass aria-live="off" on every Spin that wraps content; aria-busy still signals loading. Each vital tile is now a group named by its metric, so "47.2 %" is read as CPU. rc-select renders each tag's remove "x" as an aria-hidden span with no tabIndex, so screen-reader users could only Backspace the last value away, never remove one SNI or inbound from the middle. selectTagRender swaps in a real button named "Remove <value>" and moves focus to the next tag after removal. It is applied to every tags/multiple Select. maxTagCount="responsive" is dropped because it hid selected inbounds behind "+N", where no screen reader could reach them. Two oxlint rules (antd-a11y/spin-content-not-live and antd-a11y/select-tag-remove) keep new code from regressing either fix.
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
import { RadarChartOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { selectTagRender } from '@/components/form';
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
import { UTLS_FINGERPRINT } from '@/schemas/primitives';
|
||||
import {
|
||||
@@ -208,7 +209,12 @@ export default function RealityForm({
|
||||
</Form.Item>
|
||||
)}
|
||||
<FormField label="SNI" name={['streamSettings', 'realitySettings', 'serverNames']}>
|
||||
<Select mode="tags" tokenSeparators={[',']} style={{ width: '100%' }} />
|
||||
<Select
|
||||
mode="tags"
|
||||
tagRender={selectTagRender}
|
||||
tokenSeparators={[',']}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
name={['streamSettings', 'realitySettings', 'maxTimediff']}
|
||||
@@ -251,6 +257,7 @@ export default function RealityForm({
|
||||
<Select
|
||||
id={`${uid}-shortIds`}
|
||||
mode="tags"
|
||||
tagRender={selectTagRender}
|
||||
tokenSeparators={[',']}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { CipherSuitesSelect } from '@/components/form';
|
||||
import { CipherSuitesSelect, selectTagRender } from '@/components/form';
|
||||
import DynamicEchHelper from '@/components/form/DynamicEchHelper';
|
||||
import { FormField } from '@/components/form/rhf';
|
||||
import {
|
||||
@@ -380,6 +380,7 @@ export default function TlsForm({
|
||||
<FormField name={['streamSettings', 'tlsSettings', 'alpn']} label="ALPN">
|
||||
<Select
|
||||
mode="multiple"
|
||||
tagRender={selectTagRender}
|
||||
tokenSeparators={[',']}
|
||||
style={{ width: '100%' }}
|
||||
options={Object.values(ALPN_OPTION).map((a) => ({ value: a, label: a }))}
|
||||
@@ -392,6 +393,7 @@ export default function TlsForm({
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
tagRender={selectTagRender}
|
||||
tokenSeparators={[',', ' ']}
|
||||
style={{ width: '100%' }}
|
||||
options={['X25519MLKEM768', 'X25519', 'P-256', 'P-384', 'P-521'].map((c) => ({
|
||||
@@ -478,6 +480,7 @@ export default function TlsForm({
|
||||
id={`${uid}-pinnedPeerCertSha256`}
|
||||
aria-describedby={`${uid}-pinnedPeerCertSha256-hint`}
|
||||
mode="tags"
|
||||
tagRender={selectTagRender}
|
||||
tokenSeparators={[',', ' ']}
|
||||
placeholder={t('pages.inbounds.form.pinnedPeerCertSha256Placeholder')}
|
||||
style={{ width: 'calc(100% - 64px)' }}
|
||||
|
||||
Reference in New Issue
Block a user