Files
3x-ui/frontend/src/pages/sub/SubQrButton.tsx
T
Alireza Ahmadi e2184777a0 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.
2026-10-23 18:29:47 +02:00

81 lines
2.2 KiB
TypeScript

import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Input, Modal, QRCode } from 'antd';
import { CopyOutlined, DownloadOutlined, QrcodeOutlined } from '@ant-design/icons';
interface SubQrButtonProps {
value: string;
label: string;
onCopy: (value: string) => void;
}
export default function SubQrButton({ value, label, onCopy }: SubQrButtonProps) {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const qrRef = useRef<HTMLDivElement>(null);
const saveQr = () => {
const canvas = qrRef.current?.querySelector('canvas');
if (!canvas) return;
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = `${label || 'qrcode'}.png`;
link.click();
};
return (
<>
<Button
icon={<QrcodeOutlined aria-hidden />}
aria-label="QR"
title="QR"
onClick={() => setOpen(true)}
/>
<Modal
open={open}
onCancel={() => setOpen(false)}
footer={null}
width={440}
centered
destroyOnHidden
rootClassName="sub-qr-modal"
title={t('subscription.qrTitle')}
>
<p className="sub-muted sub-qr-modal-hint">{t('subscription.qrHint')}</p>
<div ref={qrRef} className="sub-qr-modal-code">
<QRCode
value={value}
size={240}
type="canvas"
marginSize={2}
bordered={false}
color="#000000"
bgColor="#ffffff"
/>
</div>
<Input.TextArea
aria-label={t('subscription.tabLinks')}
className="sub-qr-modal-link"
value={value}
readOnly
dir="ltr"
autoSize={{ minRows: 2, maxRows: 5 }}
/>
<div className="sub-qr-modal-actions">
<Button
type="primary"
size="large"
icon={<CopyOutlined aria-hidden />}
onClick={() => onCopy(value)}
>
{t('copy')}
</Button>
<Button size="large" icon={<DownloadOutlined aria-hidden />} onClick={saveQr}>
{t('subscription.saveQr')}
</Button>
</div>
</Modal>
</>
);
}