- Updated DoctorDetailPage, MySecretariesPage, RecordNumberSettingsPage, RepresentationsPage, ResourcePoolsPage, ResourceTypesPage, SecretariesPage, SecretaryDetailPage, SettingsPage, SkillsPage, SmsWalletPage, and TagsSettingsPage to use the new Switch component instead of native checkboxes. - Enhanced accessibility by ensuring the Switch component uses appropriate roles and labels. - Added tests for the new Switch component to ensure functionality and accessibility compliance. - Updated styles to accommodate the new Switch component design.
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import React, { useId } from 'react';
|
|
|
|
interface Props {
|
|
checked: boolean;
|
|
onChange: (checked: boolean) => void;
|
|
/** متن کنار سوییچ. بدون آن، `ariaLabel` الزامی است. */
|
|
label?: React.ReactNode;
|
|
/** توضیح یکخطی زیر برچسب. */
|
|
hint?: React.ReactNode;
|
|
disabled?: boolean;
|
|
/**
|
|
* چیدمان فشرده: سوییچ و متن کنار هم، بدون کشآمدن تا عرض والد.
|
|
* برای ردیف فهرست و سلول جدول. پیشفرض (`false`) ردیف تنظیمات است:
|
|
* متن راست، سوییچ چپ.
|
|
*/
|
|
inline?: boolean;
|
|
ariaLabel?: string;
|
|
id?: string;
|
|
}
|
|
|
|
/**
|
|
* سوییچ فعال/غیرفعال — تنها شکل مجاز ورودی boolean در پنل.
|
|
*
|
|
* چکباکس نیتیو در این پنل استفاده نمیشود: ظاهرش را مرورگر تعیین میکند، با تم تیره
|
|
* و توکنهای رنگی نمیخواند، و ارتفاعش زیر هدف لمسی میماند. کلاسهای `.switch` در
|
|
* `styles.css` همین حالا ظاهر درست را دارند؛ این کامپوننت فقط آنها را یکجا و با
|
|
* برچسبِ متصل بستهبندی میکند.
|
|
*
|
|
* `input` نیتیو زیر لایهٔ ظاهری میماند تا کیبورد و صفحهخوان و `:focus-visible`
|
|
* دستنخورده کار کنند.
|
|
*/
|
|
export default function Switch({
|
|
checked, onChange, label, hint, disabled, inline, ariaLabel, id,
|
|
}: Props) {
|
|
const autoId = useId();
|
|
const inputId = id ?? autoId;
|
|
|
|
const control = (
|
|
<span className="switch">
|
|
<input
|
|
id={inputId}
|
|
type="checkbox"
|
|
role="switch"
|
|
checked={checked}
|
|
disabled={disabled}
|
|
/* نامِ دسترسیپذیر صریح: متنِ `<label>` شاملِ `hint` هم میشود و نامی میسازد
|
|
که هیچکس به آن فکر نکرده. با `aria-label`، نام همان برچسبِ کوتاه میماند. */
|
|
aria-label={ariaLabel ?? (typeof label === 'string' ? label : undefined)}
|
|
onChange={(e) => onChange(e.target.checked)}
|
|
/>
|
|
<span className="switch-track"><span className="switch-thumb" /></span>
|
|
</span>
|
|
);
|
|
|
|
if (label === undefined) return control;
|
|
|
|
return (
|
|
<label
|
|
htmlFor={inputId}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: inline ? 8 : 12,
|
|
justifyContent: inline ? 'flex-start' : 'space-between',
|
|
width: inline ? undefined : '100%',
|
|
fontSize: 13.5,
|
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
opacity: disabled ? 0.6 : undefined,
|
|
}}
|
|
>
|
|
{inline ? (
|
|
<>
|
|
{control}
|
|
<span style={{ color: 'var(--text)' }}>{label}</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<span>
|
|
<span style={{ display: 'block', color: 'var(--text)' }}>{label}</span>
|
|
{hint && <span className="field-hint" style={{ marginTop: 2 }}>{hint}</span>}
|
|
</span>
|
|
{control}
|
|
</>
|
|
)}
|
|
</label>
|
|
);
|
|
}
|