شريط تحديد القيمة (Slider)

يتيح لك مكوّن شريط تحديد القيمة أن تختار قيمة، أو نطاقًا من القيم، بسحب مقبض على مسار أو بمفاتيح الأسهم. يأتي بثلاثة أحجام، ويمتلئ مساره من بداية السطر، فيتبع اتجاه القراءة في الصفحة.

التثبيت

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

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

معاينة

الأحجام

تغيّر الأحجام الثلاثة المسار وحده: ارتفاعه في الحجم sm هو 6px، وفي الحجم md هو 8px، وفي الحجم lg هو 12px. أما المقبض فقطره 16px في كل الأحجام، فيبقى ارتفاع المكوّن 16px دائمًا. ويمتد المكوّن بعرض الحاوية التي يوضع فيها.

صغير
متوسط
كبير

القيمة

يدل الجزء الممتلئ من المسار على القيمة، فهو يمتد من بداية المسار إلى المقبض. عند الحد الأدنى يقف المقبض في بداية المسار، وعند الحد الأقصى يقف في نهايته، ولا يتجاوز أيًّا من طرفيه. حدّد مدى القيم بالخاصية min والخاصية max، وحدّد مقدار الخطوة الواحدة بالخاصية step.

الحد الأدنى
المنتصف
الحد الأقصى

الحالات

التركيز حالة حقيقية، لا خاصية: انتقل إلى المقبض بمفتاح Tab. عند التركيز من لوحة المفاتيح تظهر حول المقبض حلقة بعرض 4px بدل ظله. أما الضغط أو السحب بالمؤشر فلا يُظهر الحلقة.

الحالة الافتراضية
التركيز

النطاق

مرّر مصفوفة إلى الخاصية value أو الخاصية defaultValue، مثل [25, 75]، فيظهر مقبض لكل قيمة ويمتلئ المسار بين المقبضين. وتعيد الدالة onValueChange عندئذٍ مصفوفة. وإذا سحبت أحد المقبضين إلى ما بعد الآخر دفعه أمامه، وتُبقي الخاصية minStepsBetweenValues مسافة بينهما.

التسمية والقيمة

لا يعرض مكوّن شريط تحديد القيمة أي نص. ضع فوقه تسمية ظاهرة واربطهما بالسمة aria-labelledby، واعرض القيمة الحالية بجانب التسمية، حتى لا يضطر القارئ إلى تقديرها من المسار.

مستوى الصوت40%
السعرمن 200 إلى 800 ريال

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

  • يحمل كل مقبض حقل نطاق حقيقيًا بالدور slider، فيُعلن قارئ الشاشة اسمه وقيمته والحدين الأدنى والأقصى. ومع الخاصية name ترسل حقول مخفية القيمة مع النموذج.
  • ينقلك مفتاح Tab إلى المقابض واحدًا بعد الآخر. تحرّك مفاتيح الأسهم المقبض خطوة واحدة، ويحرّكه مفتاح Page Up ومفتاح Page Down عشر خطوات، وهي قيمة الخاصية largeStep. وينقله مفتاح Home إلى الحد الأدنى، ومفتاح End إلى الحد الأقصى.
  • سمِّ المكوّن دائمًا: بالسمة aria-labelledby مع تسمية ظاهرة، أو بالسمة aria-label إن لم توجد تسمية. وفي النطاق، أعطِ كل مقبض اسمه الخاص بالدالة getAriaLabel، مثل «أدنى سعر» و«أعلى سعر».
  • يسمع قارئ الشاشة الرقم وحده. وإذا احتاج الرقم إلى وحدة، كالعملة، فأعِد النص كاملًا من الدالة getAriaValueText.
  • يتبع الاتجاه السمة dir في الصفحة، ولا توجد له خاصية. في الصفحات من اليمين إلى اليسار يمتلئ المسار من بداية السطر ويبدأ المقبض منها، ويحرّك كل سهم المقبض في الاتجاه الذي يشير إليه، فالسهم المتجه إلى نهاية السطر يرفع القيمة.

الخصائص (props)

Slider

الخاصيةالنوعالافتراضيالوصف
valuenumber | number[]لا يوجدالقيمة الحالية: رقم، أو مصفوفة في النطاق. وتُستخدم مع الدالة onValueChange.
defaultValuenumber | number[]لا يوجدالقيمة عند العرض الأول. وتعطي المصفوفة مقبضًا لكل قيمة.
onValueChange(value) => voidلا يوجدتُستدعى بالقيمة الجديدة أثناء تحرّك المقبض.
onValueCommitted(value) => voidلا يوجدتُستدعى مرة واحدة بالقيمة النهائية، عند رفع المؤشر أو تغيير القيمة بمفتاح.
size"sm" | "md" | "lg""md"ارتفاع المسار: 6 أو 8 أو 12px. وقطر المقبض 16px في كل الأحجام.
minnumber0أدنى قيمة.
maxnumber100أعلى قيمة.
stepnumber1مقدار الخطوة الواحدة، وتلتزم القيمة بمضاعفاته.
largeStepnumber10مقدار ما يحرّكه مفتاح Page Up ومفتاح Page Down.
minStepsBetweenValuesnumber0في النطاق، أقل عدد من الخطوات بين المقبضين.
aria-labelstringلا يوجدالاسم الذي يقرؤه قارئ الشاشة حين لا توجد تسمية ظاهرة.
aria-labelledbystringلا يوجدمعرّف التسمية الظاهرة التي تسمّي المكوّن.
getAriaLabel(index) => stringلا يوجدتسمّي كل مقبض في النطاق برقم ترتيبه: 0 للبداية، و1 للنهاية.
getAriaValueText(text, value, index) => stringلا يوجدتعيد النص الذي يسمعه قارئ الشاشة لقيمة المقبض.
namestringلا يوجدالاسم الذي تُرسل به القيمة مع النموذج.
formstringلا يوجدمعرّف النموذج الذي تُرسل معه القيمة، حين يكون المكوّن خارجه.
classNamestringلا يوجدأصناف إضافية للمكوّن.

الاستخدام

شريط تحديد القيمة البسيط

tsx
import { Slider } from "@/components/ui/slider"

export function VolumeSlider() {
  return <Slider name="volume" defaultValue={60} aria-label="مستوى الصوت" />
}

نطاق للسعر مع التحكّم بالقيمة

tsx
"use client"

import * as React from "react"
import { Slider } from "@/components/ui/slider"

export function PriceFilter() {
  const [price, setPrice] = React.useState<readonly number[]>([200, 800])
  return (
    <Slider
      value={price}
      onValueChange={setPrice}
      min={0}
      max={1000}
      step={50}
      getAriaLabel={(index) => (index === 0 ? "أدنى سعر" : "أعلى سعر")}
    />
  )
}

مع تسمية ظاهرة وقيمة

tsx
"use client"

import * as React from "react"
import { Slider } from "@/components/ui/slider"

export function VolumeField() {
  const id = React.useId()
  const [volume, setVolume] = React.useState(40)
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-baseline justify-between gap-4">
        <span id={id} className="text-sm font-medium">
          مستوى الصوت
        </span>
        <span className="text-sm tabular-nums text-muted-foreground">{volume}%</span>
      </div>
      <Slider value={volume} onValueChange={setVolume} aria-labelledby={id} />
    </div>
  )
}