mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-05 20:55:29 +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:
@@ -252,7 +252,7 @@ export default function NodeList({
|
||||
parent: record.parentGuid ? nameByGuid.get(record.parentGuid) || '-' : '-',
|
||||
})}
|
||||
>
|
||||
<Tag icon={<ApartmentOutlined />} style={{ margin: 0 }}>
|
||||
<Tag icon={<ApartmentOutlined aria-hidden />} style={{ margin: 0 }}>
|
||||
{t('pages.nodes.subNode')}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
@@ -263,7 +263,7 @@ export default function NodeList({
|
||||
type="text"
|
||||
size="small"
|
||||
style={{ fontSize: 16 }}
|
||||
icon={<ThunderboltOutlined />}
|
||||
icon={<ThunderboltOutlined aria-hidden />}
|
||||
aria-label={t('pages.nodes.probe')}
|
||||
onClick={() => onProbe(record)}
|
||||
/>
|
||||
@@ -274,7 +274,7 @@ export default function NodeList({
|
||||
type="text"
|
||||
size="small"
|
||||
style={{ fontSize: 16 }}
|
||||
icon={<CloudDownloadOutlined />}
|
||||
icon={<CloudDownloadOutlined aria-hidden />}
|
||||
aria-label={t('pages.nodes.updatePanel')}
|
||||
onClick={() => onUpdateNode(record)}
|
||||
/>
|
||||
@@ -285,7 +285,7 @@ export default function NodeList({
|
||||
type="text"
|
||||
size="small"
|
||||
style={{ fontSize: 16 }}
|
||||
icon={<EditOutlined />}
|
||||
icon={<EditOutlined aria-hidden />}
|
||||
aria-label={t('edit')}
|
||||
onClick={() => onEdit(record)}
|
||||
/>
|
||||
@@ -296,7 +296,7 @@ export default function NodeList({
|
||||
size="small"
|
||||
danger
|
||||
style={{ fontSize: 16 }}
|
||||
icon={<DeleteOutlined />}
|
||||
icon={<DeleteOutlined aria-hidden />}
|
||||
aria-label={t('delete')}
|
||||
onClick={() => onDelete(record)}
|
||||
/>
|
||||
@@ -316,6 +316,7 @@ export default function NodeList({
|
||||
<Switch
|
||||
checked={!!record.enable}
|
||||
size="small"
|
||||
aria-label={t('pages.nodes.enable')}
|
||||
onChange={(v) => onToggleEnable(record, v)}
|
||||
/>
|
||||
),
|
||||
@@ -331,7 +332,7 @@ export default function NodeList({
|
||||
>
|
||||
<span className="name">
|
||||
{record.transitive && (
|
||||
<ApartmentOutlined style={{ marginInlineEnd: 6, opacity: 0.6 }} />
|
||||
<ApartmentOutlined aria-hidden style={{ marginInlineEnd: 6, opacity: 0.6 }} />
|
||||
)}
|
||||
{record.name}
|
||||
</span>
|
||||
@@ -391,7 +392,7 @@ export default function NodeList({
|
||||
<StatusLabel status={record.status} xrayState={record.xrayState} />
|
||||
{tip && (
|
||||
<Tooltip title={tip}>
|
||||
<ExclamationCircleOutlined style={{ color: iconColor }} />
|
||||
<ExclamationCircleOutlined style={{ color: iconColor }} aria-label={tip} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
@@ -460,7 +461,7 @@ export default function NodeList({
|
||||
render: (_value, record) => (
|
||||
<Space size={2}>
|
||||
<Tag className="client-count-tag" style={{ margin: 0, padding: '0 2px' }}>
|
||||
<TeamOutlined /> {record.clientCount || 0}
|
||||
<TeamOutlined aria-hidden /> {record.clientCount || 0}
|
||||
</Tag>
|
||||
{record.activeCount ? (
|
||||
<Tooltip title={t('subscription.active')}>
|
||||
@@ -562,7 +563,7 @@ export default function NodeList({
|
||||
locale={{
|
||||
emptyText: (
|
||||
<div className="card-empty">
|
||||
<ClusterOutlined style={{ fontSize: 32, marginBottom: 8 }} />
|
||||
<ClusterOutlined aria-hidden style={{ fontSize: 32, marginBottom: 8 }} />
|
||||
<div>{t('noData')}</div>
|
||||
</div>
|
||||
),
|
||||
@@ -579,14 +580,14 @@ export default function NodeList({
|
||||
return (
|
||||
<Card size="small" hoverable>
|
||||
<div className="toolbar">
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={onAdd}>
|
||||
<Button type="primary" icon={<PlusOutlined aria-hidden />} onClick={onAdd}>
|
||||
{t('pages.nodes.addNode')}
|
||||
</Button>
|
||||
<Button icon={<SafetyCertificateOutlined />} onClick={onMtls}>
|
||||
<Button icon={<SafetyCertificateOutlined aria-hidden />} onClick={onMtls}>
|
||||
{t('pages.nodes.mtls.title')}
|
||||
</Button>
|
||||
{selectedIds.length > 0 && (
|
||||
<Button icon={<CloudDownloadOutlined />} onClick={onUpdateSelected}>
|
||||
<Button icon={<CloudDownloadOutlined aria-hidden />} onClick={onUpdateSelected}>
|
||||
{t('pages.nodes.updateSelected', { count: selectedIds.length })}
|
||||
</Button>
|
||||
)}
|
||||
@@ -597,7 +598,7 @@ export default function NodeList({
|
||||
<div className="node-cards">
|
||||
{dataSource.length === 0 ? (
|
||||
<div className="card-empty">
|
||||
<ClusterOutlined style={{ fontSize: 28, opacity: 0.5 }} />
|
||||
<ClusterOutlined aria-hidden style={{ fontSize: 28, opacity: 0.5 }} />
|
||||
<div>{t('noData')}</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -609,11 +610,11 @@ export default function NodeList({
|
||||
style={{ paddingInlineStart: 16, opacity: 0.85 }}
|
||||
>
|
||||
<div className="card-head">
|
||||
<ApartmentOutlined style={{ opacity: 0.6 }} />
|
||||
<ApartmentOutlined aria-hidden style={{ opacity: 0.6 }} />
|
||||
<StatusDot status={record.status} xrayState={record.xrayState} />
|
||||
<span className="node-name">{record.name}</span>
|
||||
<div className="card-actions">
|
||||
<Tag icon={<ApartmentOutlined />} style={{ margin: 0 }}>
|
||||
<Tag icon={<ApartmentOutlined aria-hidden />} style={{ margin: 0 }}>
|
||||
{t('pages.nodes.subNode')}
|
||||
</Tag>
|
||||
</div>
|
||||
@@ -653,6 +654,7 @@ export default function NodeList({
|
||||
<Switch
|
||||
checked={!!record.enable}
|
||||
size="small"
|
||||
aria-label={t('pages.nodes.enable')}
|
||||
onChange={(v) => onToggleEnable(record, v)}
|
||||
/>
|
||||
<Dropdown
|
||||
@@ -664,7 +666,7 @@ export default function NodeList({
|
||||
key: 'probe',
|
||||
label: (
|
||||
<>
|
||||
<ThunderboltOutlined /> {t('pages.nodes.probe')}
|
||||
<ThunderboltOutlined aria-hidden /> {t('pages.nodes.probe')}
|
||||
</>
|
||||
),
|
||||
onClick: () => onProbe(record),
|
||||
@@ -675,7 +677,8 @@ export default function NodeList({
|
||||
key: 'update',
|
||||
label: (
|
||||
<>
|
||||
<CloudDownloadOutlined /> {t('pages.nodes.updatePanel')}
|
||||
<CloudDownloadOutlined aria-hidden />{' '}
|
||||
{t('pages.nodes.updatePanel')}
|
||||
</>
|
||||
),
|
||||
onClick: () => onUpdateNode(record),
|
||||
@@ -686,7 +689,7 @@ export default function NodeList({
|
||||
key: 'edit',
|
||||
label: (
|
||||
<>
|
||||
<EditOutlined /> {t('edit')}
|
||||
<EditOutlined aria-hidden /> {t('edit')}
|
||||
</>
|
||||
),
|
||||
onClick: () => onEdit(record),
|
||||
@@ -696,7 +699,7 @@ export default function NodeList({
|
||||
danger: true,
|
||||
label: (
|
||||
<>
|
||||
<DeleteOutlined /> {t('delete')}
|
||||
<DeleteOutlined aria-hidden /> {t('delete')}
|
||||
</>
|
||||
),
|
||||
onClick: () => onDelete(record),
|
||||
@@ -708,7 +711,7 @@ export default function NodeList({
|
||||
type="text"
|
||||
size="small"
|
||||
className="row-action-trigger"
|
||||
icon={<MoreOutlined />}
|
||||
icon={<MoreOutlined aria-hidden />}
|
||||
aria-label={t('more')}
|
||||
/>
|
||||
</Dropdown>
|
||||
@@ -782,7 +785,7 @@ export default function NodeList({
|
||||
const { tip, iconColor } = statusIssue(statsNode);
|
||||
return tip ? (
|
||||
<Tooltip title={tip}>
|
||||
<ExclamationCircleOutlined style={{ color: iconColor }} />
|
||||
<ExclamationCircleOutlined style={{ color: iconColor }} aria-label={tip} />
|
||||
</Tooltip>
|
||||
) : null;
|
||||
})()}
|
||||
@@ -820,7 +823,7 @@ export default function NodeList({
|
||||
<div className="stat-row">
|
||||
<span className="stat-label">{t('clients')}</span>
|
||||
<Tag>
|
||||
<TeamOutlined /> {statsNode.clientCount || 0}
|
||||
<TeamOutlined aria-hidden /> {statsNode.clientCount || 0}
|
||||
</Tag>
|
||||
{statsNode.activeCount ? (
|
||||
<Tag color="green">
|
||||
|
||||
Reference in New Issue
Block a user