Sidebar
沿应用一侧纵向排列的导航栏:黑底带斜线纹理,各行与 Dropdown menu 相同,当前页填充强调色。
安装
# once per project: where @zzzui items come frompnpm dlx shadcn@latest registry add @zzzui=https://zzzui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zzzui/sidebar示例
可收起为图标栏的应用侧边栏
Board
Press the button or ⌘B / Ctrl+B to fold the sidebar to its icons.
import { ArrowRightIcon, ExternalIcon, InfoIcon, LockIcon, SearchIcon,} from "@/components/zero/icons";import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarSeparator, SidebarTrigger,} from "@/components/zero/sidebar";const jobs = [ { label: "Board", icon: SearchIcon, count: 12, active: true }, { label: "Taken", icon: ArrowRightIcon, count: 3 }, { label: "Locked", icon: LockIcon },];export default function SidebarApp() { return ( // A box instead of a whole page: the sidebar's container is absolute here, not fixed. <SidebarProvider className="relative h-[26rem] min-h-0 w-full overflow-hidden border-(length:--zero-stroke-thin) border-zero-line"> <Sidebar collapsible="icon" className="absolute h-full"> <SidebarHeader> <div className="flex items-center gap-3 px-3 py-2"> <span className="h-5 w-3 shrink-0 -skew-x-(--zero-slant) bg-zero-accent" /> <span className="truncate font-black font-zero text-zero-lg italic group-data-[collapsible=icon]/sidebar:hidden"> Dispatch </span> </div> </SidebarHeader> <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Jobs</SidebarGroupLabel> <SidebarMenu> {jobs.map(({ label, icon: Icon, count, active }) => ( <SidebarMenuItem key={label}> <SidebarMenuButton isActive={active} tooltip={label}> <Icon /> <span>{label}</span> </SidebarMenuButton> {count ? <SidebarMenuBadge>{count}</SidebarMenuBadge> : null} </SidebarMenuItem> ))} <SidebarMenuItem> <SidebarMenuButton tooltip="Districts"> <ExternalIcon /> <span>Districts</span> </SidebarMenuButton> <SidebarMenuSub> <SidebarMenuSubItem> <SidebarMenuSubButton href="#harbor" isActive> Harbor </SidebarMenuSubButton> </SidebarMenuSubItem> <SidebarMenuSubItem> <SidebarMenuSubButton href="#rail-yards"> Rail yards </SidebarMenuSubButton> </SidebarMenuSubItem> </SidebarMenuSub> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> </SidebarContent> <SidebarSeparator /> <SidebarFooter> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton tooltip="Help"> <InfoIcon /> <span>Help</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarFooter> </Sidebar> <SidebarInset render={<div />} className="gap-4 p-4"> <div className="flex items-center gap-3"> <SidebarTrigger /> <span className="font-black font-zero text-zero-lg italic"> Board </span> </div> <p className="font-medium font-zero-text text-zero-sm text-zero-text-2"> Press the button or ⌘B / Ctrl+B to fold the sidebar to its icons. </p> </SidebarInset> </SidebarProvider> );}贴边、悬浮与内嵌
Board
The floating sidebar.
import { useState } from "react";import { InfoIcon, LockIcon, SearchIcon } from "@/components/zero/icons";import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger,} from "@/components/zero/sidebar";import { ToggleGroup, ToggleGroupItem } from "@/components/zero/toggle-group";type Variant = "sidebar" | "floating" | "inset";const rows = [ { label: "Board", icon: SearchIcon, active: true }, { label: "Locked", icon: LockIcon }, { label: "Help", icon: InfoIcon },];export default function SidebarVariants() { const [variant, setVariant] = useState<Variant>("floating"); return ( <div className="grid w-full gap-4"> <ToggleGroup size="sm" variant="secondary" value={[variant]} onValueChange={(next) => next[0] && setVariant(next[0] as Variant)} > <ToggleGroupItem value="sidebar">Sidebar</ToggleGroupItem> <ToggleGroupItem value="floating">Floating</ToggleGroupItem> <ToggleGroupItem value="inset">Inset</ToggleGroupItem> </ToggleGroup> {/* A box instead of a whole page: the sidebar's container is absolute here, not fixed. */} <SidebarProvider className="relative h-80 min-h-0 w-full overflow-hidden border-(length:--zero-stroke-thin) border-zero-line"> <Sidebar variant={variant} collapsible="icon" className="absolute h-full" > <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Jobs</SidebarGroupLabel> <SidebarMenu> {rows.map(({ label, icon: Icon, active }) => ( <SidebarMenuItem key={label}> <SidebarMenuButton isActive={active} tooltip={label}> <Icon /> <span>{label}</span> </SidebarMenuButton> </SidebarMenuItem> ))} </SidebarMenu> </SidebarGroup> </SidebarContent> </Sidebar> <SidebarInset render={<div />} className="gap-3 p-4"> <div className="flex items-center gap-3"> <SidebarTrigger /> <span className="font-black font-zero text-zero-lg italic"> Board </span> </div> <p className="font-medium font-zero-text text-zero-sm text-zero-text-2"> The {variant} sidebar. </p> </SidebarInset> </SidebarProvider> </div> );}行为
在宽屏上,可以完全收起(collapsible="offcanvas"),也可以收窄为一列图标("icon"),此时每行只显示图标,名称改由 Tooltip 显示。在手机上以 Sheet 的形式打开。点击 SidebarTrigger 或按 ⌘B / Ctrl+B 可展开或收起。
默认占满屏幕高度。若要像示例那样置于一个容器中,需为 provider 设定高度,并为侧边栏添加 className="absolute h-full"。provider 内的任意组件都可以通过 useSidebar 读取展开状态。
variant 决定摆放方式:贴边("sidebar");作为与边缘稍有间距的面板("floating");或铺在黑色底色上,页面作为面板嵌在一旁("inset",配合 SidebarInset 使用)。
属性
SidebarProvider
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| defaultOpen | boolean | true | 初始是否展开(非受控)。 |
| open / onOpenChange | boolean / (open) => void | — | 是否展开(受控)。 |
Sidebar
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| variant | "sidebar" | "floating" | "inset" | "sidebar" | 贴边、与边缘留有间距的悬浮面板,或让页面作为面板内嵌。 |
| collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | 宽屏上收起时的表现。 |
| side | "left" | "right" | "left" | 停靠在哪一侧。 |
| label | string | "Sidebar" | 手机上侧边栏以侧滑面板打开,这是该面板的隐藏标题。 |
SidebarMenuButton
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| isActive | boolean | false | 当前页:填充强调色。 |
| tooltip | string | — | 侧边栏收起为图标时,提示中显示的该行名称。 |
| render | ReactElement | — | 改为渲染的元素,例如链接。 |