عارض الشرائح (Carousel)
عارض شرائح مبني على scroll-snap مع مؤشرات الشرائح: نقطة لكل شريحة، وتستطيل النقطة النشطة إلى حبة ممدودة.
التثبيت
npx @smicolon/fasla-ui@latest add carouselقبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.
معاينة
المسار شريط تمرير أصلي مثبّت بخاصية scroll-snap، فيتحرك بالسحب ولوحة التتبّع والنقاط على حد سواء، وتتبع النقطة النشطة تمريرك في أي اتجاه.
الأنماط (variant)
محور No. of items في Figma هو ببساطة عدد الشرائح التي تعرضها: صف النقاط يتبع عدد الشرائح، والحبة الممدودة هي Active Item.
234الحالات
النقطة النشطة حبة ممدودة بعرض 24px بلون primary، والبقية دوائر 8px بلون muted-foreground، ويتحرك الامتداد بينهما ما لم تكن الحركة مخفّضة. تبدأ defaultActiveIndex من شريحة لاحقة بقفزة فورية دون تمرير.
مؤشرات الشرائح وحدها
إذا أعطيت CarouselDots قيمة count فلن يحتاج إلى عارض شرائح حوله: يعرض activeIndex ويُبلغ عن كل ضغطة عبر onActiveIndexChange، فيمكن لمعرض صور أو لخطوات متتابعة أن يتحكم فيه بحالته الخاصة.
إتاحة الوصول (accessibility)
- الجذر منطقة
regionمعaria-roledescriptionبقيمةcarousel. مرّر دائمًاaria-labelلتسميته. - كل شريحة مجموعة
groupتُسمّى بالأرقام "2 / 3"، فلا يحتاج الاسم إلى ترجمة؛ مرّرaria-labelعلى الشريحة لتسميتها بمحتواها. - مؤشرات الشرائح أزرار حقيقية: يصل إليها مفتاح Tab، وتحمل النقطة النشطة
aria-current، وتعيدlabelتسميتها بلغتك. - المسار محطة تركيز، ومفاتيح الأسهم تنقل شريحة واحدة من أي موضع في المكوّن وفق اتجاه النص، فالمفتاح الذي يشير إلى الأمام على الشاشة يتقدم دائمًا. وينتقل بمفتاح Home إلى أول شريحة وبمفتاح End إلى آخرها.
- يرسم التركيز من لوحة المفاتيح حلقة بعرض 2px، على بعد 2px من النقطة أو من المسار، باللون
ring. وتتبع الحلقة على النقطة شكل الدائرة أو الحبة الممدودة، لا المساحة الأكبر التي تقبل الضغط. - التمرير السلس يأتي من CSS وحدها، لذا تجعل
prefers-reduced-motionكل حركة فورية دون أي JavaScript. - الاتجاه يُورَث من السمة
dirوليس خاصية. المسار والتثبيت والنقاط كلها تتبع اتجاه السطر.
الخصائص (props)
Carousel
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
activeIndex | number | لا يوجد | الشريحة النشطة عند التحكم بالحالة من الخارج. |
defaultActiveIndex | number | 0 | الشريحة المعروضة في البداية. |
onActiveIndexChange | (index) => void | لا يوجد | تُستدعى عند تغيّر الشريحة النشطة، سواء من النقاط أو من التمرير اليدوي. |
aria-label | string | لا يوجد | يسمّي المكوّن للتقنيات المساعدة. مرّره دائمًا، أو مرّر aria-labelledby بدلًا منه. |
children | ReactNode | لا يوجد | المسار CarouselContent ومؤشرات الشرائح CarouselDots. |
className | string | لا يوجد | أصناف إضافية للجزء. |
CarouselContent
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
children | ReactNode | لا يوجد | الشرائح، لكل شريحة CarouselItem. |
className | string | لا يوجد | أصناف إضافية للجزء. |
CarouselItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
aria-label | string | لا يوجد | يسمّي الشريحة بمحتواها. والاسم الافتراضي موضعها، مثل "2 / 3". |
children | ReactNode | لا يوجد | محتوى الشريحة. |
className | string | لا يوجد | أصناف إضافية. بدّل basis-full لعرض أكثر من شريحة في المرة الواحدة. |
CarouselDots
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
count | number | لا يوجد | عدد النقاط، عند استخدام المؤشرات وحدها. اتركه داخل عارض الشرائح، فهو يعدّ شرائحه بنفسه. |
activeIndex | number | 0 | النقطة النشطة، عند استخدام المؤشرات وحدها. |
onActiveIndexChange | (index) => void | لا يوجد | تُستدعى برقم النقطة عند الضغط عليها، عند استخدام المؤشرات وحدها. |
label | (index, count) => string | لا يوجد | تسمّي كل نقطة بلغتك. والاسم الافتراضي موضعها، مثل "2 / 3". |
className | string | لا يوجد | أصناف إضافية للجزء. |
الاستخدام
شرائح مع مؤشراتها
import {
Carousel,
CarouselContent,
CarouselDots,
CarouselItem,
} from "@/components/ui/carousel"
export function CityGuide() {
return (
<Carousel aria-label="دليل المدينة" className="space-y-4">
<CarouselContent>
<CarouselItem>البلدة القديمة عند الفجر</CarouselItem>
<CarouselItem>سوق الميناء</CarouselItem>
<CarouselItem>الكورنيش عند الغروب</CarouselItem>
</CarouselContent>
<CarouselDots />
</Carousel>
)
}البدء من شريحة لاحقة
import {
Carousel,
CarouselContent,
CarouselDots,
CarouselItem,
} from "@/components/ui/carousel"
export function CityGuide() {
return (
<Carousel aria-label="دليل المدينة" defaultActiveIndex={2} className="space-y-4">
<CarouselContent>
<CarouselItem>البلدة القديمة عند الفجر</CarouselItem>
<CarouselItem>سوق الميناء</CarouselItem>
<CarouselItem>الكورنيش عند الغروب</CarouselItem>
</CarouselContent>
<CarouselDots />
</Carousel>
)
}مؤشرات الشرائح وحدها
"use client"
import * as React from "react"
import { CarouselDots } from "@/components/ui/carousel"
const PHOTOS = ["البلدة القديمة عند الفجر", "سوق الميناء", "الكورنيش عند الغروب"]
export function PhotoStepper() {
const [index, setIndex] = React.useState(0)
return (
<div className="flex flex-col gap-4">
<p className="text-sm font-medium">{PHOTOS[index]}</p>
<CarouselDots count={PHOTOS.length} activeIndex={index} onActiveIndexChange={setIndex} />
</div>
)
}التحكم بالحالة
"use client"
import * as React from "react"
import {
Carousel,
CarouselContent,
CarouselDots,
CarouselItem,
} from "@/components/ui/carousel"
export function CityGuide() {
const [index, setIndex] = React.useState(0)
return (
<Carousel
aria-label="دليل المدينة"
activeIndex={index}
onActiveIndexChange={setIndex}
className="space-y-4"
>
<CarouselContent>
<CarouselItem>البلدة القديمة عند الفجر</CarouselItem>
<CarouselItem>سوق الميناء</CarouselItem>
<CarouselItem>الكورنيش عند الغروب</CarouselItem>
</CarouselContent>
<CarouselDots />
</Carousel>
)
}