خانة الاختيار (Checkbox)

يتيح لك مكوّن خانة الاختيار (Checkbox) تحديد خيار أو إلغاء تحديده.

التثبيت

bash
npx @smicolon/fasla-ui@latest add checkbox

قبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.

معاينة

الأنماط (variant)

تأتي خانة الاختيار بنمطين (variant) كما في Figma. النمط (variant) default هو الافتراضي، ويعرض المربع مع تسميته فقط. أما النمط (variant) layout فيضعهما داخل بطاقة بإطار، ويمكن النقر في أي مكان منها، وتتلوّن خلفيتها عند حالة التمرير (hover).

default
layout

الأحجام

تتوفر خانة الاختيار بثلاثة أحجام، والحجم الافتراضي هو md. يبلغ طول ضلع المربع 16 أو 20 أو 24px، ويبقى حجم التسمية ثابتًا.

default
layout

الحالات

تظهر علامة الصح عند التحديد، وتظهر الشرطة عند التحديد الجزئي (indeterminate)، ولا تنعكس أيّ منهما في الاتجاه من اليمين إلى اليسار. تأتي حالتا التمرير (hover) والتركيز (focus) من CSS مباشرة، لا من خاصية. مرّر المؤشر فوق خانة الاختيار، أو انتقل إليها بمفتاح Tab، لتراهما. وعند التعطيل يظهر المكوّن كله باهتًا، بما فيه التسمية والوصف.

default
layout

إتاحة الوصول (accessibility)

  • تعمل خانة الاختيار مع لوحة المفاتيح تلقائيًا: تتبدّل حالتها بمفتاح المسافة، وتُرسَل مع النموذج دون شيفرة إضافية، لأنها مبنية على عنصر <input type="checkbox"> الأصلي.
  • تضبط الخاصية indeterminate حالة العنصر نفسه، فيعلن قارئ الشاشة أن الخانة محددة جزئيًا. وتزول هذه الحالة عند النقر كما في المتصفح، لذا حدّث الخاصية من حالة تطبيقك.
  • يأخذ قارئ الشاشة اسم الخانة من نص label وحده، ويقرأ نص description بعده لأنه مرتبط بالخانة عبر aria-describedby. أما المربع المرسوم فمخفي عن التقنيات المساعدة.
  • أضف aria-label لخانة الاختيار التي تظهر بلا label، ليعرف قارئ الشاشة ما تتحكم فيه.
  • تتبع خانة الاختيار اتجاه الصفحة المحدد في السمة (attribute) dir، فيبقى المربع في بداية السطر في الاتجاهين.

الخصائص (props)

الخاصيةالنوعالافتراضيالوصف
variant"default" | "layout""default"المربع وحده، أو داخل بطاقة بإطار.
size"sm" | "md" | "lg""md"حجم خانة الاختيار.
checkedbooleanلا يوجدحالة التحديد، وتُستخدم مع onChange.
defaultCheckedbooleanfalseالحالة الأولى عندما لا تتحكم بالقيمة.
indeterminatebooleanfalseيعرض شرطة لمجموعة محددة جزئيًا.
disabledbooleanfalseيعطّل الخانة ويُظهرها باهتة.
labelstringلا يوجدنص التسمية.
descriptionstringلا يوجدسطر ثانوي تحت التسمية.
namestringلا يوجدالاسم الذي يُرسَل مع النموذج.
valuestringلا يوجدالقيمة التي تُرسَل عند التحديد.
onChange(event) => voidلا يوجدتُستدعى عند تغيير حالة الخانة.
classNamestringلا يوجدأصناف (class) إضافية للمكوّن.

الاستخدام

خانة اختيار بسيطة

tsx
"use client"

import { Checkbox } from "@/components/ui/checkbox"

export function TermsCheckbox() {
  return <Checkbox name="terms" aria-label="أوافق على الشروط والأحكام" />
}

مع تسمية

tsx
"use client"

import { Checkbox } from "@/components/ui/checkbox"

export function TermsCheckbox() {
  return <Checkbox name="terms" label="أوافق على الشروط والأحكام" />
}

مع وصف

tsx
"use client"

import { Checkbox } from "@/components/ui/checkbox"

export function MarketingCheckbox() {
  return (
    <Checkbox
      name="marketing"
      label="رسائل العروض"
      description="تصلك رسائل عن المنتجات والميزات الجديدة"
    />
  )
}

داخل بطاقة بإطار

tsx
"use client"

import { Checkbox } from "@/components/ui/checkbox"

export function SecurityCheckbox() {
  return <Checkbox name="security" variant="layout" label="تنبيهات الأمان" />
}

أحجام مختلفة

tsx
"use client"

import { Checkbox } from "@/components/ui/checkbox"

export function CheckboxSizes() {
  return (
    <div className="flex flex-col gap-3">
      <Checkbox size="sm" label="صغير" />
      <Checkbox size="md" label="متوسط" />
      <Checkbox size="lg" label="كبير" />
    </div>
  )
}

تحديد الكل مع خانة رئيسية محددة جزئيًا

tsx
"use client"

import { useState } from "react"
import { Checkbox } from "@/components/ui/checkbox"

const files = ["التقرير السنوي", "الفواتير", "العقد"]

export function SelectAllFiles() {
  const [selected, setSelected] = useState<string[]>([])
  const all = selected.length === files.length

  return (
    <div className="flex flex-col gap-3">
      <Checkbox
        label="تحديد كل الملفات"
        checked={all}
        indeterminate={selected.length > 0 && !all}
        onChange={(e) => setSelected(e.target.checked ? files : [])}
      />
      <div className="flex flex-col gap-3 ps-8">
        {files.map((file) => (
          <Checkbox
            key={file}
            size="sm"
            label={file}
            checked={selected.includes(file)}
            onChange={(e) =>
              setSelected((prev) =>
                e.target.checked ? [...prev, file] : prev.filter((f) => f !== file)
              )
            }
          />
        ))}
      </div>
    </div>
  )
}

عدد الملفات المحددة: 1