شريط جانبي (Sidebar)
يعرض مكوّن شريط جانبي تنقّل التطبيق، وينطوي إلى أيقونات: مجموعات من الروابط لكلٍّ منها عنوان، وعناصر فرعية تحت عنصر رئيسي، والمستخدم الذي سجّل دخوله في أسفله. يأتي بحالتَي التوسيع والطيّ كما في Figma، مع بطاقة خطة اختيارية.
التثبيت
npx @smicolon/fasla-ui@latest add sidebarقبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.
مكوّن الشريط الجانبي مبني على مكتبة Base UI، لذلك يحتاج إلى الحزمة @base-ui/react، ويستخدم المكوّن avatar والمكوّن tooltip. يثبّت الأمر add كليهما مع المكوّن، وكذلك أداة shadcn.
معاينة
اطوِ الشريط الجانبي من الزر في رأسه، ثم ضع المؤشر على أيقونة لترى تسميتها.
التوسيع والطيّ
الشريط الجانبي موسَّعًا بتسمياته وعناوين أقسامه، ومطويًا إلى عمود من الأيقونات.
الحالات
الحالات الأربع للعنصر والعنصر الفرعي: الحالة الافتراضية، ومرور المؤشر، والتحديد، والتركيز.
العناصر والعناصر الفرعية
عناصر بأيقونة ونقطة إشعار، وعناصر فرعية تُفتح وتُغلق تحت العنصر الرئيسي.
بطاقة الخطة
بطاقة الخطة الاختيارية، وتظهر فوق الملف الشخصي أسفل الشريط الجانبي.
إتاحة الوصول (accessibility)
- الشريط الجانبي عنصر
aside، والمكوّنSidebarContentعنصرnav. أعطِ العنصرnavالخاصيةaria-labelإذا كانت في الصفحة منطقة تنقّل أخرى. - تعيّن الخاصية
selectedالقيمةaria-current="page"على رابط العنصر، فيعلن قارئ الشاشة أنه الصفحة الحالية. - اسم الزر في رأس الشريط «طيّ الشريط الجانبي» أو «توسيع الشريط الجانبي» في الصفحة العربية، و«Collapse sidebar» أو «Expand sidebar» في الإنجليزية، ويعلن حالته بالخاصية
aria-expanded. ويمكنك تغيير الاسمين بالخاصيةcollapseLabelوالخاصيةexpandLabel. - العنصر الذي له عناصر فرعية زرٌّ يحمل الخاصية
aria-expanded. يفتحه مفتاح Enter ومفتاح المسافة ويغلقانه. - حين يكون الشريط مطويًا، تحتفظ كل أيقونة بتسميتها اسمًا لدى قارئ الشاشة. ويظهر التلميح عند مرور المؤشر وعند التركيز بلوحة المفاتيح، ويغلقه مفتاح Escape.
- يرسم التركيز بلوحة المفاتيح حلقة التركيز نفسها التي في مكوّن حقل الاختيار: حدّ بلون
ringمع هالة عرضها 3px بلونringبشفافية 50%. وفي وضع الألوان القسرية يرسم إطارًا بلونHighlightعرضه 2px بدلًا منها. - اسم نقطة الإشعار «جديد» في الصفحة العربية و«New» في الإنجليزية. عيّن الخاصية
badgeLabelلتقول أكثر، مثل عدد التحديثات. - يتغيّر عرض الشريط وتُفتح العناصر الفرعية وتُغلق بحركة مدتها 180ms. وإذا فعّل المستخدم إعداد تقليل الحركة، تتغيّر فورًا.
- يأخذ الشريط الجانبي اتجاه الصفحة. ففي الصفحة العربية يقع على اليمين وتنعكس كل المسافات، وتنقلب أيقونة زر الطيّ والأسهم. ولا يحتاج الاتجاه إلى خاصية.
الخصائص (props)
Sidebar
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
collapsed | boolean | لا يوجد | يطوي الشريط فلا تظهر إلا الأيقونات، عند التحكّم بالقيمة. |
defaultCollapsed | boolean | false | يبدأ الشريط مطويًا، حين لا تتحكّم بالقيمة. |
onCollapsedChange | (collapsed) => void | لا يوجد | تُستدعى بالحالة الجديدة عند طيّ الشريط أو توسيعه. |
children | ReactNode | لا يوجد | الرأس والمحتوى وبطاقة الخطة والتذييل، بهذا الترتيب. وتختفي بطاقة الخطة ما دام الشريط مطويًا. |
className | string | لا يوجد | أصناف CSS إضافية للعنصر aside. |
SidebarHeader
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
logo | ReactNode | لا يوجد | الشعار كاملًا، ويظهر والشريط موسَّع. في Figma عرض الشعار الإنجليزي 90px وارتفاع الشعار العربي 28px. |
logoIcon | ReactNode | لا يوجد | رمز الشعار وحده، ويظهر والشريط مطوي. قياسه في Figma هو 28px. |
collapseLabel | string | "Collapse sidebar" | اسم زر الطيّ لدى قارئ الشاشة والشريط موسَّع. |
expandLabel | string | "Expand sidebar" | اسم زر التوسيع لدى قارئ الشاشة والشريط مطوي. |
className | string | لا يوجد | أصناف CSS إضافية للرأس. |
SidebarContent
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
aria-label | string | لا يوجد | يسمّي منطقة التنقّل. |
children | ReactNode | لا يوجد | مكوّنات SidebarGroup. |
className | string | لا يوجد | أصناف CSS إضافية للعنصر nav. |
SidebarGroup
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
label | ReactNode | لا يوجد | عنوان القسم. يتحوّل إلى خط فاصل عند طيّ الشريط، ويبقى اسم المجموعة لدى قارئ الشاشة. |
children | ReactNode | لا يوجد | مكوّنات SidebarItem. |
className | string | لا يوجد | أصناف CSS إضافية للمجموعة. |
SidebarItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
label | ReactNode | لا يوجد | نص العنصر. يصبح التلميح واسم العنصر لدى قارئ الشاشة عند طيّ الشريط. |
icon | ReactNode | لا يوجد | أيقونة بقياس 16px قبل التسمية. لا تظهر إلا هي عند طيّ الشريط، لذلك أعطِ كل عنصر أيقونة. |
selected | boolean | false | يعلّم العنصر بأنه الصفحة الحالية. عيّنها لعنصر واحد فقط. |
badge | boolean | false | يعرض نقطة الإشعار في نهاية الصف. |
badgeLabel | string | "New" | اسم النقطة لدى قارئ الشاشة. |
href | string | لا يوجد | يجعل العنصر رابطًا إلى هذا العنوان. |
render | ReactElement | لا يوجد | يعرض هذا العنصر بدل الرابط، مثل المكوّن Link في أداة التوجيه لديك. |
open | boolean | لا يوجد | يُظهر العناصر الفرعية أو يخفيها، عند التحكّم بالقيمة. |
defaultOpen | boolean | لا يوجد | يُظهر العناصر الفرعية عند العرض الأول. ويكون مفتوحًا افتراضيًا إذا كان أحدها محدَّدًا. |
onOpenChange | (open) => void | لا يوجد | تُستدعى عند فتح العناصر الفرعية أو إغلاقها. |
onClick | (event) => void | لا يوجد | تُستدعى عند الضغط على العنصر. |
children | ReactNode | لا يوجد | مكوّنات SidebarSubItem، بمستوى واحد فقط. إذا وُجد أيٌّ منها، فتح العنصر عناصره الفرعية وأغلقها بدل أن يكون رابطًا. |
className | string | لا يوجد | أصناف CSS إضافية لرابط العنصر أو زره. |
SidebarSubItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
label | ReactNode | لا يوجد | نص العنصر الفرعي. |
selected | boolean | false | يعلّم العنصر بأنه الصفحة الحالية. ويُفتح العنصر الرئيسي ليُظهره، ويظهر محدَّدًا عند طيّ الشريط. |
badge | boolean | false | يعرض نقطة الإشعار في نهاية الصف. |
badgeLabel | string | "New" | اسم النقطة لدى قارئ الشاشة. |
href | string | لا يوجد | يجعل العنصر الفرعي رابطًا إلى هذا العنوان. |
render | ReactElement | لا يوجد | يعرض هذا العنصر بدل الرابط، مثل المكوّن Link في أداة التوجيه لديك. |
onClick | (event) => void | لا يوجد | تُستدعى عند الضغط على العنصر الفرعي. |
className | string | لا يوجد | أصناف CSS إضافية لرابط العنصر الفرعي أو زره. |
SidebarPlanCard
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
title | ReactNode | لا يوجد | اسم الخطة. |
description | ReactNode | لا يوجد | سطر تحت الاسم. |
footer | ReactNode | لا يوجد | السطر تحت الخط الفاصل. |
footerIcon | ReactNode | لا يوجد | أيقونة بقياس 16px قبل footer. |
className | string | لا يوجد | أصناف CSS إضافية للبطاقة. |
SidebarFooter
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
children | ReactNode | لا يوجد | المكوّن SidebarProfile. |
className | string | لا يوجد | أصناف CSS إضافية للتذييل. |
SidebarProfile
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
name | string | لا يوجد | اسم الشخص. يصبح اسم الزر لدى قارئ الشاشة عند طيّ الشريط. |
description | ReactNode | لا يوجد | سطر تحت الاسم، مثل مساحة العمل. |
avatar | AvatarProps | لا يوجد | خصائص الصورة الرمزية، مثل src. تظهر بقياس 32px. |
onClick | (event) => void | لا يوجد | تُستدعى عند الضغط على الملف الشخصي، مثلًا لفتح قائمة الحساب. |
className | string | لا يوجد | أصناف CSS إضافية لزر الملف الشخصي. |
الاستخدام
شريط جانبي لتطبيق
import { LayoutDashboardIcon, SettingsIcon, ShoppingCartIcon } from "lucide-react"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarItem,
SidebarProfile,
SidebarSubItem,
} from "@/components/ui/sidebar"
export function AppSidebar() {
return (
<Sidebar>
<SidebarHeader
logo={<img src="/logo.svg" alt="فاصلة" className="h-7" />}
logoIcon={<img src="/logo-icon.svg" alt="فاصلة" className="size-7" />}
/>
<SidebarContent aria-label="الرئيسية">
<SidebarGroup label="المتجر">
<SidebarItem icon={<LayoutDashboardIcon strokeWidth={1.5} />} label="لوحة التحكم" href="/dashboard" />
<SidebarItem icon={<ShoppingCartIcon strokeWidth={1.5} />} label="الطلبات">
<SidebarSubItem label="كل الطلبات" href="/orders" selected />
<SidebarSubItem label="المرتجعات" href="/orders/returns" />
</SidebarItem>
</SidebarGroup>
<SidebarGroup label="الحساب">
<SidebarItem icon={<SettingsIcon strokeWidth={1.5} />} label="الإعدادات" href="/settings" />
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarProfile name="نادية رحمن" description="فاصلة" avatar={{ src: "/avatar.png" }} />
</SidebarFooter>
</Sidebar>
)
}التحكّم بحالة الطيّ
"use client"
import { useState } from "react"
import { LayoutDashboardIcon } from "lucide-react"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarHeader,
SidebarItem,
} from "@/components/ui/sidebar"
export function CollapsibleSidebar() {
// Keep it in your layout state, or in a cookie, to remember it between pages.
const [collapsed, setCollapsed] = useState(false)
return (
<Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
<SidebarHeader logo={<img src="/logo.svg" alt="فاصلة" className="h-7" />} />
<SidebarContent>
<SidebarGroup label="المتجر">
<SidebarItem icon={<LayoutDashboardIcon strokeWidth={1.5} />} label="لوحة التحكم" href="/dashboard" />
</SidebarGroup>
</SidebarContent>
</Sidebar>
)
}الربط بأداة التوجيه
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LayoutDashboardIcon, ShoppingCartIcon } from "lucide-react"
import { Sidebar, SidebarContent, SidebarGroup, SidebarItem } from "@/components/ui/sidebar"
const links = [
{ href: "/dashboard", label: "لوحة التحكم", icon: LayoutDashboardIcon },
{ href: "/orders", label: "الطلبات", icon: ShoppingCartIcon },
]
export function RouterSidebar() {
const pathname = usePathname()
return (
<Sidebar>
<SidebarContent>
<SidebarGroup label="المتجر">
{links.map(({ href, label, icon: Icon }) => (
<SidebarItem
key={href}
label={label}
icon={<Icon strokeWidth={1.5} />}
render={<Link href={href} />}
selected={pathname === href}
/>
))}
</SidebarGroup>
</SidebarContent>
</Sidebar>
)
}إضافة بطاقة الخطة
import { CalendarIcon, LayoutDashboardIcon } from "lucide-react"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarItem,
SidebarPlanCard,
SidebarProfile,
} from "@/components/ui/sidebar"
export function SidebarWithPlan() {
return (
<Sidebar>
<SidebarContent>
<SidebarGroup label="المتجر">
<SidebarItem icon={<LayoutDashboardIcon strokeWidth={1.5} />} label="لوحة التحكم" href="/dashboard" />
</SidebarGroup>
</SidebarContent>
<SidebarPlanCard
title="الخطة الاحترافية"
description="3 أعضاء في الفريق"
footer="التجديد التالي: 10 مارس"
footerIcon={<CalendarIcon strokeWidth={1.5} />}
/>
<SidebarFooter>
<SidebarProfile name="نادية رحمن" description="فاصلة" />
</SidebarFooter>
</Sidebar>
)
}