zzzUI

Sidebar

沿应用一侧纵向排列的导航栏:黑底带斜线纹理,各行与 Dropdown menu 相同,当前页填充强调色。

安装

terminal
# 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.

sidebar-app.tsx
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.

sidebar-variants.tsx
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

属性类型默认值说明
defaultOpenbooleantrue初始是否展开(非受控)。
open / onOpenChangeboolean / (open) => void—是否展开(受控)。

Sidebar

属性类型默认值说明
variant"sidebar" | "floating" | "inset""sidebar"贴边、与边缘留有间距的悬浮面板,或让页面作为面板内嵌。
collapsible"offcanvas" | "icon" | "none""offcanvas"宽屏上收起时的表现。
side"left" | "right""left"停靠在哪一侧。
labelstring"Sidebar"手机上侧边栏以侧滑面板打开,这是该面板的隐藏标题。

SidebarMenuButton

属性类型默认值说明
isActivebooleanfalse当前页:填充强调色。
tooltipstring—侧边栏收起为图标时,提示中显示的该行名称。
renderReactElement—改为渲染的元素,例如链接。

SidebarHeader, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuItem, SidebarMenuBadge, SidebarMenuSub, SidebarMenuSubButton, SidebarSeparator, SidebarTrigger, SidebarInset