mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-13 08:35:00 +03:00
fix(frontend): make the panel usable with screen readers (#6907)
* fix(frontend): make the panel usable with screen readers * fix(frontend): keep mobile tab names and the inbound expiry hint Review of #6907 found two gaps in the screen-reader pass: - On phone-width screens catTabLabel renders only the icon and names the tab by putting aria-label on it, but callers now pass that icon with aria-hidden for the desktop label, so every Settings, Xray and Host category tab lost its name under TalkBack. catTabLabel now clears aria-hidden on the icon it uses as the name. - DateTimePicker forwarded id and aria-label but not aria-describedby, so the inbound expiry hint ("leave blank to never expire") was never attached. It now forwards it on both the Gregorian and Jalali input.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState, useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, Button, Form, Input, Modal, Select, Space, Spin, Typography, message } from 'antd';
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
@@ -33,6 +33,7 @@ function fileNameFromUrl(url: string): string {
|
||||
|
||||
export default function GeodataSection({ active, onBusy, onClose }: GeodataSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const uid = useId();
|
||||
const [modal, modalContextHolder] = Modal.useModal();
|
||||
const [messageApi, messageContextHolder] = message.useMessage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -180,15 +181,25 @@ export default function GeodataSection({ active, onBusy, onClose }: GeodataSecti
|
||||
<Spin spinning={loading}>
|
||||
<Alert type="info" className="mb-12" title={t('pages.index.geodataHint')} showIcon />
|
||||
<Form layout="vertical">
|
||||
<Form.Item label={t('pages.index.geodataCron')} style={{ marginBottom: 8 }}>
|
||||
<Form.Item
|
||||
htmlFor={`${uid}-cron`}
|
||||
label={t('pages.index.geodataCron')}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<Input
|
||||
id={`${uid}-cron`}
|
||||
value={cron}
|
||||
placeholder={DEFAULT_CRON}
|
||||
onChange={(e) => setCron(e.target.value)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('pages.index.geodataOutbound')} style={{ marginBottom: 8 }}>
|
||||
<Form.Item
|
||||
htmlFor={`${uid}-outbound`}
|
||||
label={t('pages.index.geodataOutbound')}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<Select
|
||||
id={`${uid}-outbound`}
|
||||
style={{ width: '100%' }}
|
||||
allowClear
|
||||
value={outbound}
|
||||
@@ -204,6 +215,7 @@ export default function GeodataSection({ active, onBusy, onClose }: GeodataSecti
|
||||
{rows.map((row, i) => (
|
||||
<Space.Compact key={i} style={{ width: '100%' }}>
|
||||
<Input
|
||||
aria-label={`URL ${i + 1}`}
|
||||
style={{ width: '60%' }}
|
||||
placeholder="https://example.com/geosite_custom.dat"
|
||||
value={row.url}
|
||||
@@ -211,6 +223,7 @@ export default function GeodataSection({ active, onBusy, onClose }: GeodataSecti
|
||||
onBlur={() => onUrlBlur(i)}
|
||||
/>
|
||||
<Input
|
||||
aria-label={`${t('pages.index.geodataFile')} ${i + 1}`}
|
||||
style={{ width: '40%' }}
|
||||
placeholder={t('pages.index.geodataFile')}
|
||||
value={row.file}
|
||||
@@ -218,14 +231,14 @@ export default function GeodataSection({ active, onBusy, onClose }: GeodataSecti
|
||||
/>
|
||||
<Button
|
||||
aria-label={t('delete')}
|
||||
icon={<DeleteOutlined />}
|
||||
icon={<DeleteOutlined aria-hidden />}
|
||||
onClick={() => setRows((p) => p.filter((_, j) => j !== i))}
|
||||
/>
|
||||
</Space.Compact>
|
||||
))}
|
||||
<div className="actions-row">
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
icon={<PlusOutlined aria-hidden />}
|
||||
onClick={() => setRows((p) => [...p, { url: '', file: '' }])}
|
||||
>
|
||||
{t('pages.index.geodataAddFile')}
|
||||
|
||||
Reference in New Issue
Block a user