Menubar
จาก shadcnแถบเมนูแนวนอนแบบโปรแกรมสำนักงาน เช่น ไฟล์ มุมมอง ที่เปิดเมนูย่อยรายการ พร้อมรายการกาติ๊กและวิทยุสำหรับตัวเลือกมุมมอง
ติดตั้ง
Terminal
pnpm dlx shadcn@latest add @whereui/menubarต้องผูก registry และติดตั้ง @whereui/theme นำหน้าเสมอเมื่อติดตั้งครั้งแรก
การใช้งาน
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarTrigger } from "@/components/ui/menubar"<Menubar> <MenubarMenu> <MenubarTrigger>ไฟล์</MenubarTrigger> <MenubarContent> <MenubarItem>สร้างคำขอใหม่</MenubarItem> </MenubarContent> </MenubarMenu></Menubar>ข้อควรรู้
- เมนูแต่ละอันใช้คีย์บอร์ดได้เต็มรูปแบบ: ลูกศรซ้าย–ขวาสลับเมนู, ขึ้น–ลงเลื่อนรายการ, Esc ปิด
- บนจอแคบควรใส่ overflow-x-auto ให้แถบเลื่อนได้ หรือพิจารณาใช้ DropdownMenu แทน
ซอร์สโค้ด
components/ui/menubar.tsx
"use client"import * as React from "react"import { Menu as MenuPrimitive } from "@base-ui/react/menu"import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"import { cn } from "cn"import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"import { CheckIcon } from "lucide-react"function Menubar({ className, ...props }: MenubarPrimitive.Props) { return ( <MenubarPrimitive data-slot="menubar" className={cn( "flex h-8 items-center gap-0.5 rounded-lg border p-[3px]", className )} {...props} /> )}function MenubarMenu({ ...props }: React.ComponentProps<typeof DropdownMenu>) { return <DropdownMenu data-slot="menubar-menu" {...props} />}function MenubarGroup({ ...props}: React.ComponentProps<typeof DropdownMenuGroup>) { return <DropdownMenuGroup data-slot="menubar-group" {...props} />}function MenubarPortal({ ...props}: React.ComponentProps<typeof DropdownMenuPortal>) { return <DropdownMenuPortal data-slot="menubar-portal" {...props} />}function MenubarTrigger({ className, ...props}: React.ComponentProps<typeof DropdownMenuTrigger>) { return ( <DropdownMenuTrigger data-slot="menubar-trigger" className={cn( "flex items-center rounded-sm px-1.5 py-[2px] text-sm font-medium outline-hidden select-none hover:bg-muted aria-expanded:bg-muted", className )} {...props} /> )}function MenubarContent({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props}: React.ComponentProps<typeof DropdownMenuContent>) { return ( <DropdownMenuContent data-slot="menubar-content" align={align} alignOffset={alignOffset} sideOffset={sideOffset} className={cn("min-w-36 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95", className )} {...props} /> )}function MenubarItem({ className, inset, variant = "default", ...props}: React.ComponentProps<typeof DropdownMenuItem>) { return ( <DropdownMenuItem data-slot="menubar-item" data-inset={inset} data-variant={variant} className={cn( "group/menubar-item gap-1.5 rounded-md px-1.5 py-1 text-sm focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive!", className )} {...props} /> )}function MenubarCheckboxItem({ className, children, checked, inset, ...props}: MenuPrimitive.CheckboxItem.Props & { inset?: boolean}) { return ( <MenuPrimitive.CheckboxItem data-slot="menubar-checkbox-item" data-inset={inset} className={cn( "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", className )} checked={checked} {...props} > <span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4"> <MenuPrimitive.CheckboxItemIndicator> <CheckIcon /> </MenuPrimitive.CheckboxItemIndicator> </span> {children} </MenuPrimitive.CheckboxItem> )}function MenubarRadioGroup({ ...props}: React.ComponentProps<typeof DropdownMenuRadioGroup>) { return <DropdownMenuRadioGroup data-slot="menubar-radio-group" {...props} />}function MenubarRadioItem({ className, children, inset, ...props}: MenuPrimitive.RadioItem.Props & { inset?: boolean}) { return ( <MenuPrimitive.RadioItem data-slot="menubar-radio-item" data-inset={inset} className={cn( "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-1.5 pl-7 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} {...props} > <span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center [&_svg:not([class*='size-'])]:size-4"> <MenuPrimitive.RadioItemIndicator> <CheckIcon /> </MenuPrimitive.RadioItemIndicator> </span> {children} </MenuPrimitive.RadioItem> )}function MenubarLabel({ className, inset, ...props}: React.ComponentProps<typeof DropdownMenuLabel> & { inset?: boolean}) { return ( <DropdownMenuLabel data-slot="menubar-label" data-inset={inset} className={cn( "px-1.5 py-1 text-sm font-medium data-inset:pl-7", className )} {...props} /> )}function MenubarSeparator({ className, ...props}: React.ComponentProps<typeof DropdownMenuSeparator>) { return ( <DropdownMenuSeparator data-slot="menubar-separator" className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} /> )}function MenubarShortcut({ className, ...props}: React.ComponentProps<typeof DropdownMenuShortcut>) { return ( <DropdownMenuShortcut data-slot="menubar-shortcut" className={cn( "ml-auto text-xs tracking-widest text-muted-foreground group-focus/menubar-item:text-accent-foreground", className )} {...props} /> )}function MenubarSub({ ...props}: React.ComponentProps<typeof DropdownMenuSub>) { return <DropdownMenuSub data-slot="menubar-sub" {...props} />}function MenubarSubTrigger({ className, inset, ...props}: React.ComponentProps<typeof DropdownMenuSubTrigger> & { inset?: boolean}) { return ( <DropdownMenuSubTrigger data-slot="menubar-sub-trigger" data-inset={inset} className={cn( "gap-1.5 rounded-md px-1.5 py-1 text-sm focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4", className )} {...props} /> )}function MenubarSubContent({ className, ...props}: React.ComponentProps<typeof DropdownMenuSubContent>) { return ( <DropdownMenuSubContent data-slot="menubar-sub-content" className={cn("min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )} {...props} /> )}export { Menubar, MenubarPortal, MenubarMenu, MenubarTrigger, MenubarContent, MenubarGroup, MenubarSeparator, MenubarLabel, MenubarItem, MenubarShortcut, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarSub, MenubarSubTrigger, MenubarSubContent,}