Search for a command to run...
A composable, themeable and customizable sidebar component.
npx shadcn@latest add @zaidan/sidebarpnpx shadcn add @zaidan/sidebaryarn dlx shadcn@latest add @zaidan/sidebarbunx shadcn@latest add @zaidan/sidebarInstall the following dependencies:
npm i @kobalte/corepnpm add @kobalte/coreyarn add @kobalte/corebun add @kobalte/coreCopy and paste the following code into your project.
1import type { PolymorphicProps } from "@kobalte/core";2import { Polymorphic } from "@kobalte/core";3import type { VariantProps } from "class-variance-authority";4import { cva } from "class-variance-authority";5import { Menu } from "lucide-solid";6import type { Accessor, Component, ComponentProps, JSX, ValidComponent } from "solid-js";7import {8 createContext,9 createEffect,10 createMemo,11 createSignal,12 Match,13 mergeProps,14 onCleanup,15 Show,16 Switch,17 splitProps,18 useContext,19} from "solid-js";20import { cn } from "~/lib/utils";21import { useIsMobile } from "~/lib/hooks/use-mobile";22import type { ButtonProps } from "~/components/ui/button";23import { Button } from "~/components/ui/button";24import { Input } from "~/components/ui/input";25import { Separator } from "~/components/ui/separator";26import {27 Sheet,28 SheetContent,29 SheetDescription,30 SheetHeader,31 SheetTitle,32} from "~/components/ui/sheet";33import { Skeleton } from "~/components/ui/skeleton";34import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";35
36const SIDEBAR_COOKIE_NAME = "sidebar_state";37const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;38const SIDEBAR_WIDTH = "16rem";39const SIDEBAR_WIDTH_MOBILE = "18rem";40const SIDEBAR_WIDTH_ICON = "3rem";41const SIDEBAR_KEYBOARD_SHORTCUT = "b";42
43type SidebarContextProps = {44 state: Accessor<"expanded" | "collapsed">;45 open: Accessor<boolean>;46 setOpen: (open: boolean) => void;47 openMobile: Accessor<boolean>;48 setOpenMobile: (open: boolean) => void;49 isMobile: Accessor<boolean>;50 toggleSidebar: () => void;51};52
53const SidebarContext = createContext<SidebarContextProps | null>(null);54
55function useSidebar() {56 const context = useContext(SidebarContext);57 if (!context) {58 throw new Error("useSidebar must be used within a SidebarProvider.");59 }60
61 return context;62}63
64type SidebarProviderProps = ComponentProps<"div"> & {65 defaultOpen?: boolean;66 open?: boolean;67 onOpenChange?: (open: boolean) => void;68};69
70const SidebarProvider = (props: SidebarProviderProps) => {71 const mergedProps = mergeProps({ defaultOpen: true }, props);72 const [local, others] = splitProps(mergedProps, [73 "defaultOpen",74 "open",75 "onOpenChange",76 "class",77 "style",78 "children",79 ]);80
81 const isMobile = useIsMobile();82 const [openMobile, setOpenMobile] = createSignal(false);83
84 // This is the internal state of the sidebar.85 // We use open and onOpenChange for control from outside the component.86 const [_open, _setOpen] = createSignal(local.defaultOpen);87 const open = () => local.open ?? _open();88 const setOpen = (value: boolean | ((value: boolean) => boolean)) => {89 if (local.onOpenChange) {90 return local.onOpenChange?.(typeof value === "function" ? value(open()) : value);91 }92 _setOpen(value);93
94 // This sets the cookie to keep the sidebar state.95 // biome-ignore lint/suspicious/noDocumentCookie: <waiting for better solution>96 document.cookie = `${SIDEBAR_COOKIE_NAME}=${open()}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;97 };98
99 // Helper to toggle the sidebar.100 const toggleSidebar = () => {101 return isMobile() ? setOpenMobile((open) => !open) : setOpen((open) => !open);102 };103
104 // Adds a keyboard shortcut to toggle the sidebar.105 createEffect(() => {106 const handleKeyDown = (event: KeyboardEvent) => {107 if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {108 event.preventDefault();109 toggleSidebar();110 }111 };112
113 window.addEventListener("keydown", handleKeyDown);114 onCleanup(() => window.removeEventListener("keydown", handleKeyDown));115 });116
117 // We add a state so that we can do data-state="expanded" or "collapsed".118 // This makes it easier to style the sidebar with Tailwind classes.119 const state = () => (open() ? "expanded" : "collapsed");120
121 const contextValue = {122 state,123 open,124 setOpen,125 isMobile,126 openMobile,127 setOpenMobile,128 toggleSidebar,129 };130
131 return (132 <SidebarContext.Provider value={contextValue}>133 <div134 data-slot="sidebar-wrapper"135 style={{136 "--sidebar-width": SIDEBAR_WIDTH,137 "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,138 ...(local.style as JSX.CSSProperties),139 }}140 class={cn(141 "group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar",142 local.class,143 )}144 {...others}145 >146 {local.children}147 </div>148 </SidebarContext.Provider>149 );150};151
152type SidebarProps = ComponentProps<"div"> & {153 side?: "left" | "right";154 variant?: "sidebar" | "floating" | "inset";155 collapsible?: "offcanvas" | "icon" | "none";156};157
158const Sidebar: Component<SidebarProps> = (props) => {159 const mergedProps = mergeProps<SidebarProps[]>(160 {161 side: "left",162 variant: "sidebar",163 collapsible: "offcanvas",164 },165 props,166 );167 const [local, others] = splitProps(mergedProps, [168 "side",169 "variant",170 "collapsible",171 "class",172 "children",173 ]);174
175 const { isMobile, state, openMobile, setOpenMobile } = useSidebar();176
177 return (178 <Switch>179 <Match when={local.collapsible === "none"}>180 <div181 class={cn(182 "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",183 local.class,184 )}185 data-slot="sidebar"186 {...others}187 >188 {local.children}189 </div>190 </Match>191 <Match when={isMobile()}>192 <Sheet onOpenChange={setOpenMobile} open={openMobile()} {...others}>193 <SheetContent194 class="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"195 data-mobile="true"196 data-sidebar="sidebar"197 data-slot="sidebar"198 side={local.side}199 style={{200 "--sidebar-width": SIDEBAR_WIDTH_MOBILE,201 }}202 >203 <SheetHeader class="sr-only">204 <SheetTitle>Sidebar</SheetTitle>205 <SheetDescription>Displays the mobile sidebar.</SheetDescription>206 </SheetHeader>207 <div class="flex size-full flex-col">{local.children}</div>208 </SheetContent>209 </Sheet>210 </Match>211 <Match when={!isMobile()}>212 <div213 class="group peer hidden text-sidebar-foreground md:block"214 data-state={state()}215 data-collapsible={state() === "collapsed" ? local.collapsible : ""}216 data-variant={local.variant}217 data-side={local.side}218 data-slot="sidebar"219 >220 {/* This is what handles the sidebar gap on desktop */}221 <div222 data-slot="sidebar-gap"223 class={cn(224 "relative z-sidebar-gap w-(--sidebar-width) bg-transparent",225 "group-data-[collapsible=offcanvas]:w-0",226 "group-data-[side=right]:rotate-180",227 local.variant === "floating" || local.variant === "inset"228 ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"229 : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",230 )}231 />232 <div233 data-slot="sidebar-container"234 class={cn(235 "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",236 local.side === "left"237 ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"238 : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",239 // Adjust the padding for floating and inset variants.240 local.variant === "floating" || local.variant === "inset"241 ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"242 : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",243 local.class,244 )}245 {...others}246 >247 <div248 data-sidebar="sidebar"249 data-slot="sidebar-inner"250 class="z-sidebar-inner flex size-full flex-col"251 >252 {local.children}253 </div>254 </div>255 </div>256 </Match>257 </Switch>258 );259};260
261type SidebarTriggerProps = ButtonProps;262
263const SidebarTrigger = (props: SidebarTriggerProps) => {264 const [local, others] = splitProps(props, ["class", "onClick"]);265 const { toggleSidebar } = useSidebar();266
267 return (268 <Button269 data-sidebar="trigger"270 data-slot="sidebar-trigger"271 variant="ghost"272 size="icon-sm"273 class={cn("z-sidebar-trigger", local.class)}274 onClick={(event: MouseEvent) => {275 //@ts-expect-error - TODO: Typescript wizardry needed here276 local.onClick?.(event);277 toggleSidebar();278 }}279 {...others}280 >281 <Menu />282 <span class="sr-only">Toggle Sidebar</span>283 </Button>284 );285};286
287const SidebarRail = (props: ComponentProps<"button">) => {288 const [local, others] = splitProps(props, ["class"]);289 const { toggleSidebar } = useSidebar();290
291 return (292 <button293 data-sidebar="rail"294 data-slot="sidebar-rail"295 aria-label="Toggle Sidebar"296 tabIndex={-1}297 onClick={toggleSidebar}298 title="Toggle Sidebar"299 class={cn(300 "absolute inset-y-0 z-20 z-sidebar-rail hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",301 "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",302 "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",303 "group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",304 "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",305 "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",306 local.class,307 )}308 {...others}309 />310 );311};312
313const SidebarInset = (props: ComponentProps<"main">) => {314 const [local, others] = splitProps(props, ["class"]);315 return (316 <main317 data-slot="sidebar-inset"318 class={cn("relative z-sidebar-inset flex w-full flex-1 flex-col", local.class)}319 {...others}320 />321 );322};323
324const SidebarInput = (props: ComponentProps<typeof Input>) => {325 const [local, others] = splitProps(props, ["class"]);326 return (327 <Input328 data-slot="sidebar-input"329 data-sidebar="input"330 class={cn("z-sidebar-input", local.class)}331 {...others}332 />333 );334};335
336const SidebarHeader = (props: ComponentProps<"div">) => {337 const [local, others] = splitProps(props, ["class"]);338 return (339 <div340 data-slot="sidebar-header"341 data-sidebar="header"342 class={cn("z-sidebar-header flex flex-col", local.class)}343 {...others}344 />345 );346};347
348const SidebarFooter = (props: ComponentProps<"div">) => {349 const [local, others] = splitProps(props, ["class"]);350 return (351 <div352 data-slot="sidebar-footer"353 data-sidebar="footer"354 class={cn("z-sidebar-footer flex flex-col", local.class)}355 {...others}356 />357 );358};359
360type SidebarSeparatorProps<T extends ValidComponent = "hr"> = PolymorphicProps<361 T,362 ComponentProps<typeof Separator<T>>363>;364
365const SidebarSeparator = <T extends ValidComponent = "hr">(props: SidebarSeparatorProps<T>) => {366 const [local, others] = splitProps(props as SidebarSeparatorProps, ["class"]);367 return (368 <Separator369 data-slot="sidebar-separator"370 data-sidebar="separator"371 class={cn("z-sidebar-separator w-auto", local.class)}372 {...others}373 />374 );375};376
377const SidebarContent = (props: ComponentProps<"div">) => {378 const [local, others] = splitProps(props, ["class"]);379 return (380 <div381 data-slot="sidebar-content"382 data-sidebar="content"383 class={cn(384 "z-sidebar-content flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden",385 local.class,386 )}387 {...others}388 />389 );390};391
392const SidebarGroup = (props: ComponentProps<"div">) => {393 const [local, others] = splitProps(props, ["class"]);394 return (395 <div396 data-slot="sidebar-group"397 data-sidebar="group"398 class={cn("relative z-sidebar-group flex w-full min-w-0 flex-col", local.class)}399 {...others}400 />401 );402};403
404type SidebarGroupLabelProps<T extends ValidComponent = "div"> = PolymorphicProps<405 T,406 ComponentProps<T>407>;408
409const SidebarGroupLabel = <T extends ValidComponent = "div">(props: SidebarGroupLabelProps<T>) => {410 const [local, others] = splitProps(props as SidebarGroupLabelProps, ["class"]);411
412 return (413 <Polymorphic<SidebarGroupLabelProps>414 as="div"415 data-slot="sidebar-group-label"416 data-sidebar="group-label"417 class={cn(418 "z-sidebar-group-label flex shrink-0 items-center outline-hidden [&>svg]:shrink-0",419 local.class,420 )}421 {...others}422 />423 );424};425
426type SidebarGroupActionProps<T extends ValidComponent = "button"> = PolymorphicProps<427 T,428 ComponentProps<T>429>;430
431const SidebarGroupAction = <T extends ValidComponent = "button">(432 props: SidebarGroupActionProps<T>,433) => {434 const [local, others] = splitProps(props as SidebarGroupActionProps, ["class"]);435 return (436 <Polymorphic<SidebarGroupActionProps>437 as="button"438 data-slot="sidebar-group-action"439 data-sidebar="group-action"440 class={cn(441 "z-sidebar-group-action flex aspect-square items-center justify-center outline-hidden transition-transform after:absolute after:-inset-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:shrink-0",442 local.class,443 )}444 {...others}445 />446 );447};448
449const SidebarGroupContent = (props: ComponentProps<"div">) => {450 const [local, others] = splitProps(props, ["class"]);451 return (452 <div453 data-slot="sidebar-group-content"454 data-sidebar="group-content"455 class={cn("z-sidebar-group-content w-full", local.class)}456 {...others}457 />458 );459};460
461const SidebarMenu = (props: ComponentProps<"ul">) => {462 const [local, others] = splitProps(props, ["class"]);463 return (464 <ul465 data-slot="sidebar-menu"466 data-sidebar="menu"467 class={cn("z-sidebar-menu flex w-full min-w-0 flex-col", local.class)}468 {...others}469 />470 );471};472
473const SidebarMenuItem = (props: ComponentProps<"li">) => {474 const [local, others] = splitProps(props, ["class"]);475 return (476 <li477 data-slot="sidebar-menu-item"478 data-sidebar="menu-item"479 class={cn("group/menu-item relative", local.class)}480 {...others}481 />482 );483};484
485const sidebarMenuButtonVariants = cva(486 "peer/menu-button group/menu-button z-sidebar-menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0",487 {488 variants: {489 variant: {490 default: "z-sidebar-menu-button-variant-default",491 outline: "z-sidebar-menu-button-variant-outline",492 },493 size: {494 default: "z-sidebar-menu-button-size-default",495 sm: "z-sidebar-menu-button-size-sm",496 lg: "z-sidebar-menu-button-size-lg",497 },498 },499 defaultVariants: {500 variant: "default",501 size: "default",502 },503 },504);505
506type SidebarMenuButtonProps<T extends ValidComponent = "button"> = PolymorphicProps<507 T,508 Pick<ComponentProps<T>, "class" | "children">509> &510 VariantProps<typeof sidebarMenuButtonVariants> & {511 isActive?: boolean;512 tooltip?: string;513 };514
515const SidebarMenuButton = <T extends ValidComponent = "button">(516 rawProps: SidebarMenuButtonProps<T>,517) => {518 const props = mergeProps({ isActive: false, variant: "default", size: "default" }, rawProps);519 const [local, others] = splitProps(props as SidebarMenuButtonProps, [520 "isActive",521 "tooltip",522 "variant",523 "size",524 "class",525 ]);526 const { isMobile, state } = useSidebar();527
528 const MenuButton = (props: SidebarMenuButtonProps) => {529 const [_local, _others] = splitProps(props as SidebarMenuButtonProps, ["class"]);530 return (531 <Polymorphic<SidebarMenuButtonProps>532 as="button"533 data-slot="sidebar-menu-button"534 data-sidebar="menu-button"535 data-size={local.size}536 data-active={local.isActive}537 class={cn(538 sidebarMenuButtonVariants({ variant: local.variant, size: local.size }),539 _local.class,540 local.class,541 )}542 {..._others}543 {...others}544 />545 );546 };547
548 return (549 <Show fallback={<MenuButton />} when={local.tooltip}>550 <Tooltip placement="right">551 <TooltipTrigger as={MenuButton} class="w-full" />552 <TooltipContent hidden={state() !== "collapsed" || isMobile()}>553 {local.tooltip}554 </TooltipContent>555 </Tooltip>556 </Show>557 );558};559
560type SidebarMenuActionProps<T extends ValidComponent = "button"> = ComponentProps<T> & {561 showOnHover?: boolean;562};563
564const SidebarMenuAction = <T extends ValidComponent = "button">(565 rawProps: PolymorphicProps<T, SidebarMenuActionProps<T>>,566) => {567 const props = mergeProps({ showOnHover: false }, rawProps);568 const [local, others] = splitProps(props as SidebarMenuActionProps, ["class", "showOnHover"]);569
570 return (571 <Polymorphic<SidebarMenuActionProps>572 as="button"573 data-slot="sidebar-menu-action"574 data-sidebar="menu-action"575 class={cn(576 "z-sidebar-menu-action flex items-center justify-center outline-hidden transition-transform after:absolute after:-inset-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:shrink-0",577 local.showOnHover &&578 "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-open:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0",579 local.class,580 )}581 {...others}582 />583 );584};585
586const SidebarMenuBadge: Component<ComponentProps<"div">> = (props) => {587 const [local, others] = splitProps(props, ["class"]);588 return (589 <div590 data-slot="sidebar-menu-badge"591 data-sidebar="menu-badge"592 class={cn(593 "z-sidebar-menu-badge flex select-none items-center justify-center tabular-nums group-data-[collapsible=icon]:hidden",594 local.class,595 )}596 {...others}597 />598 );599};600
601type SidebarMenuSkeletonProps = ComponentProps<"div"> & {602 showIcon?: boolean;603};604
605const SidebarMenuSkeleton: Component<SidebarMenuSkeletonProps> = (rawProps) => {606 const props = mergeProps({ showIcon: false }, rawProps);607 const [local, others] = splitProps(props, ["class", "showIcon"]);608
609 // Random width between 50 to 90%.610 const width = createMemo(() => `${Math.floor(Math.random() * 40) + 50}%`);611
612 return (613 <div614 data-slot="sidebar-menu-skeleton"615 data-sidebar="menu-skeleton"616 class={cn("z-sidebar-menu-skeleton flex items-center", local.class)}617 {...others}618 >619 <Show when={local.showIcon}>620 <Skeleton class="z-sidebar-menu-skeleton-icon" data-sidebar="menu-skeleton-icon" />621 </Show>622 <Skeleton623 class="z-sidebar-menu-skeleton-text max-w-(--skeleton-width) flex-1"624 data-sidebar="menu-skeleton-text"625 style={{626 "--skeleton-width": width(),627 }}628 />629 </div>630 );631};632
633const SidebarMenuSub: Component<ComponentProps<"ul">> = (props) => {634 const [local, others] = splitProps(props, ["class"]);635 return (636 <ul637 data-slot="sidebar-menu-sub"638 data-sidebar="menu-sub"639 class={cn("z-sidebar-menu-sub flex min-w-0 flex-col", local.class)}640 {...others}641 />642 );643};644
645const SidebarMenuSubItem: Component<ComponentProps<"li">> = (props) => {646 const [local, others] = splitProps(props, ["class"]);647 return (648 <li649 data-slot="sidebar-menu-sub-item"650 data-sidebar="menu-sub-item"651 class={cn("group/menu-sub-item relative", local.class)}652 {...others}653 />654 );655};656
657type SidebarMenuSubButtonProps<T extends ValidComponent = "a"> = ComponentProps<T> & {658 size?: "sm" | "md";659 isActive?: boolean;660};661
662const SidebarMenuSubButton = <T extends ValidComponent = "a">(663 rawProps: PolymorphicProps<T, SidebarMenuSubButtonProps<T>>,664) => {665 const props = mergeProps({ size: "md" }, rawProps);666 const [local, others] = splitProps(props as SidebarMenuSubButtonProps, [667 "size",668 "isActive",669 "class",670 ]);671
672 return (673 <Polymorphic<SidebarMenuSubButtonProps>674 as="a"675 data-slot="sidebar-menu-sub-button"676 data-sidebar="menu-sub-button"677 data-size={local.size}678 data-active={local.isActive}679 class={cn(680 "z-sidebar-menu-sub-button flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:shrink-0",681 local.class,682 )}683 {...others}684 />685 );686};687
688export {689 Sidebar,690 SidebarContent,691 SidebarFooter,692 SidebarGroup,693 SidebarGroupAction,694 SidebarGroupContent,695 SidebarGroupLabel,696 SidebarHeader,697 SidebarInput,698 SidebarInset,699 SidebarMenu,700 SidebarMenuAction,701 SidebarMenuBadge,702 SidebarMenuButton,703 SidebarMenuItem,704 SidebarMenuSkeleton,705 SidebarMenuSub,706 SidebarMenuSubButton,707 SidebarMenuSubItem,708 SidebarProvider,709 SidebarRail,710 SidebarSeparator,711 SidebarTrigger,712 useSidebar,713 type SidebarProps,714};The sidebar component supports multiple variants and collapsible modes:
sidebar - Default sidebar attached to the edgefloating - Sidebar with rounded corners and spacing from edgesinset - Sidebar inset within the main content areaoffcanvas - Slides in/out from the edge (default)icon - Collapses to show only iconsnone - Non-collapsible fixed sidebar| Prop | Type | Default | Description |
|---|---|---|---|
side | "left" | "right" | "left" | Which side the sidebar appears on |
variant | "sidebar" | "floating" | "inset" | "sidebar" | Visual style variant |
collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | How the sidebar collapses |
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | true | Whether sidebar starts open |
open | boolean | - | Controlled open state |
onOpenChange | (open: boolean) => void | - | Callback when open state changes |
Cmd+B (Mac) / Ctrl+B (Windows) - Toggle sidebarHere is the source code of the example from the preview page:
import { Check, ChevronsUpDown, Search } from "lucide-solid";import { createSignal, For, Show } from "solid-js";import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator,} from "~/components/ui/breadcrumb";import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger,} from "~/components/ui/dropdown-menu";import { Label } from "~/components/ui/label";import { Separator } from "~/components/ui/separator";import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarTrigger,} from "~/components/ui/sidebar";export default function SidebarExample() { const [selectedVersion, setSelectedVersion] = createSignal(data.versions[0]);
return ( <SidebarProvider> <Sidebar> <SidebarHeader> <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger as={SidebarMenuButton} size="lg" class="data-expanded:bg-sidebar-accent data-expanded:text-sidebar-accent-foreground" > <div class="flex flex-col gap-0.5 leading-none"> <span class="font-medium">Documentation</span> <span class="text-muted-foreground text-xs">v{selectedVersion()}</span> </div> <ChevronsUpDown class="ml-auto" /> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--kb-popper-anchor-width)"> <For each={data.versions}> {(version) => ( <DropdownMenuItem onSelect={() => setSelectedVersion(version)}> v{version} <Show when={version === selectedVersion()}> <Check class="ml-auto" /> </Show> </DropdownMenuItem> )} </For> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> <form> <SidebarGroup class="py-0"> <SidebarGroupContent class="relative"> <Label for="search" class="sr-only"> Search </Label> <SidebarInput id="search" placeholder="Search the docs..." class="pl-8" /> <Search class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 select-none opacity-50" /> </SidebarGroupContent> </SidebarGroup> </form> </SidebarHeader> <SidebarContent> <For each={data.navMain}> {(item) => ( <SidebarGroup> <SidebarGroupLabel>{item.title}</SidebarGroupLabel> <SidebarGroupContent> <SidebarMenu> <For each={item.items}> {(subItem) => ( <SidebarMenuItem> <SidebarMenuButton as="a" href={subItem.url} isActive={subItem.isActive}> {subItem.title} </SidebarMenuButton> </SidebarMenuItem> )} </For> </SidebarMenu> </SidebarGroupContent> </SidebarGroup> )} </For> </SidebarContent> <SidebarRail /> </Sidebar> <SidebarInset> <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> <SidebarTrigger class="-ml-1" /> <Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" /> <Breadcrumb> <BreadcrumbList> <BreadcrumbItem class="hidden md:block"> <BreadcrumbLink href="#">Building Your Application</BreadcrumbLink> </BreadcrumbItem> <BreadcrumbSeparator class="hidden md:block" /> <BreadcrumbItem> <BreadcrumbPage>Data Fetching</BreadcrumbPage> </BreadcrumbItem> </BreadcrumbList> </Breadcrumb> </header> <div class="flex flex-1 flex-col gap-4 p-4"> <div class="grid auto-rows-min gap-4 md:grid-cols-3"> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> <div class="aspect-video rounded-xl bg-muted/50" /> </div> <div class="min-h-screen flex-1 rounded-xl bg-muted/50 md:min-h-min" /> </div> </SidebarInset> </SidebarProvider> );}