mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-06 21:25:04 +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 { lazy, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { lazy, useCallback, useEffect, useId, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button,
|
||||
@@ -89,6 +89,7 @@ async function fetchEmailsForGroup(name: string): Promise<string[]> {
|
||||
export default function GroupsPage() {
|
||||
usePageTitle();
|
||||
const { t } = useTranslation();
|
||||
const uid = useId();
|
||||
const { isDark, isUltra, antdThemeConfig } = useTheme();
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [modal, modalContextHolder] = Modal.useModal();
|
||||
@@ -353,41 +354,41 @@ export default function GroupsPage() {
|
||||
return [
|
||||
{
|
||||
key: 'subLinks',
|
||||
icon: <LinkOutlined />,
|
||||
icon: <LinkOutlined aria-hidden />,
|
||||
label: t('pages.clients.subLinksSelected', { count: row.clientCount || 0 }),
|
||||
disabled: !row.clientCount,
|
||||
onClick: () => openSubLinksFor(row),
|
||||
},
|
||||
{
|
||||
key: 'adjust',
|
||||
icon: <ClockCircleOutlined />,
|
||||
icon: <ClockCircleOutlined aria-hidden />,
|
||||
label: t('pages.clients.adjustSelected', { count: row.clientCount || 0 }),
|
||||
disabled: !row.clientCount,
|
||||
onClick: () => openAdjustFor(row),
|
||||
},
|
||||
{
|
||||
key: 'reset',
|
||||
icon: <RetweetOutlined />,
|
||||
icon: <RetweetOutlined aria-hidden />,
|
||||
label: t('pages.groups.resetTraffic'),
|
||||
disabled: !row.clientCount,
|
||||
onClick: () => onResetTraffic(row),
|
||||
},
|
||||
{
|
||||
key: 'addClients',
|
||||
icon: <UsergroupAddOutlined />,
|
||||
icon: <UsergroupAddOutlined aria-hidden />,
|
||||
label: t('pages.groups.addToGroup'),
|
||||
onClick: () => openAddClientsFor(row),
|
||||
},
|
||||
{
|
||||
key: 'rename',
|
||||
icon: <EditOutlined />,
|
||||
icon: <EditOutlined aria-hidden />,
|
||||
label: t('pages.groups.rename'),
|
||||
onClick: () => openRename(row),
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'removeClients',
|
||||
icon: <UsergroupDeleteOutlined />,
|
||||
icon: <UsergroupDeleteOutlined aria-hidden />,
|
||||
label: t('pages.groups.removeFromGroup'),
|
||||
danger: true,
|
||||
disabled: !row.clientCount,
|
||||
@@ -395,7 +396,7 @@ export default function GroupsPage() {
|
||||
},
|
||||
{
|
||||
key: 'deleteClients',
|
||||
icon: <DeleteOutlined />,
|
||||
icon: <DeleteOutlined aria-hidden />,
|
||||
label: t('pages.groups.deleteClients'),
|
||||
danger: true,
|
||||
disabled: !row.clientCount,
|
||||
@@ -403,7 +404,7 @@ export default function GroupsPage() {
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
icon: <DeleteOutlined />,
|
||||
icon: <DeleteOutlined aria-hidden />,
|
||||
label: t('pages.groups.deleteGroupOnly'),
|
||||
danger: true,
|
||||
onClick: () => onDelete(row),
|
||||
@@ -424,7 +425,7 @@ export default function GroupsPage() {
|
||||
size="small"
|
||||
type="text"
|
||||
style={{ fontSize: 16 }}
|
||||
icon={<MoreOutlined />}
|
||||
icon={<MoreOutlined aria-hidden />}
|
||||
/>
|
||||
</Dropdown>
|
||||
<Tooltip title={t('pages.groups.rename')}>
|
||||
@@ -433,7 +434,7 @@ export default function GroupsPage() {
|
||||
size="small"
|
||||
type="text"
|
||||
style={{ fontSize: 16 }}
|
||||
icon={<EditOutlined />}
|
||||
icon={<EditOutlined aria-hidden />}
|
||||
onClick={() => openRename(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -518,14 +519,14 @@ export default function GroupsPage() {
|
||||
<Statistic
|
||||
title={t('pages.groups.totalGroups')}
|
||||
value={String(totalGroups)}
|
||||
prefix={<TagsOutlined />}
|
||||
prefix={<TagsOutlined aria-hidden />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
<Statistic
|
||||
title={t('pages.groups.totalGroupedClients')}
|
||||
value={String(totalClients)}
|
||||
prefix={<TeamOutlined />}
|
||||
prefix={<TeamOutlined aria-hidden />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={12} md={6}>
|
||||
@@ -534,9 +535,11 @@ export default function GroupsPage() {
|
||||
value={0}
|
||||
formatter={() => (
|
||||
<span>
|
||||
<ArrowUpOutlined /> {SizeFormatter.sizeFormat(totalUpload)}
|
||||
<ArrowUpOutlined aria-hidden />{' '}
|
||||
{SizeFormatter.sizeFormat(totalUpload)}
|
||||
{' / '}
|
||||
<ArrowDownOutlined /> {SizeFormatter.sizeFormat(totalDownload)}
|
||||
<ArrowDownOutlined aria-hidden />{' '}
|
||||
{SizeFormatter.sizeFormat(totalDownload)}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
@@ -545,7 +548,7 @@ export default function GroupsPage() {
|
||||
<Statistic
|
||||
title={t('pages.groups.totalTraffic')}
|
||||
value={SizeFormatter.sizeFormat(totalTraffic)}
|
||||
prefix={<PieChartOutlined />}
|
||||
prefix={<PieChartOutlined aria-hidden />}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -561,7 +564,7 @@ export default function GroupsPage() {
|
||||
<Button
|
||||
aria-label={t('pages.groups.addGroup')}
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
icon={<PlusOutlined aria-hidden />}
|
||||
onClick={openCreate}
|
||||
>
|
||||
{!isMobile && t('pages.groups.addGroup')}
|
||||
@@ -579,7 +582,7 @@ export default function GroupsPage() {
|
||||
locale={{
|
||||
emptyText: (
|
||||
<div className="card-empty">
|
||||
<TagsOutlined style={{ fontSize: 32, marginBottom: 8 }} />
|
||||
<TagsOutlined aria-hidden style={{ fontSize: 32, marginBottom: 8 }} />
|
||||
<div>{t('noData')}</div>
|
||||
</div>
|
||||
),
|
||||
@@ -604,8 +607,9 @@ export default function GroupsPage() {
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label={t('pages.groups.name')}>
|
||||
<Form.Item label={t('pages.groups.name')} htmlFor={`${uid}-create-name`}>
|
||||
<Input
|
||||
id={`${uid}-create-name`}
|
||||
value={createName}
|
||||
onChange={(e) => setCreateName(e.target.value)}
|
||||
onPressEnter={confirmCreate}
|
||||
@@ -627,8 +631,9 @@ export default function GroupsPage() {
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label={t('pages.groups.name')}>
|
||||
<Form.Item label={t('pages.groups.name')} htmlFor={`${uid}-rename-name`}>
|
||||
<Input
|
||||
id={`${uid}-rename-name`}
|
||||
value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
onPressEnter={confirmRename}
|
||||
|
||||
Reference in New Issue
Block a user