قائمة منسدلة (Dropdown Menu)
قائمة بالإجراءات والإعدادات تُفتح من زر. استخدمها لعرض قائمة قصيرة بما يستطيع القارئ فعله في الصفحة، أو بعنصر واحد فيها، مثل التعديل أو المشاركة أو تسجيل الخروج.
التثبيت
npx @smicolon/fasla-ui@latest add dropdown-menuقبل إضافة أي مكوّن، شغّل الأمر init مرة واحدة في مشروعك. التفاصيل في صفحة التثبيت.
مكوّن القائمة المنسدلة مبني على مكتبة Base UI، لذلك يحتاج إلى الحزمة @base-ui/react. ويتشارك أنماط القائمة مع المكوّن select، وتستخدم صفوف مفتاح التبديل المكوّن switch. يثبّت الأمر add كليهما مع المكوّن، وكذلك أداة shadcn.
معاينة
اضغط الزر، أو انتقل إليه بمفتاح Tab ثم اضغط مفتاح Enter أو السهم السفلي.
الأنماط (variant)
للصفوف نمطان (variant) تحددهما الخاصية variant. النمط default للإجراءات المعتادة، والنمط destructive لإجراء يحذف شيئًا أو لا يمكن التراجع عنه: ضعه في آخر القائمة بعد فاصل، واكتب في عنوانه ما يفعله بالضبط. وتضع الخاصية icon أيقونة بحجم 16px قبل النص.
الحالات
يُبرز الصف حين يستقر عليه مؤشر الفأرة أو تصل إليه مفاتيح الأسهم، فمرّر المؤشر فوق الصفوف أعلاه لترى ذلك. والصف الذي يحمل الخاصية disabled يظهر باهتًا ولا يمكن الضغط عليه، لكنه يبقى في متناول مفاتيح الأسهم، ليعلن قارئ الشاشة أنه غير متاح.
العناوين والمجموعات والأيقونات
يضع المكوّن DropdownMenuLabel عنوانًا فوق مجموعة من الصفوف. وإذا وضعت العنوان وصفوفه داخل DropdownMenuGroup، يعلن قارئ الشاشة المجموعة بهذا العنوان. ويمكن أن يحمل العنوان أكثر من نص، مثل صورة رمزية مع اسم وبريد إلكتروني. ويرسم المكوّن DropdownMenuSeparator خطًا يفصل بين المجموعات.
صفوف الاختيار
المكوّن DropdownMenuCheckboxItem إعداد يُفعَّل أو يُلغى. الضغط عليه يبدّل حالته ويُبقي القائمة مفتوحة. افتراضيًا تظهر علامة الصح قبل النص حين يكون مفعّلًا، ويبقى مكانها محجوزًا حين يكون ملغى، فلا يتحرك النص. ومع indicator="switch" يعرض مفتاح التبديل في نهاية الصف الحالة بدلًا منها.
مجموعة الخيارات
يضم المكوّن DropdownMenuRadioGroup صفوفًا يُختار منها صف واحد فقط، مثل إعداد له قيم قليلة. وتدل نقطة قبل النص على الصف المختار. الضغط على صف يختاره ويُبقي القائمة مفتوحة.
القوائم الفرعية والاختصارات
يضع المكوّن DropdownMenuSub قائمة ثانية داخل القائمة. وينتهي صف DropdownMenuSubTrigger بسهم صغير يشير إلى الجهة التي تُفتح فيها القائمة الفرعية، وهي نهاية السطر: اليمين في الإنجليزية واليسار في العربية. وتُفتح عند تمرير مؤشر الفأرة أو الضغط، أو بمفتاح السهم الذي يشير إليها. ويعرض المكوّن DropdownMenuShortcut اختصار لوحة المفاتيح في نهاية الصف، بالترتيب نفسه في اللغتين. وهو يعرض المفاتيح فقط، فاستمع لها في شيفرتك.
إتاحة الوصول (accessibility)
- تمنح مكتبة Base UI القائمة الدور
menu، وكل صف الدورmenuitemأوmenuitemcheckboxأوmenuitemradio، فيعلن قارئ الشاشة نوع كل صف وهل هو مفعّل. ويُعلم العنصر المُشغِّل هل القائمة مفتوحة. - تُفتح القائمة من العنصر المُشغِّل بمفتاح Enter أو بمفتاح المسافة أو بالسهم السفلي. تنتقل بين الصفوف بمفاتيح الأسهم، ويقفز مفتاح Home إلى الصف الأول ومفتاح End إلى الأخير، وتقفز كتابة حرف إلى الصف التالي الذي يبدأ به. ويغلق مفتاح Escape القائمة ويعيد التركيز إلى العنصر المُشغِّل.
- امنح العنصر المُشغِّل اسمًا يوضّح الغرض من القائمة، وإذا كان أيقونة فقط فأضف السمة
aria-label. وضع العناوين داخلDropdownMenuGroupأوDropdownMenuRadioGroupلتسمّي صفوفها. - في صف مفتاح التبديل، الصف نفسه هو خانة الاختيار. ومفتاح التبديل يعرض الحالة فقط، لذلك يُخفى عن قارئ الشاشة ولا يستقبل الضغط ولا التركيز.
- تأخذ القائمة اتجاه الصفحة ولغتها وخطها من العنصر المُشغِّل، مع أنها تُعرض في نهاية العنصر
body. ففي الصفحة العربية تحاذي الحافة اليمنى للعنصر المُشغِّل، وتُفتح القوائم الفرعية إلى اليسار. ولا يحتاج الاتجاه إلى خاصية.
الخصائص (props)
DropdownMenu
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
open | boolean | لا يوجد | يفتح القائمة أو يغلقها حين تتحكم بها. |
defaultOpen | boolean | false | يفتح القائمة عند العرض الأول. |
onOpenChange | (open, eventDetails) => void | لا يوجد | تُستدعى عند فتح القائمة أو إغلاقها. |
modal | boolean | true | يمنع الضغط خارج القائمة وتمرير الصفحة ما دامت مفتوحة. مرّر false لتبقى الصفحة قابلة للاستخدام. |
disabled | boolean | false | يمنع فتح القائمة. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
DropdownMenuTrigger
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
render | ReactElement | لا يوجد | يستبدل الزر الافتراضي بعنصرك، مثل مكوّن زر. |
disabled | boolean | false | يمنع هذا العنصر المُشغِّل من فتح القائمة. |
openOnHover | boolean | false | يفتح القائمة أيضًا حين يستقر مؤشر الفأرة على العنصر المُشغِّل. |
delay | number | 100 | مدة الانتظار قبل الفتح بالتمرير، بالملّي ثانية. |
closeDelay | number | 0 | مدة الانتظار قبل إغلاق قائمة فُتحت بالتمرير، بالملّي ثانية. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuContent
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
side | "bottom" | "top" | "left" | "right" | "inline-start" | "inline-end" | "bottom" | جهة العنصر المُشغِّل التي تُفتح فيها القائمة. وتتبع الجهتان inline-* اتجاه القراءة. |
align | "start" | "center" | "end" | "start" | موضع القائمة على طول تلك الجهة. وتتبع القيمة start اتجاه القراءة. |
sideOffset | number | 4 | المسافة بين العنصر المُشغِّل والقائمة، بالبكسل. |
alignOffset | number | 0 | يُزيح القائمة على طول جهتها، بالبكسل. |
collisionAvoidance | { side, align, fallbackAxisSide } | لا يوجد | كيف تتجنب القائمة حافة النافذة. تنتقل تلقائيًا إلى الجهة الأخرى من العنصر المُشغِّل حين لا يتسع لها المكان، وتبقيها القيمة { side: "none" } في مكانها. |
container | HTMLElement | لا يوجد | العنصر الذي تُعرض فيه القائمة وقوائمها الفرعية، والافتراضي العنصر body. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية للقائمة، مثل العرض. |
DropdownMenuLabel
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
inset | boolean | false | يحاذي النص مع الصفوف التي فيها أيقونة أو علامة. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
variant | "default" | "destructive" | "default" | يدل النمط destructive على إجراء يحذف شيئًا أو لا يمكن التراجع عنه. |
icon | ReactNode | لا يوجد | أيقونة بحجم 16px قبل النص. |
inset | boolean | false | يحاذي النص مع الصفوف التي فيها أيقونة أو علامة. |
disabled | boolean | false | يُظهر الصف باهتًا ويمنع الضغط عليه. |
onClick | (event) => void | لا يوجد | تُستدعى عند الضغط على الصف أو اختياره من لوحة المفاتيح. |
closeOnClick | boolean | true | يغلق القائمة عند الضغط على الصف. |
label | string | لا يوجد | النص الذي تطابقه كتابة حرف، والافتراضي نص الصف. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuCheckboxItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
indicator | "check" | "switch" | "check" | كيف يُظهر الصف أنه مفعّل: علامة الصح قبل النص، أو مفتاح التبديل في نهاية الصف. |
checked | boolean | لا يوجد | هل الصف مفعّل، حين تتحكم به. |
defaultChecked | boolean | false | هل الصف مفعّل عند العرض الأول. |
onCheckedChange | (checked, eventDetails) => void | لا يوجد | تُستدعى بالحالة الجديدة عند تبديل الصف. |
disabled | boolean | false | يُظهر الصف باهتًا ويمنع الضغط عليه. |
closeOnClick | boolean | false | يغلق القائمة عند الضغط على الصف. وهو معطّل افتراضيًا، لتغيير عدة صفوف دفعة واحدة. |
label | string | لا يوجد | النص الذي تطابقه كتابة حرف، والافتراضي نص الصف. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuRadioGroup
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
value | unknown | لا يوجد | القيمة المختارة، حين تتحكم بها. |
defaultValue | unknown | لا يوجد | القيمة المختارة عند العرض الأول. |
onValueChange | (value, eventDetails) => void | لا يوجد | تُستدعى بالقيمة الجديدة عند اختيار صف. |
disabled | boolean | false | يمنع اختيار أي صف في المجموعة. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuRadioItem
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
value | unknown | لا يوجد | القيمة التي يضعها هذا الصف في مجموعته. |
disabled | boolean | false | يُظهر الصف باهتًا ويمنع الضغط عليه. |
closeOnClick | boolean | false | يغلق القائمة عند الضغط على الصف. وهو معطّل افتراضيًا، لتغيير عدة صفوف دفعة واحدة. |
label | string | لا يوجد | النص الذي تطابقه كتابة حرف، والافتراضي نص الصف. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuSub
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
open | boolean | لا يوجد | يفتح القائمة الفرعية أو يغلقها حين تتحكم بها. |
defaultOpen | boolean | false | يفتح القائمة الفرعية عند العرض الأول. |
onOpenChange | (open, eventDetails) => void | لا يوجد | تُستدعى عند فتح القائمة الفرعية أو إغلاقها. |
disabled | boolean | false | يمنع فتح القائمة الفرعية. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
DropdownMenuSubTrigger
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
icon | ReactNode | لا يوجد | أيقونة بحجم 16px قبل النص. |
inset | boolean | false | يحاذي النص مع الصفوف التي فيها أيقونة أو علامة. |
disabled | boolean | false | يُظهر الصف باهتًا ويمنع الضغط عليه. |
openOnHover | boolean | true | يفتح القائمة الفرعية حين يستقر مؤشر الفأرة على صفها. |
delay | number | 100 | مدة الانتظار قبل الفتح بالتمرير، بالملّي ثانية. |
closeDelay | number | 0 | مدة الانتظار قبل إغلاق قائمة فُتحت بالتمرير، بالملّي ثانية. |
label | string | لا يوجد | النص الذي تطابقه كتابة حرف، والافتراضي نص الصف. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية لهذا الجزء. |
DropdownMenuSubContent
| الخاصية | النوع | الافتراضي | الوصف |
|---|---|---|---|
sideOffset | number | 0 | المسافة بين الصف والقائمة الفرعية، بالبكسل. |
alignOffset | number | -5 | يُزيح القائمة الفرعية على طول الصف، بالبكسل. والقيمة الافتراضية تحاذي صفها الأول مع صف العنصر المُشغِّل. |
collisionAvoidance | { side, align, fallbackAxisSide } | لا يوجد | كيف تتجنب القائمة حافة النافذة. تنتقل تلقائيًا إلى الجهة الأخرى من العنصر المُشغِّل حين لا يتسع لها المكان، وتبقيها القيمة { side: "none" } في مكانها. |
container | HTMLElement | لا يوجد | العنصر الذي تُعرض فيه القائمة الفرعية، والافتراضي عنصر القائمة نفسها. |
children | ReactNode | لا يوجد | محتوى هذا الجزء. |
className | string | لا يوجد | أصناف CSS إضافية للقائمة، مثل العرض. |
لا يأخذ المكوّن DropdownMenuGroup ولا المكوّن DropdownMenuShortcut إلا الخاصية className مع children، ولا يأخذ المكوّن DropdownMenuSeparator إلا الخاصية className.
الاستخدام
قائمة بسيطة
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function AccountMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline" />}>فتح القائمة</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>حسابي</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
الملف الشخصي
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>الفوترة</DropdownMenuItem>
<DropdownMenuItem disabled>واجهة البرمجة</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">تسجيل الخروج</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}أيقونات وإجراء حذف
import { PencilLine, Share, Trash2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function FileActionsMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline" />}>فتح القائمة</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem icon={<PencilLine strokeWidth={1.5} />}>تعديل</DropdownMenuItem>
<DropdownMenuItem icon={<Share strokeWidth={1.5} />}>مشاركة</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive" icon={<Trash2 strokeWidth={1.5} />}>
حذف
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}صفوف الاختيار ومفتاح التبديل
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function NotificationsMenu() {
const [email, setEmail] = useState(true)
const [sms, setSms] = useState(false)
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline" />}>الإشعارات</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>أبلغني عبر</DropdownMenuLabel>
<DropdownMenuCheckboxItem checked={email} onCheckedChange={setEmail}>
البريد الإلكتروني
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem indicator="switch" checked={sms} onCheckedChange={setSms}>
رسالة نصية
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}مجموعة الخيارات
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function PanelPositionMenu() {
const [position, setPosition] = useState("top")
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline" />}>موضع اللوحة</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
<DropdownMenuLabel>موضع اللوحة</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioItem value="top">أعلى</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">أسفل</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="end">يمين</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}قائمة فرعية
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ShareMenu() {
return (
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline" />}>فتح القائمة</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem>علامة تبويب جديدة</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>مشاركة</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>البريد الإلكتروني</DropdownMenuItem>
<DropdownMenuItem>رسالة</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
)
}