|
@@ -0,0 +1,2610 @@
|
|
|
|
|
+---
|
|
|
|
|
+title: Sidebar
|
|
|
|
|
+description: A composable, themeable and customizable sidebar component.
|
|
|
|
|
+component: true
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+<figure className="flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+import { AppSidebar } from "@/components/blocks/sidebar-07/components/app-sidebar"
|
|
|
|
|
+import {
|
|
|
|
|
+ Breadcrumb,
|
|
|
|
|
+ BreadcrumbItem,
|
|
|
|
|
+ BreadcrumbLink,
|
|
|
|
|
+ BreadcrumbList,
|
|
|
|
|
+ BreadcrumbPage,
|
|
|
|
|
+ BreadcrumbSeparator,
|
|
|
|
|
+} from "@/components/ui/breadcrumb"
|
|
|
|
|
+import { Separator } from "@/components/ui/separator"
|
|
|
|
|
+import {
|
|
|
|
|
+ SidebarInset,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+ SidebarTrigger,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function Page() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <AppSidebar />
|
|
|
|
|
+ <SidebarInset>
|
|
|
|
|
+ <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
|
|
|
+ <div className="flex items-center gap-2 px-4">
|
|
|
|
|
+ <SidebarTrigger className="-ml-1" />
|
|
|
|
|
+ <Separator
|
|
|
|
|
+ orientation="vertical"
|
|
|
|
|
+ className="mr-2 data-[orientation=vertical]:h-4"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Breadcrumb>
|
|
|
|
|
+ <BreadcrumbList>
|
|
|
|
|
+ <BreadcrumbItem className="hidden md:block">
|
|
|
|
|
+ <BreadcrumbLink href="#">
|
|
|
|
|
+ Building Your Application
|
|
|
|
|
+ </BreadcrumbLink>
|
|
|
|
|
+ </BreadcrumbItem>
|
|
|
|
|
+ <BreadcrumbSeparator className="hidden md:block" />
|
|
|
|
|
+ <BreadcrumbItem>
|
|
|
|
|
+ <BreadcrumbPage>Data Fetching</BreadcrumbPage>
|
|
|
|
|
+ </BreadcrumbItem>
|
|
|
|
|
+ </BreadcrumbList>
|
|
|
|
|
+ </Breadcrumb>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </header>
|
|
|
|
|
+ <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
|
|
|
|
+ <div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
|
|
|
|
+ <div className="bg-muted/50 aspect-video rounded-xl" />
|
|
|
|
|
+ <div className="bg-muted/50 aspect-video rounded-xl" />
|
|
|
|
|
+ <div className="bg-muted/50 aspect-video rounded-xl" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="bg-muted/50 min-h-[100vh] flex-1 rounded-xl md:min-h-min" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </SidebarInset>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar that collapses to icons.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+Sidebars are one of the most complex components to build. They are central
|
|
|
|
|
+to any application and often contain a lot of moving parts.
|
|
|
|
|
+
|
|
|
|
|
+I don't like building sidebars. So I built 30+ of them. All kinds of
|
|
|
|
|
+configurations. Then I extracted the core components into `sidebar.tsx`.
|
|
|
|
|
+
|
|
|
|
|
+We now have a solid foundation to build on top of. Composable. Themeable.
|
|
|
|
|
+Customizable.
|
|
|
|
|
+
|
|
|
|
|
+[Browse the Blocks Library](/blocks).
|
|
|
|
|
+
|
|
|
|
|
+## Installation
|
|
|
|
|
+
|
|
|
|
|
+<CodeTabs>
|
|
|
|
|
+
|
|
|
|
|
+<TabsList>
|
|
|
|
|
+ <TabsTrigger value="cli">CLI</TabsTrigger>
|
|
|
|
|
+ <TabsTrigger value="manual">Manual</TabsTrigger>
|
|
|
|
|
+</TabsList>
|
|
|
|
|
+<TabsContent value="cli">
|
|
|
|
|
+
|
|
|
|
|
+<Steps>
|
|
|
|
|
+
|
|
|
|
|
+<Step>Run the following command to install `sidebar.tsx`</Step>
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+npx shadcn@latest add sidebar
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+<Step>Add the following colors to your CSS file</Step>
|
|
|
|
|
+
|
|
|
|
|
+The command above should install the colors for you. If not, copy and paste the following in your CSS file.
|
|
|
|
|
+
|
|
|
|
|
+We'll go over the colors later in the [theming section](/docs/components/sidebar#theming).
|
|
|
|
|
+
|
|
|
|
|
+```css showLineNumbers title="app/globals.css"
|
|
|
|
|
+@layer base {
|
|
|
|
|
+ :root {
|
|
|
|
|
+ --sidebar: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-foreground: oklch(0.145 0 0);
|
|
|
|
|
+ --sidebar-primary: oklch(0.205 0 0);
|
|
|
|
|
+ --sidebar-primary-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-accent: oklch(0.97 0 0);
|
|
|
|
|
+ --sidebar-accent-foreground: oklch(0.205 0 0);
|
|
|
|
|
+ --sidebar-border: oklch(0.922 0 0);
|
|
|
|
|
+ --sidebar-ring: oklch(0.708 0 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dark {
|
|
|
|
|
+ --sidebar: oklch(0.205 0 0);
|
|
|
|
|
+ --sidebar-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-primary: oklch(0.488 0.243 264.376);
|
|
|
|
|
+ --sidebar-primary-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-accent: oklch(0.269 0 0);
|
|
|
|
|
+ --sidebar-accent-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-border: oklch(1 0 0 / 10%);
|
|
|
|
|
+ --sidebar-ring: oklch(0.439 0 0);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+</Steps>
|
|
|
|
|
+
|
|
|
|
|
+</TabsContent>
|
|
|
|
|
+
|
|
|
|
|
+<TabsContent value="manual">
|
|
|
|
|
+
|
|
|
|
|
+<Steps>
|
|
|
|
|
+
|
|
|
|
|
+<Step>Copy and paste the following code into your project.</Step>
|
|
|
|
|
+
|
|
|
|
|
+<ComponentSource name="sidebar" title="components/ui/sidebar.tsx" />
|
|
|
|
|
+
|
|
|
|
|
+<Step>Update the import paths to match your project setup.</Step>
|
|
|
|
|
+
|
|
|
|
|
+<Step>Add the following colors to your CSS file</Step>
|
|
|
|
|
+
|
|
|
|
|
+We'll go over the colors later in the [theming section](/docs/components/sidebar#theming).
|
|
|
|
|
+
|
|
|
|
|
+```css showLineNumbers title="app/globals.css"
|
|
|
|
|
+@layer base {
|
|
|
|
|
+ :root {
|
|
|
|
|
+ --sidebar: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-foreground: oklch(0.145 0 0);
|
|
|
|
|
+ --sidebar-primary: oklch(0.205 0 0);
|
|
|
|
|
+ --sidebar-primary-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-accent: oklch(0.97 0 0);
|
|
|
|
|
+ --sidebar-accent-foreground: oklch(0.205 0 0);
|
|
|
|
|
+ --sidebar-border: oklch(0.922 0 0);
|
|
|
|
|
+ --sidebar-ring: oklch(0.708 0 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dark {
|
|
|
|
|
+ --sidebar: oklch(0.205 0 0);
|
|
|
|
|
+ --sidebar-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-primary: oklch(0.488 0.243 264.376);
|
|
|
|
|
+ --sidebar-primary-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-accent: oklch(0.269 0 0);
|
|
|
|
|
+ --sidebar-accent-foreground: oklch(0.985 0 0);
|
|
|
|
|
+ --sidebar-border: oklch(1 0 0 / 10%);
|
|
|
|
|
+ --sidebar-ring: oklch(0.439 0 0);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+</Steps>
|
|
|
|
|
+
|
|
|
|
|
+</TabsContent>
|
|
|
|
|
+
|
|
|
|
|
+</CodeTabs>
|
|
|
|
|
+
|
|
|
|
|
+## Structure
|
|
|
|
|
+
|
|
|
|
|
+A `Sidebar` component is composed of the following parts:
|
|
|
|
|
+
|
|
|
|
|
+- `SidebarProvider` - Handles collapsible state.
|
|
|
|
|
+- `Sidebar` - The sidebar container.
|
|
|
|
|
+- `SidebarHeader` and `SidebarFooter` - Sticky at the top and bottom of the sidebar.
|
|
|
|
|
+- `SidebarContent` - Scrollable content.
|
|
|
|
|
+- `SidebarGroup` - Section within the `SidebarContent`.
|
|
|
|
|
+- `SidebarTrigger` - Trigger for the `Sidebar`.
|
|
|
|
|
+
|
|
|
|
|
+<Image
|
|
|
|
|
+ src="/images/sidebar-structure.png"
|
|
|
|
|
+ width="716"
|
|
|
|
|
+ height="420"
|
|
|
|
|
+ alt="Sidebar Structure"
|
|
|
|
|
+ className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
|
|
|
|
+/>
|
|
|
|
|
+<Image
|
|
|
|
|
+ src="/images/sidebar-structure-dark.png"
|
|
|
|
|
+ width="716"
|
|
|
|
|
+ height="420"
|
|
|
|
|
+ alt="Sidebar Structure"
|
|
|
|
|
+ className="mt-6 hidden w-full overflow-hidden rounded-lg border dark:block"
|
|
|
|
|
+/>
|
|
|
|
|
+
|
|
|
|
|
+## Usage
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="app/layout.tsx"
|
|
|
|
|
+import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
|
|
|
|
+import { AppSidebar } from "@/components/app-sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export default function Layout({ children }: { children: React.ReactNode }) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <AppSidebar />
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </main>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/app-sidebar.tsx"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarFooter,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarHeader,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarHeader />
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ <SidebarFooter />
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Your First Sidebar
|
|
|
|
|
+
|
|
|
|
|
+Let's start with the most basic sidebar. A collapsible sidebar with a menu.
|
|
|
|
|
+
|
|
|
|
|
+<Steps>
|
|
|
|
|
+
|
|
|
|
|
+<Step>
|
|
|
|
|
+ Add a `SidebarProvider` and `SidebarTrigger` at the root of your application.
|
|
|
|
|
+</Step>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="app/layout.tsx"
|
|
|
|
|
+import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
|
|
|
|
+import { AppSidebar } from "@/components/app-sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export default function Layout({ children }: { children: React.ReactNode }) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <AppSidebar />
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </main>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+<Step>Create a new sidebar component at `components/app-sidebar.tsx`.</Step>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/app-sidebar.tsx"
|
|
|
|
|
+import { Sidebar, SidebarContent } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent />
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+<Step>Now, let's add a `SidebarMenu` to the sidebar.</Step>
|
|
|
|
|
+
|
|
|
|
|
+We'll use the `SidebarMenu` component in a `SidebarGroup`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/app-sidebar.tsx"
|
|
|
|
|
+import { Calendar, Home, Inbox, Search, Settings } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+// Menu items.
|
|
|
|
|
+const items = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Home",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: Home,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Inbox",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: Inbox,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Calendar",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: Calendar,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Search",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: Search,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Settings",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: Settings,
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Application</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {items.map((item) => (
|
|
|
|
|
+ <SidebarMenuItem key={item.title}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={item.url}>
|
|
|
|
|
+ <item.icon />
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+<Step>You've created your first sidebar.</Step>
|
|
|
|
|
+
|
|
|
|
|
+You should see something like this:
|
|
|
|
|
+
|
|
|
|
|
+<figure className="flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ CalendarIcon,
|
|
|
|
|
+ HomeIcon,
|
|
|
|
|
+ InboxIcon,
|
|
|
|
|
+ SearchIcon,
|
|
|
|
|
+ SettingsIcon,
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarInset,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+ SidebarTrigger,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+// Menu items.
|
|
|
|
|
+const items = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Home",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: HomeIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Inbox",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: InboxIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Calendar",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: CalendarIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Search",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SearchIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Settings",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SettingsIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Application</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {items.map((item) => (
|
|
|
|
|
+ <SidebarMenuItem key={item.title}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={item.url}>
|
|
|
|
|
+ <item.icon />
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ <SidebarInset>
|
|
|
|
|
+ <header className="flex h-12 items-center justify-between px-4">
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ </header>
|
|
|
|
|
+ </SidebarInset>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ Your first sidebar.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+</Steps>
|
|
|
|
|
+
|
|
|
|
|
+## Components
|
|
|
|
|
+
|
|
|
|
|
+The components in `sidebar.tsx` are built to be composable i.e you build your sidebar by putting the provided components together. They also compose well with other shadcn/ui components such as `DropdownMenu`, `Collapsible` or `Dialog` etc.
|
|
|
|
|
+
|
|
|
|
|
+**If you need to change the code in `sidebar.tsx`, you are encouraged to do so. The code is yours. Use `sidebar.tsx` as a starting point and build your own.**
|
|
|
|
|
+
|
|
|
|
|
+In the next sections, we'll go over each component and how to use them.
|
|
|
|
|
+
|
|
|
|
|
+## SidebarProvider
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarProvider` component is used to provide the sidebar context to the `Sidebar` component. You should always wrap your application in a `SidebarProvider` component.
|
|
|
|
|
+
|
|
|
|
|
+### Props
|
|
|
|
|
+
|
|
|
|
|
+| Name | Type | Description |
|
|
|
|
|
+| -------------- | ------------------------- | -------------------------------------------- |
|
|
|
|
|
+| `defaultOpen` | `boolean` | Default open state of the sidebar. |
|
|
|
|
|
+| `open` | `boolean` | Open state of the sidebar (controlled). |
|
|
|
|
|
+| `onOpenChange` | `(open: boolean) => void` | Sets open state of the sidebar (controlled). |
|
|
|
|
|
+
|
|
|
|
|
+### Width
|
|
|
|
|
+
|
|
|
|
|
+If you have a single sidebar in your application, you can use the `SIDEBAR_WIDTH` and `SIDEBAR_WIDTH_MOBILE` variables in `sidebar.tsx` to set the width of the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
|
|
|
|
|
+const SIDEBAR_WIDTH = "16rem"
|
|
|
|
|
+const SIDEBAR_WIDTH_MOBILE = "18rem"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+For multiple sidebars in your application, you can use the `style` prop to set the width of the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+To set the width of the sidebar, you can use the `--sidebar-width` and `--sidebar-width-mobile` CSS variables in the `style` prop.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
|
|
|
|
|
+<SidebarProvider
|
|
|
|
|
+ style={{
|
|
|
|
|
+ "--sidebar-width": "20rem",
|
|
|
|
|
+ "--sidebar-width-mobile": "20rem",
|
|
|
|
|
+ }}
|
|
|
|
|
+>
|
|
|
|
|
+ <Sidebar />
|
|
|
|
|
+</SidebarProvider>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+This will handle the width of the sidebar but also the layout spacing.
|
|
|
|
|
+
|
|
|
|
|
+### Keyboard Shortcut
|
|
|
|
|
+
|
|
|
|
|
+The `SIDEBAR_KEYBOARD_SHORTCUT` variable is used to set the keyboard shortcut used to open and close the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+To trigger the sidebar, you use the `cmd+b` keyboard shortcut on Mac and `ctrl+b` on Windows.
|
|
|
|
|
+
|
|
|
|
|
+You can change the keyboard shortcut by updating the `SIDEBAR_KEYBOARD_SHORTCUT` variable.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
|
|
|
|
|
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Persisted State
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarProvider` supports persisting the sidebar state across page reloads and server-side rendering. It uses cookies to store the current state of the sidebar. When the sidebar state changes, a default cookie named `sidebar_state` is set with the current open/closed state. This cookie is then read on subsequent page loads to restore the sidebar state.
|
|
|
|
|
+
|
|
|
|
|
+To persist sidebar state in Next.js, set up your `SidebarProvider` in `app/layout.tsx` like this:
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="app/layout.tsx"
|
|
|
|
|
+import { cookies } from "next/headers"
|
|
|
|
|
+
|
|
|
|
|
+import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
|
|
|
|
+import { AppSidebar } from "@/components/app-sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export async function Layout({ children }: { children: React.ReactNode }) {
|
|
|
|
|
+ const cookieStore = await cookies()
|
|
|
|
|
+ const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider defaultOpen={defaultOpen}>
|
|
|
|
|
+ <AppSidebar />
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </main>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+You can change the name of the cookie by updating the `SIDEBAR_COOKIE_NAME` variable in `sidebar.tsx`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/ui/sidebar.tsx"
|
|
|
|
|
+const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Sidebar
|
|
|
|
|
+
|
|
|
|
|
+The main `Sidebar` component used to render a collapsible sidebar.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { Sidebar } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return <Sidebar />
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Props
|
|
|
|
|
+
|
|
|
|
|
+| Property | Type | Description |
|
|
|
|
|
+| ------------- | --------------------------------- | --------------------------------- |
|
|
|
|
|
+| `side` | `left` or `right` | The side of the sidebar. |
|
|
|
|
|
+| `variant` | `sidebar`, `floating`, or `inset` | The variant of the sidebar. |
|
|
|
|
|
+| `collapsible` | `offcanvas`, `icon`, or `none` | Collapsible state of the sidebar. |
|
|
|
|
|
+
|
|
|
|
|
+### side
|
|
|
|
|
+
|
|
|
|
|
+Use the `side` prop to change the side of the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+Available options are `left` and `right`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { Sidebar } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return <Sidebar side="left | right" />
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### variant
|
|
|
|
|
+
|
|
|
|
|
+Use the `variant` prop to change the variant of the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+Available options are `sidebar`, `floating` and `inset`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { Sidebar } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return <Sidebar variant="sidebar | floating | inset" />
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+<Callout>
|
|
|
|
|
+ **Note:** If you use the `inset` variant, remember to wrap your main content
|
|
|
|
|
+ in a `SidebarInset` component.
|
|
|
|
|
+</Callout>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarProvider>
|
|
|
|
|
+ <Sidebar variant="inset" />
|
|
|
|
|
+ <SidebarInset>
|
|
|
|
|
+ <main>{children}</main>
|
|
|
|
|
+ </SidebarInset>
|
|
|
|
|
+</SidebarProvider>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### collapsible
|
|
|
|
|
+
|
|
|
|
|
+Use the `collapsible` prop to make the sidebar collapsible.
|
|
|
|
|
+
|
|
|
|
|
+Available options are `offcanvas`, `icon` and `none`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { Sidebar } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return <Sidebar collapsible="offcanvas | icon | none" />
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+| Prop | Description |
|
|
|
|
|
+| ----------- | ------------------------------------------------------------ |
|
|
|
|
|
+| `offcanvas` | A collapsible sidebar that slides in from the left or right. |
|
|
|
|
|
+| `icon` | A sidebar that collapses to icons. |
|
|
|
|
|
+| `none` | A non-collapsible sidebar. |
|
|
|
|
|
+
|
|
|
|
|
+## useSidebar
|
|
|
|
|
+
|
|
|
|
|
+The `useSidebar` hook is used to control the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { useSidebar } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ const {
|
|
|
|
|
+ state,
|
|
|
|
|
+ open,
|
|
|
|
|
+ setOpen,
|
|
|
|
|
+ openMobile,
|
|
|
|
|
+ setOpenMobile,
|
|
|
|
|
+ isMobile,
|
|
|
|
|
+ toggleSidebar,
|
|
|
|
|
+ } = useSidebar()
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+| Property | Type | Description |
|
|
|
|
|
+| --------------- | ------------------------- | --------------------------------------------- |
|
|
|
|
|
+| `state` | `expanded` or `collapsed` | The current state of the sidebar. |
|
|
|
|
|
+| `open` | `boolean` | Whether the sidebar is open. |
|
|
|
|
|
+| `setOpen` | `(open: boolean) => void` | Sets the open state of the sidebar. |
|
|
|
|
|
+| `openMobile` | `boolean` | Whether the sidebar is open on mobile. |
|
|
|
|
|
+| `setOpenMobile` | `(open: boolean) => void` | Sets the open state of the sidebar on mobile. |
|
|
|
|
|
+| `isMobile` | `boolean` | Whether the sidebar is on mobile. |
|
|
|
|
|
+| `toggleSidebar` | `() => void` | Toggles the sidebar. Desktop and mobile. |
|
|
|
|
|
+
|
|
|
|
|
+## SidebarHeader
|
|
|
|
|
+
|
|
|
|
|
+Use the `SidebarHeader` component to add a sticky header to the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+The following example adds a `<DropdownMenu>` to the `SidebarHeader`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { ChevronDownIcon } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ DropdownMenu,
|
|
|
|
|
+ DropdownMenuContent,
|
|
|
|
|
+ DropdownMenuItem,
|
|
|
|
|
+ DropdownMenuTrigger,
|
|
|
|
|
+} from "@/components/ui/dropdown-menu"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarHeader,
|
|
|
|
|
+ SidebarInset,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+ SidebarTrigger,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarHeader>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
|
|
|
|
+ Select Workspace
|
|
|
|
|
+ <ChevronDownIcon className="ml-auto" />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent className="w-(--radix-popper-anchor-width)">
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Acme Inc</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Acme Corp.</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarHeader>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ <SidebarInset>
|
|
|
|
|
+ <header className="flex h-12 items-center justify-between px-4">
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ </header>
|
|
|
|
|
+ </SidebarInset>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar header with a dropdown menu.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/app-sidebar.tsx"
|
|
|
|
|
+<Sidebar>
|
|
|
|
|
+ <SidebarHeader>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ Select Workspace
|
|
|
|
|
+ <ChevronDown className="ml-auto" />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent className="w-[--radix-popper-anchor-width]">
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Acme Inc</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Acme Corp.</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarHeader>
|
|
|
|
|
+</Sidebar>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarFooter
|
|
|
|
|
+
|
|
|
|
|
+Use the `SidebarFooter` component to add a sticky footer to the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+The following example adds a `<DropdownMenu>` to the `SidebarFooter`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { ChevronUpIcon } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ DropdownMenu,
|
|
|
|
|
+ DropdownMenuContent,
|
|
|
|
|
+ DropdownMenuItem,
|
|
|
|
|
+ DropdownMenuTrigger,
|
|
|
|
|
+} from "@/components/ui/dropdown-menu"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarFooter,
|
|
|
|
|
+ SidebarHeader,
|
|
|
|
|
+ SidebarInset,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+ SidebarTrigger,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarHeader />
|
|
|
|
|
+ <SidebarContent />
|
|
|
|
|
+ <SidebarFooter>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
|
|
|
|
+ Username
|
|
|
|
|
+ <ChevronUpIcon className="ml-auto" />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent
|
|
|
|
|
+ side="top"
|
|
|
|
|
+ className="w-(--radix-popper-anchor-width)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Account</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Billing</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Sign out</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarFooter>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ <SidebarInset>
|
|
|
|
|
+ <header className="flex h-12 items-center justify-between px-4">
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ </header>
|
|
|
|
|
+ </SidebarInset>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar footer with a dropdown menu.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="components/app-sidebar.tsx"
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarHeader />
|
|
|
|
|
+ <SidebarContent />
|
|
|
|
|
+ <SidebarFooter>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ <User2 /> Username
|
|
|
|
|
+ <ChevronUp className="ml-auto" />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent
|
|
|
|
|
+ side="top"
|
|
|
|
|
+ className="w-[--radix-popper-anchor-width]"
|
|
|
|
|
+ >
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Account</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Billing</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Sign out</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarFooter>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarContent
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarContent` component is used to wrap the content of the sidebar. This is where you add your `SidebarGroup` components. It is scrollable.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { Sidebar, SidebarContent } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarGroup
|
|
|
|
|
+
|
|
|
|
|
+Use the `SidebarGroup` component to create a section within the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an optional `SidebarGroupAction`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { LifeBuoyIcon, SendIcon } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Help</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ <LifeBuoyIcon />
|
|
|
|
|
+ Support
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ <SendIcon />
|
|
|
|
|
+ Feedback
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar group.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { Sidebar, SidebarContent, SidebarGroup } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Application</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupAction>
|
|
|
|
|
+ <Plus /> <span className="sr-only">Add Project</span>
|
|
|
|
|
+ </SidebarGroupAction>
|
|
|
|
|
+ <SidebarGroupContent></SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Collapsible SidebarGroup
|
|
|
|
|
+
|
|
|
|
|
+To make a `SidebarGroup` collapsible, wrap it in a `Collapsible`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { ChevronDownIcon, LifeBuoyIcon, SendIcon } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Collapsible,
|
|
|
|
|
+ CollapsibleContent,
|
|
|
|
|
+ CollapsibleTrigger,
|
|
|
|
|
+} from "@/components/ui/collapsible"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <Collapsible defaultOpen className="group/collapsible">
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel
|
|
|
|
|
+ asChild
|
|
|
|
|
+ className="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground text-sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CollapsibleTrigger>
|
|
|
|
|
+ Help
|
|
|
|
|
+ <ChevronDownIcon className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
|
|
|
+ </CollapsibleTrigger>
|
|
|
|
|
+ </SidebarGroupLabel>
|
|
|
|
|
+ <CollapsibleContent>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ <LifeBuoyIcon />
|
|
|
|
|
+ Support
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ <SendIcon />
|
|
|
|
|
+ Feedback
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </CollapsibleContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </Collapsible>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A collapsible sidebar group.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Collapsible defaultOpen className="group/collapsible">
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel asChild>
|
|
|
|
|
+ <CollapsibleTrigger>
|
|
|
|
|
+ Help
|
|
|
|
|
+ <ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
|
|
|
+ </CollapsibleTrigger>
|
|
|
|
|
+ </SidebarGroupLabel>
|
|
|
|
|
+ <CollapsibleContent>
|
|
|
|
|
+ <SidebarGroupContent />
|
|
|
|
|
+ </CollapsibleContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </Collapsible>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+<Callout>
|
|
|
|
|
+ **Note:** We wrap the `CollapsibleTrigger` in a `SidebarGroupLabel` to render
|
|
|
|
|
+ a button.
|
|
|
|
|
+</Callout>
|
|
|
|
|
+
|
|
|
|
|
+## SidebarGroupAction
|
|
|
|
|
+
|
|
|
|
|
+Use the `SidebarGroupAction` component to add an action button to the `SidebarGroup`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { FrameIcon, MapIcon, PieChartIcon, PlusIcon } from "lucide-react"
|
|
|
|
|
+import { toast, Toaster } from "sonner"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupAction,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Toaster
|
|
|
|
|
+ position="bottom-left"
|
|
|
|
|
+ toastOptions={{
|
|
|
|
|
+ className: "ml-[160px]",
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupAction
|
|
|
|
|
+ title="Add Project"
|
|
|
|
|
+ onClick={() => toast("You clicked the group action!")}
|
|
|
|
|
+ >
|
|
|
|
|
+ <PlusIcon /> <span className="sr-only">Add Project</span>
|
|
|
|
|
+ </SidebarGroupAction>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">
|
|
|
|
|
+ <FrameIcon />
|
|
|
|
|
+ <span>Design Engineering</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">
|
|
|
|
|
+ <PieChartIcon />
|
|
|
|
|
+ <span>Sales & Marketing</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">
|
|
|
|
|
+ <MapIcon />
|
|
|
|
|
+ <span>Travel</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar group with an action button.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers {5-7}
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel asChild>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupAction title="Add Project">
|
|
|
|
|
+ <Plus /> <span className="sr-only">Add Project</span>
|
|
|
|
|
+ </SidebarGroupAction>
|
|
|
|
|
+ <SidebarGroupContent />
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarMenu
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarMenu` component is used for building a menu within a `SidebarGroup`.
|
|
|
|
|
+
|
|
|
|
|
+A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton`, `<SidebarMenuAction />` and `<SidebarMenuSub />` components.
|
|
|
|
|
+
|
|
|
|
|
+<Image
|
|
|
|
|
+ src="/images/sidebar-menu.png"
|
|
|
|
|
+ width="716"
|
|
|
|
|
+ height="420"
|
|
|
|
|
+ alt="Sidebar Menu"
|
|
|
|
|
+ className="mt-6 w-full overflow-hidden rounded-lg border dark:hidden"
|
|
|
|
|
+/>
|
|
|
|
|
+<Image
|
|
|
|
|
+ src="/images/sidebar-menu-dark.png"
|
|
|
|
|
+ width="716"
|
|
|
|
|
+ height="420"
|
|
|
|
|
+ alt="Sidebar Menu"
|
|
|
|
|
+ className="mt-6 hidden w-full overflow-hidden rounded-lg border dark:block"
|
|
|
|
|
+/>
|
|
|
|
|
+
|
|
|
|
|
+Here's an example of a `SidebarMenu` component rendering a list of projects.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ FrameIcon,
|
|
|
|
|
+ LifeBuoyIcon,
|
|
|
|
|
+ MapIcon,
|
|
|
|
|
+ PieChartIcon,
|
|
|
|
|
+ SendIcon,
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const projects = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Design Engineering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: FrameIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Sales & Marketing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: PieChartIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Travel",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: MapIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Support",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: LifeBuoyIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Feedback",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SendIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar menu with a list of projects.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+</Sidebar>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarMenuButton
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarMenuButton` component is used to render a menu button within a `SidebarMenuItem`.
|
|
|
|
|
+
|
|
|
|
|
+### Link or Anchor
|
|
|
|
|
+
|
|
|
|
|
+By default, the `SidebarMenuButton` renders a button but you can use the `asChild` prop to render a different component such as a `Link` or an `a` tag.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">Home</a>
|
|
|
|
|
+</SidebarMenuButton>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Icon and Label
|
|
|
|
|
+
|
|
|
|
|
+You can render an icon and a truncated label inside the button. Remember to wrap the label in a `<span>`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">
|
|
|
|
|
+ <Home />
|
|
|
|
|
+ <span>Home</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+</SidebarMenuButton>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### isActive
|
|
|
|
|
+
|
|
|
|
|
+Use the `isActive` prop to mark a menu item as active.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuButton asChild isActive>
|
|
|
|
|
+ <a href="#">Home</a>
|
|
|
|
|
+</SidebarMenuButton>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarMenuAction
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarMenuAction` component is used to render a menu action within a `SidebarMenuItem`.
|
|
|
|
|
+
|
|
|
|
|
+This button works independently of the `SidebarMenuButton` i.e you can have the `<SidebarMenuButton />` as a clickable link and the `<SidebarMenuAction />` as a button.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">
|
|
|
|
|
+ <Home />
|
|
|
|
|
+ <span>Home</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ <SidebarMenuAction>
|
|
|
|
|
+ <Plus /> <span className="sr-only">Add Project</span>
|
|
|
|
|
+ </SidebarMenuAction>
|
|
|
|
|
+</SidebarMenuItem>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### DropdownMenu
|
|
|
|
|
+
|
|
|
|
|
+Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ FrameIcon,
|
|
|
|
|
+ LifeBuoyIcon,
|
|
|
|
|
+ MapIcon,
|
|
|
|
|
+ MoreHorizontalIcon,
|
|
|
|
|
+ PieChartIcon,
|
|
|
|
|
+ SendIcon,
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ DropdownMenu,
|
|
|
|
|
+ DropdownMenuContent,
|
|
|
|
|
+ DropdownMenuItem,
|
|
|
|
|
+ DropdownMenuTrigger,
|
|
|
|
|
+} from "@/components/ui/dropdown-menu"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuAction,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const projects = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Design Engineering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: FrameIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Sales & Marketing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: PieChartIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Travel",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: MapIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Support",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: LifeBuoyIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Feedback",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SendIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton
|
|
|
|
|
+ asChild
|
|
|
|
|
+ className="group-has-[[data-state=open]]/menu-item:bg-sidebar-accent"
|
|
|
|
|
+ >
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuAction>
|
|
|
|
|
+ <MoreHorizontalIcon />
|
|
|
|
|
+ <span className="sr-only">More</span>
|
|
|
|
|
+ </SidebarMenuAction>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent side="right" align="start">
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Edit Project</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Delete Project</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar menu action with a dropdown menu.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href="#">
|
|
|
|
|
+ <Home />
|
|
|
|
|
+ <span>Home</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
|
|
+ <SidebarMenuAction>
|
|
|
|
|
+ <MoreHorizontal />
|
|
|
|
|
+ </SidebarMenuAction>
|
|
|
|
|
+ </DropdownMenuTrigger>
|
|
|
|
|
+ <DropdownMenuContent side="right" align="start">
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Edit Project</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ <DropdownMenuItem>
|
|
|
|
|
+ <span>Delete Project</span>
|
|
|
|
|
+ </DropdownMenuItem>
|
|
|
|
|
+ </DropdownMenuContent>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+</SidebarMenuItem>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarMenuSub
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarMenuSub` component is used to render a submenu within a `SidebarMenu`.
|
|
|
|
|
+
|
|
|
|
|
+Use `<SidebarMenuSubItem />` and `<SidebarMenuSubButton />` to render a submenu item.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarMenuSub,
|
|
|
|
|
+ SidebarMenuSubButton,
|
|
|
|
|
+ SidebarMenuSubItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const items = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Getting Started",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Installation",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Project Structure",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Building Your Application",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Routing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Data Fetching",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ isActive: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Rendering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Caching",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Styling",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Optimizing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Configuring",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Testing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Authentication",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Deploying",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Upgrading",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Examples",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "API Reference",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Components",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "File Conventions",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Functions",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "next.config.js Options",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "CLI",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Edge Runtime",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Architecture",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Accessibility",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Fast Refresh",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Next.js Compiler",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Supported Browsers",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Turbopack",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {items.map((item, index) => (
|
|
|
|
|
+ <SidebarMenuItem key={index}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={item.url}>
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ <SidebarMenuSub>
|
|
|
|
|
+ {item.items.map((subItem, subIndex) => (
|
|
|
|
|
+ <SidebarMenuSubItem key={subIndex}>
|
|
|
|
|
+ <SidebarMenuSubButton asChild>
|
|
|
|
|
+ <a href={subItem.url}>
|
|
|
|
|
+ <span>{subItem.title}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuSubButton>
|
|
|
|
|
+ </SidebarMenuSubItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenuSub>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar menu with a submenu.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton />
|
|
|
|
|
+ <SidebarMenuSub>
|
|
|
|
|
+ <SidebarMenuSubItem>
|
|
|
|
|
+ <SidebarMenuSubButton />
|
|
|
|
|
+ </SidebarMenuSubItem>
|
|
|
|
|
+ <SidebarMenuSubItem>
|
|
|
|
|
+ <SidebarMenuSubButton />
|
|
|
|
|
+ </SidebarMenuSubItem>
|
|
|
|
|
+ </SidebarMenuSub>
|
|
|
|
|
+</SidebarMenuItem>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Collapsible SidebarMenu
|
|
|
|
|
+
|
|
|
|
|
+To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` components in a `Collapsible`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { ChevronRightIcon } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Collapsible,
|
|
|
|
|
+ CollapsibleContent,
|
|
|
|
|
+ CollapsibleTrigger,
|
|
|
|
|
+} from "@/components/ui/collapsible"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarMenuSub,
|
|
|
|
|
+ SidebarMenuSubButton,
|
|
|
|
|
+ SidebarMenuSubItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const items = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Getting Started",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Installation",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Project Structure",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Building Your Application",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Routing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Data Fetching",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ isActive: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Rendering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Caching",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Styling",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Optimizing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Configuring",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Testing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Authentication",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Deploying",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Upgrading",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Examples",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "API Reference",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Components",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "File Conventions",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Functions",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "next.config.js Options",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "CLI",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Edge Runtime",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Architecture",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ items: [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Accessibility",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Fast Refresh",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Next.js Compiler",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Supported Browsers",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Turbopack",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {items.map((item, index) => (
|
|
|
|
|
+ <Collapsible
|
|
|
|
|
+ key={index}
|
|
|
|
|
+ className="group/collapsible"
|
|
|
|
|
+ defaultOpen={index === 0}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <CollapsibleTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton>
|
|
|
|
|
+ <span>{item.title}</span>
|
|
|
|
|
+ <ChevronRightIcon className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-90" />
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </CollapsibleTrigger>
|
|
|
|
|
+ <CollapsibleContent>
|
|
|
|
|
+ <SidebarMenuSub>
|
|
|
|
|
+ {item.items.map((subItem, subIndex) => (
|
|
|
|
|
+ <SidebarMenuSubItem key={subIndex}>
|
|
|
|
|
+ <SidebarMenuSubButton asChild>
|
|
|
|
|
+ <a href={subItem.url}>
|
|
|
|
|
+ <span>{subItem.title}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuSubButton>
|
|
|
|
|
+ </SidebarMenuSubItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenuSub>
|
|
|
|
|
+ </CollapsibleContent>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </Collapsible>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A collapsible sidebar menu.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenu>
|
|
|
|
|
+ <Collapsible defaultOpen className="group/collapsible">
|
|
|
|
|
+ <SidebarMenuItem>
|
|
|
|
|
+ <CollapsibleTrigger asChild>
|
|
|
|
|
+ <SidebarMenuButton />
|
|
|
|
|
+ </CollapsibleTrigger>
|
|
|
|
|
+ <CollapsibleContent>
|
|
|
|
|
+ <SidebarMenuSub>
|
|
|
|
|
+ <SidebarMenuSubItem />
|
|
|
|
|
+ </SidebarMenuSub>
|
|
|
|
|
+ </CollapsibleContent>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ </Collapsible>
|
|
|
|
|
+</SidebarMenu>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarMenuBadge
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenuItem`.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ FrameIcon,
|
|
|
|
|
+ LifeBuoyIcon,
|
|
|
|
|
+ MapIcon,
|
|
|
|
|
+ PieChartIcon,
|
|
|
|
|
+ SendIcon,
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuBadge,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const projects = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Design Engineering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: FrameIcon,
|
|
|
|
|
+ badge: "24",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Sales & Marketing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: PieChartIcon,
|
|
|
|
|
+ badge: "12",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Travel",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: MapIcon,
|
|
|
|
|
+ badge: "3",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Support",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: LifeBuoyIcon,
|
|
|
|
|
+ badge: "21",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Feedback",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SendIcon,
|
|
|
|
|
+ badge: "8",
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton
|
|
|
|
|
+ asChild
|
|
|
|
|
+ className="group-has-[[data-state=open]]/menu-item:bg-sidebar-accent"
|
|
|
|
|
+ >
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ <SidebarMenuBadge>{project.badge}</SidebarMenuBadge>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar menu with a badge.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton />
|
|
|
|
|
+ <SidebarMenuBadge>24</SidebarMenuBadge>
|
|
|
|
|
+</SidebarMenuItem>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarMenuSkeleton
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarMenuSkeleton` component is used to render a skeleton for a `SidebarMenu`. You can use this to show a loading state when using React Server Components, SWR or react-query.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+function NavProjectsSkeleton() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {Array.from({ length: 5 }).map((_, index) => (
|
|
|
|
|
+ <SidebarMenuItem key={index}>
|
|
|
|
|
+ <SidebarMenuSkeleton />
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarSeparator
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarSeparator` component is used to render a separator within a `Sidebar`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<Sidebar>
|
|
|
|
|
+ <SidebarHeader />
|
|
|
|
|
+ <SidebarSeparator />
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ <SidebarSeparator />
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+</Sidebar>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarTrigger
|
|
|
|
|
+
|
|
|
|
|
+Use the `SidebarTrigger` component to render a button that toggles the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarTrigger` component must be used within a `SidebarProvider`.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<SidebarProvider>
|
|
|
|
|
+ <Sidebar />
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <SidebarTrigger />
|
|
|
|
|
+ </main>
|
|
|
|
|
+</SidebarProvider>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Custom Trigger
|
|
|
|
|
+
|
|
|
|
|
+To create a custom trigger, you can use the `useSidebar` hook.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+import { useSidebar } from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+export function CustomTrigger() {
|
|
|
|
|
+ const { toggleSidebar } = useSidebar()
|
|
|
|
|
+
|
|
|
|
|
+ return <button onClick={toggleSidebar}>Toggle Sidebar</button>
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## SidebarRail
|
|
|
|
|
+
|
|
|
|
|
+The `SidebarRail` component is used to render a rail within a `Sidebar`. This rail can be used to toggle the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+<Sidebar>
|
|
|
|
|
+ <SidebarHeader />
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup />
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ <SidebarFooter />
|
|
|
|
|
+ <SidebarRail />
|
|
|
|
|
+</Sidebar>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Data Fetching
|
|
|
|
|
+
|
|
|
|
|
+### React Server Components
|
|
|
|
|
+
|
|
|
|
|
+Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+import * as React from "react"
|
|
|
|
|
+import {
|
|
|
|
|
+ FrameIcon,
|
|
|
|
|
+ LifeBuoyIcon,
|
|
|
|
|
+ MapIcon,
|
|
|
|
|
+ PieChartIcon,
|
|
|
|
|
+ SendIcon,
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarMenuSkeleton,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const projects = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Design Engineering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: FrameIcon,
|
|
|
|
|
+ badge: "24",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Sales & Marketing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: PieChartIcon,
|
|
|
|
|
+ badge: "12",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Travel",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: MapIcon,
|
|
|
|
|
+ badge: "3",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Support",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: LifeBuoyIcon,
|
|
|
|
|
+ badge: "21",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Feedback",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SendIcon,
|
|
|
|
|
+ badge: "8",
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+// Dummy fetch function
|
|
|
|
|
+async function fetchProjects() {
|
|
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 3000))
|
|
|
|
|
+ return projects
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <React.Suspense fallback={<NavProjectsSkeleton />}>
|
|
|
|
|
+ <NavProjects />
|
|
|
|
|
+ </React.Suspense>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function NavProjectsSkeleton() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {Array.from({ length: 5 }).map((_, index) => (
|
|
|
|
|
+ <SidebarMenuItem key={index}>
|
|
|
|
|
+ <SidebarMenuSkeleton showIcon />
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function NavProjects() {
|
|
|
|
|
+ const projects = await fetchProjects()
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A sidebar menu using React Server Components.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers {6} title="Skeleton to show loading state."
|
|
|
|
|
+function NavProjectsSkeleton() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {Array.from({ length: 5 }).map((_, index) => (
|
|
|
|
|
+ <SidebarMenuItem key={index}>
|
|
|
|
|
+ <SidebarMenuSkeleton showIcon />
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers {2} title="Server component fetching data."
|
|
|
|
|
+async function NavProjects() {
|
|
|
|
|
+ const projects = await fetchProjects()
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers {8-10} title="Usage with React Suspense."
|
|
|
|
|
+function AppSidebar() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <React.Suspense fallback={<NavProjectsSkeleton />}>
|
|
|
|
|
+ <NavProjects />
|
|
|
|
|
+ </React.Suspense>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### SWR and React Query
|
|
|
|
|
+
|
|
|
|
|
+You can use the same approach with [SWR](https://swr.vercel.app/) or [react-query](https://tanstack.com/query/latest/docs/framework/react/overview).
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="SWR"
|
|
|
|
|
+function NavProjects() {
|
|
|
|
|
+ const { data, isLoading } = useSWR("/api/projects", fetcher)
|
|
|
|
|
+
|
|
|
|
|
+ if (isLoading) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {Array.from({ length: 5 }).map((_, index) => (
|
|
|
|
|
+ <SidebarMenuItem key={index}>
|
|
|
|
|
+ <SidebarMenuSkeleton showIcon />
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ return ...
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {data.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers title="React Query"
|
|
|
|
|
+function NavProjects() {
|
|
|
|
|
+ const { data, isLoading } = useQuery()
|
|
|
|
|
+
|
|
|
|
|
+ if (isLoading) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {Array.from({ length: 5 }).map((_, index) => (
|
|
|
|
|
+ <SidebarMenuItem key={index}>
|
|
|
|
|
+ <SidebarMenuSkeleton showIcon />
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ return ...
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {data.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Controlled Sidebar
|
|
|
|
|
+
|
|
|
|
|
+Use the `open` and `onOpenChange` props to control the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+<figure className="mt-6 flex flex-col gap-4">
|
|
|
|
|
+ ```tsx
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import * as React from "react"
|
|
|
|
|
+import {
|
|
|
|
|
+ FrameIcon,
|
|
|
|
|
+ LifeBuoyIcon,
|
|
|
|
|
+ MapIcon,
|
|
|
|
|
+ PanelLeftCloseIcon,
|
|
|
|
|
+ PanelLeftOpenIcon,
|
|
|
|
|
+ PieChartIcon,
|
|
|
|
|
+ SendIcon,
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import { Button } from "@/components/ui/button"
|
|
|
|
|
+import {
|
|
|
|
|
+ Sidebar,
|
|
|
|
|
+ SidebarContent,
|
|
|
|
|
+ SidebarGroup,
|
|
|
|
|
+ SidebarGroupContent,
|
|
|
|
|
+ SidebarGroupLabel,
|
|
|
|
|
+ SidebarInset,
|
|
|
|
|
+ SidebarMenu,
|
|
|
|
|
+ SidebarMenuButton,
|
|
|
|
|
+ SidebarMenuItem,
|
|
|
|
|
+ SidebarProvider,
|
|
|
|
|
+} from "@/components/ui/sidebar"
|
|
|
|
|
+
|
|
|
|
|
+const projects = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Design Engineering",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: FrameIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Sales & Marketing",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: PieChartIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Travel",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: MapIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Support",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: LifeBuoyIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "Feedback",
|
|
|
|
|
+ url: "#",
|
|
|
|
|
+ icon: SendIcon,
|
|
|
|
|
+ },
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ const [open, setOpen] = React.useState(true)
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider open={open} onOpenChange={setOpen}>
|
|
|
|
|
+ <Sidebar>
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup>
|
|
|
|
|
+ <SidebarGroupLabel>Projects</SidebarGroupLabel>
|
|
|
|
|
+ <SidebarGroupContent>
|
|
|
|
|
+ <SidebarMenu>
|
|
|
|
|
+ {projects.map((project) => (
|
|
|
|
|
+ <SidebarMenuItem key={project.name}>
|
|
|
|
|
+ <SidebarMenuButton asChild>
|
|
|
|
|
+ <a href={project.url}>
|
|
|
|
|
+ <project.icon />
|
|
|
|
|
+ <span>{project.name}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ </SidebarMenuButton>
|
|
|
|
|
+ </SidebarMenuItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SidebarMenu>
|
|
|
|
|
+ </SidebarGroupContent>
|
|
|
|
|
+ </SidebarGroup>
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+ </Sidebar>
|
|
|
|
|
+ <SidebarInset>
|
|
|
|
|
+ <header className="flex h-12 items-center justify-between px-4">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => setOpen((open) => !open)}
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ >
|
|
|
|
|
+ {open ? <PanelLeftCloseIcon /> : <PanelLeftOpenIcon />}
|
|
|
|
|
+ <span>{open ? "Close" : "Open"} Sidebar</span>
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </header>
|
|
|
|
|
+ </SidebarInset>
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+ <figcaption className="text-center text-sm text-gray-500">
|
|
|
|
|
+ A controlled sidebar.
|
|
|
|
|
+ </figcaption>
|
|
|
|
|
+</figure>
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+export function AppSidebar() {
|
|
|
|
|
+ const [open, setOpen] = React.useState(false)
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SidebarProvider open={open} onOpenChange={setOpen}>
|
|
|
|
|
+ <Sidebar />
|
|
|
|
|
+ </SidebarProvider>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Theming
|
|
|
|
|
+
|
|
|
|
|
+We use the following CSS variables to theme the sidebar.
|
|
|
|
|
+
|
|
|
|
|
+```css
|
|
|
|
|
+@layer base {
|
|
|
|
|
+ :root {
|
|
|
|
|
+ --sidebar-background: 0 0% 98%;
|
|
|
|
|
+ --sidebar-foreground: 240 5.3% 26.1%;
|
|
|
|
|
+ --sidebar-primary: 240 5.9% 10%;
|
|
|
|
|
+ --sidebar-primary-foreground: 0 0% 98%;
|
|
|
|
|
+ --sidebar-accent: 240 4.8% 95.9%;
|
|
|
|
|
+ --sidebar-accent-foreground: 240 5.9% 10%;
|
|
|
|
|
+ --sidebar-border: 220 13% 91%;
|
|
|
|
|
+ --sidebar-ring: 217.2 91.2% 59.8%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dark {
|
|
|
|
|
+ --sidebar-background: 240 5.9% 10%;
|
|
|
|
|
+ --sidebar-foreground: 240 4.8% 95.9%;
|
|
|
|
|
+ --sidebar-primary: 0 0% 98%;
|
|
|
|
|
+ --sidebar-primary-foreground: 240 5.9% 10%;
|
|
|
|
|
+ --sidebar-accent: 240 3.7% 15.9%;
|
|
|
|
|
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
|
|
|
|
|
+ --sidebar-border: 240 3.7% 15.9%;
|
|
|
|
|
+ --sidebar-ring: 217.2 91.2% 59.8%;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**We intentionally use different variables for the sidebar and the rest of the application** to make it easy to have a sidebar that is styled differently from the rest of the application. Think a sidebar with a darker shade from the main application.
|
|
|
|
|
+
|
|
|
|
|
+## Styling
|
|
|
|
|
+
|
|
|
|
|
+Here are some tips for styling the sidebar based on different states.
|
|
|
|
|
+
|
|
|
|
|
+- **Styling an element based on the sidebar collapsible state.** The following will hide the `SidebarGroup` when the sidebar is in `icon` mode.
|
|
|
|
|
+
|
|
|
|
|
+```tsx
|
|
|
|
|
+<Sidebar collapsible="icon">
|
|
|
|
|
+ <SidebarContent>
|
|
|
|
|
+ <SidebarGroup className="group-data-[collapsible=icon]:hidden" />
|
|
|
|
|
+ </SidebarContent>
|
|
|
|
|
+</Sidebar>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- **Styling a menu action based on the menu button active state.** The following will force the menu action to be visible when the menu button is active.
|
|
|
|
|
+
|
|
|
|
|
+```tsx
|
|
|
|
|
+<SidebarMenuItem>
|
|
|
|
|
+ <SidebarMenuButton />
|
|
|
|
|
+ <SidebarMenuAction className="peer-data-[active=true]/menu-button:opacity-100" />
|
|
|
|
|
+</SidebarMenuItem>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+You can find more tips on using states for styling in this [Twitter thread](https://x.com/shadcn/status/1842329158879420864).
|
|
|
|
|
+
|
|
|
|
|
+## Changelog
|
|
|
|
|
+
|
|
|
|
|
+### 2024-10-30 Cookie handling in setOpen
|
|
|
|
|
+
|
|
|
|
|
+- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in `<SidebarProvider>`.
|
|
|
|
|
+
|
|
|
|
|
+Update the `setOpen` callback in `<SidebarProvider>` as follows:
|
|
|
|
|
+
|
|
|
|
|
+```tsx showLineNumbers
|
|
|
|
|
+const setOpen = React.useCallback(
|
|
|
|
|
+ (value: boolean | ((value: boolean) => boolean)) => {
|
|
|
|
|
+ const openState = typeof value === "function" ? value(open) : value
|
|
|
|
|
+ if (setOpenProp) {
|
|
|
|
|
+ setOpenProp(openState)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ _setOpen(openState)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // This sets the cookie to keep the sidebar state.
|
|
|
|
|
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
|
|
|
|
|
+ },
|
|
|
|
|
+ [setOpenProp, open]
|
|
|
|
|
+)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2024-10-21 Fixed `text-sidebar-foreground`
|
|
|
|
|
+
|
|
|
|
|
+- [#5491](https://github.com/shadcn-ui/ui/pull/5491) - Moved `text-sidebar-foreground` from `<SidebarProvider>` to `<Sidebar>` component.
|
|
|
|
|
+
|
|
|
|
|
+### 2024-10-20 Typo in `useSidebar` hook.
|
|
|
|
|
+
|
|
|
|
|
+Fixed typo in `useSidebar` hook.
|
|
|
|
|
+
|
|
|
|
|
+```diff showLineNumbers title="sidebar.tsx"
|
|
|
|
|
+- throw new Error("useSidebar must be used within a Sidebar.")
|
|
|
|
|
++ throw new Error("useSidebar must be used within a SidebarProvider.")
|
|
|
|
|
+```
|