Files
clinicpro/assets/admin/components/ui/Switch.tsx
T
hamed 0e7970d6e0 feat: replace checkboxes with Switch component for better UI consistency
- 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.
2026-08-05 16:09:46 +03:30

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>
);
}