Files
3x-ui/frontend/src/pages/inbounds/form/protocols/vless.tsx
T
Hamed f4030c889c fix(frontend): make inbound forms accessible to screen readers (#7025)
Expose selectable options and selected values, associate validation guidance with controls, and contain keyboard focus in inbound dialogs. Preserve create and edit serialization and add Chromium keyboard and accessibility checks.
2026-11-05 18:08:49 +01:00

103 lines
3.5 KiB
TypeScript

import { NativeSelect } from '@/components/form/NativeSelect';
import { useState, useId } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Form, Input, InputNumber, Space, Typography } from 'antd';
import { Controller, useFormContext } from 'react-hook-form';
import { FormField } from '@/components/form/rhf';
import { VLESS_AUTH_LABEL_KEYS, type VlessAuthKind } from '@/lib/xray/vless-encryption';
interface VlessFieldsProps {
saving: boolean;
selectedVlessAuth: string;
vlessAuthKind: VlessAuthKind | null;
network: string;
security: string;
getNewVlessEnc: (kind: VlessAuthKind) => void;
clearVlessEnc: () => void;
}
export default function VlessFields({
saving,
selectedVlessAuth,
vlessAuthKind,
network,
security,
getNewVlessEnc,
clearVlessEnc,
}: VlessFieldsProps) {
const { t } = useTranslation();
const uid = useId();
const { control } = useFormContext();
const [authKind, setAuthKind] = useState<VlessAuthKind>(vlessAuthKind ?? 'x25519');
const [syncedAuthKind, setSyncedAuthKind] = useState(vlessAuthKind);
if (vlessAuthKind !== syncedAuthKind) {
setSyncedAuthKind(vlessAuthKind);
setAuthKind(vlessAuthKind ?? 'x25519');
}
const authOptions = (Object.entries(VLESS_AUTH_LABEL_KEYS) as [VlessAuthKind, string][]).map(
([value, labelKey]) => ({ value, label: t(labelKey) }),
);
return (
<>
<FormField name={['settings', 'decryption']} label={t('pages.inbounds.decryption')}>
<Input />
</FormField>
<FormField name={['settings', 'encryption']} label={t('pages.inbounds.encryption')}>
<Input />
</FormField>
<Form.Item htmlFor={`${uid}-authKind`} label={t('pages.inbounds.vlessAuthGenerate')}>
<Space size={8} wrap>
<NativeSelect
id={`${uid}-authKind`}
value={authKind}
onChange={(v) => setAuthKind(v)}
options={authOptions}
style={{ width: 240 }}
/>
<Button type="primary" loading={saving} onClick={() => getNewVlessEnc(authKind)}>
{t('pages.inbounds.vlessAuthGenerateButton')}
</Button>
<Button danger onClick={clearVlessEnc}>
{t('clear')}
</Button>
</Space>
<Typography.Text type="secondary" className="vless-auth-state">
{t('pages.inbounds.vlessAuthSelected', { auth: selectedVlessAuth })}
</Typography.Text>
</Form.Item>
{network === 'tcp' && (security === 'tls' || security === 'reality') && (
<Form.Item
label={t('pages.inbounds.form.visionTestseed')}
extra="Applies only to clients using the xtls-rprx-vision flow; ignored otherwise."
>
<Space.Compact block>
{[900, 500, 900, 256].map((def, i) => (
<Controller
key={i}
control={control}
name={`settings.testseed.${i}`}
defaultValue={def}
render={({ field }) => (
<InputNumber
aria-label={`${t('pages.inbounds.form.visionTestseed')} ${i + 1}`}
min={1}
style={{ width: '25%' }}
value={field.value as number}
onChange={field.onChange}
onBlur={field.onBlur}
ref={field.ref}
/>
)}
/>
))}
</Space.Compact>
</Form.Item>
)}
</>
);
}