diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx index 50649437ce..0c22c8aee8 100644 --- a/apps/www/components/mdx-components.tsx +++ b/apps/www/components/mdx-components.tsx @@ -141,13 +141,16 @@ const components = { table: ({ className, ...props }: React.HTMLAttributes) => (
), tr: ({ className, ...props }: React.HTMLAttributes) => ( - + ), th: ({ className, ...props }: React.HTMLAttributes) => (
Add the following colors to your CSS file -We'll go over the colors later in the theming section. +We'll go over the colors later in the [theming section](/docs/components/sidebar#theming). -```css +```css title="app/globals.css" @layer base { :root { --sidebar-background: 0 0% 98%; @@ -157,27 +157,13 @@ A `Sidebar` component is composed of the following parts: ## Usage ```tsx showLineNumbers title="app/layout.tsx" -import { - Sidebar, - SidebarContent, - SidebarFooter, - SidebarGroup, - SidebarHeader, - SidebarProvider, - SidebarTrigger, -} from "@/components/ui/sidebar" +import { SidebarProvider } from "@/components/ui/sidebar" +import { AppSidebar } from "@/components/app-sidebar" export default function Layout({ children }: { children: React.ReactNode }) { return ( - - - - - - - - +
{children} @@ -187,45 +173,40 @@ export default function Layout({ children }: { children: React.ReactNode }) { } ``` -## Your First Sidebar - -Let's start with the most basic sidebar. - - - -Create a new sidebar component. Let's call it `app-sidebar.tsx`. - ```tsx showLineNumbers title="components/app-sidebar.tsx" -import { Sidebar, SidebarContent } from "@/components/ui/sidebar" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarHeader, +} from "@/components/ui/sidebar" export function AppSidebar() { return ( - + + + + + + ) } ``` -Add a `SidebarProvider` at the root of your application. +## Your First Sidebar -```tsx showLineNumbers {6,7,9} title="app/layout.tsx" -import { SidebarProvider } from "@/components/ui/sidebar" -import { AppSidebar } from "@/components/app-sidebar" +Let's start with the most basic sidebar. A collapsible sidebar with a menu. -export default function Layout({ children }: { children: React.ReactNode }) { - return ( - - -
{children}
-
- ) -} -``` + -Add a `SidebarTrigger` to your application. + + Add a `SidebarProvider` and `SidebarTrigger` at the root of your application. + -```tsx showLineNumbers /SidebarTrigger/ title="app/layout.tsx" +```tsx showLineNumbers title="app/layout.tsx" import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" import { AppSidebar } from "@/components/app-sidebar" @@ -242,6 +223,20 @@ export default function Layout({ children }: { children: React.ReactNode }) { } ``` +Create a new sidebar component at `components/app-sidebar.tsx`. + +```tsx showLineNumbers title="components/app-sidebar.tsx" +import { Sidebar, SidebarContent } from "@/components/ui/sidebar" + +export function AppSidebar() { + return ( + + + + ) +} +``` + Now, let's add a `SidebarMenu` to the sidebar. We'll use the `SidebarMenu` component in a `SidebarGroup`. @@ -255,12 +250,9 @@ import { SidebarGroup, SidebarGroupContent, SidebarGroupLabel, - SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, - SidebarProvider, - SidebarTrigger, } from "@/components/ui/sidebar" // Menu items. @@ -321,8 +313,6 @@ export function AppSidebar() { You've created your first sidebar. - -
+
+ ## Components -The `Sidebar` components are built to be composable i.e you build you sidebar by putting the provided components together. They also compose well with other shadcn/ui components such as `DropdownMenu`, `Collapsible` or `Dialog` etc. +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. +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 -| Prop | Description | -| ------------- | ------------------------------------------------------------- | -| `defaultOpen` | Whether the sidebar should be open by default. | -| `open` | Whether the sidebar should be open (controlled). | -| `setOpen` | A function to set the open state of the sidebar (controlled). | +| Name | Type | Description | +| ------------- | ------------------------- | -------------------------------------------- | +| `defaultOpen` | `boolean` | Default open state of the sidebar. | +| `open` | `boolean` | Open state of the sidebar (controlled). | +| `setOpen` | `(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 to set the width of the sidebar. +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" @@ -371,14 +367,16 @@ To set the width of the sidebar, you can use the `--sidebar-width` and `--sideba "--sidebar-width": "20rem", "--sidebar-width-mobile": "20rem", }} -/> +> + + ``` 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 to open and close the sidebar. +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. @@ -400,6 +398,14 @@ export function AppSidebar() { } ``` +### 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. @@ -498,7 +504,20 @@ Use the `SidebarHeader` component to add a sticky header to the sidebar. The following example adds a `` to the `SidebarHeader`. -```tsx showLineNumbers +
+ +
+ A sidebar header with a dropdown menu. +
+
+ +```tsx showLineNumbers title="components/app-sidebar.tsx" @@ -525,48 +544,27 @@ The following example adds a `` to the `SidebarHeader`. ``` -
- -
- A sidebar with a dropdown menu. -
-
- ## SidebarFooter Use the `SidebarFooter` component to add a sticky footer to the sidebar. The following example adds a `` to the `SidebarFooter`. -```tsx showLineNumbers -import { ChevronDown, ChevronUp, User2 } from "lucide-react" +
+ +
+ A sidebar footer with a dropdown menu. +
+
-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 default function AppSidebar() { +```tsx showLineNumbers title="components/app-sidebar.tsx" +export function AppSidebar() { return ( @@ -606,19 +604,6 @@ export default function AppSidebar() { } ``` -
- -
- A sidebar with a dropdown menu. -
-
- ## SidebarContent The `SidebarContent` component is used to wrap the content of the sidebar. This is where you add your `SidebarGroup` components. It is scrollable. @@ -640,26 +625,11 @@ export function AppSidebar() { ## SidebarGroup -Use the `SidebarGroup` component to create a section within the sidebar. It has a `SidebarGroupLabel` and a `SidebarGroupContent`. +Use the `SidebarGroup` component to create a section within the sidebar. -```tsx showLineNumbers -import { Sidebar, SidebarContent, SidebarGroup } from "@/components/ui/sidebar" +A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an optional `SidebarGroupAction`. -export function AppSidebar() { - return ( - - - - Application - - - - - ) -} -``` - -
+
+```tsx showLineNumbers +import { Sidebar, SidebarContent, SidebarGroup } from "@/components/ui/sidebar" + +export function AppSidebar() { + return ( + + + + Application + + Add Project + + + + + + ) +} +``` + ## Collapsible SidebarGroup To make a `SidebarGroup` collapsible, wrap it in a `Collapsible`. -Note: we wrap the `CollapsibleTrigger` in a `SidebarGroupLabel` to render a button. +
+ +
+ A collapsible sidebar group. +
+
```tsx showLineNumbers export function AppSidebar() { @@ -698,18 +699,10 @@ export function AppSidebar() { } ``` -
- -
- A collapsible sidebar group. -
-
+ + **Note:** We wrap the `CollapsibleTrigger` in a `SidebarGroupLabel` to render + a button. + ## SidebarGroupAction @@ -744,9 +737,9 @@ export function AppSidebar() { ## SidebarMenu -The `SidebarMenu` component is used for building a menu within a `Sidebar`. +The `SidebarMenu` component is used for building a menu within a `SidebarGroup`. -A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton` and `` components. +A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton`, `` and `` components. - - - - Projects - - - {projects.map((project) => ( - - - - - {project.name} - - - - ))} - - - - - - - ) -} -``` - -
+
- A sidebar menu. + A sidebar menu with a list of projects.
+```tsx showLineNumbers + + + + Projects + + + {projects.map((project) => ( + + + + + {project.name} + + + + ))} + + + + + +``` + ## SidebarMenuButton The `SidebarMenuButton` component is used to render a menu button within a `SidebarMenuItem`. @@ -829,23 +804,9 @@ The `SidebarMenuButton` component is used to render a menu button within a `Side 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 -import { - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from "@/components/ui/sidebar" - -export function AppSidebar() { - return ( - - - - Home - - - - ) -} + + Home + ``` ### Icon and Label @@ -866,9 +827,9 @@ You can render an icon and a truncated label inside the button. Remember to wrap Use the `isActive` prop to mark a menu item as active. ```tsx showLineNumbers - - - + + Home + ``` ## SidebarMenuAction @@ -895,6 +856,19 @@ This button works independently of the `SidebarMenuButton` i.e you can have the Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`. +
+ +
+ A sidebar menu action with a dropdown menu. +
+
+ ```tsx showLineNumbers @@ -921,25 +895,25 @@ Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`. ``` -
- -
- A sidebar menu action. -
-
- ## SidebarMenuSub The `SidebarMenuSub` component is used to render a submenu within a `SidebarMenu`. Use `` and `` to render a submenu item. +
+ +
+ A sidebar menu with a submenu. +
+
+ ```tsx showLineNumbers @@ -954,23 +928,23 @@ Use `` and `` to render a submenu ``` -
- -
- A sidebar menu with a submenu. -
-
- ## Collapsible SidebarMenu To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` components in a `Collapsible`. +
+ +
+ A collapsible sidebar menu. +
+
+ ```tsx showLineNumbers @@ -988,31 +962,11 @@ To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` ``` -
- -
- A collapsible sidebar menu. -
-
- ## SidebarMenuBadge The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenuItem`. -```tsx showLineNumbers - - - 24 - -``` - -
+
+```tsx showLineNumbers + + + 24 + +``` + ## 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 or swr or react-query. +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() { @@ -1109,6 +1070,19 @@ The `SidebarRail` component is used to render a rail within a `Sidebar`. This ra Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components. +
+ +
+ A sidebar menu using React Server Components. +
+
+ ```tsx showLineNumbers {6} title="Skeleton to show loading state." function NavProjectsSkeleton() { return ( @@ -1163,19 +1137,6 @@ function AppSidebar() { } ``` -
- -
- Right-click and reload frame to see the skeleton. -
-
- ### 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). diff --git a/apps/www/registry/default/block/demo-sidebar-footer.tsx b/apps/www/registry/default/block/demo-sidebar-footer.tsx index ca01875a09..e67d2bb8d5 100644 --- a/apps/www/registry/default/block/demo-sidebar-footer.tsx +++ b/apps/www/registry/default/block/demo-sidebar-footer.tsx @@ -32,7 +32,7 @@ export default function AppSidebar() { - + Username diff --git a/apps/www/registry/default/block/demo-sidebar-header.tsx b/apps/www/registry/default/block/demo-sidebar-header.tsx index dbe5ed607c..cb1accc46e 100644 --- a/apps/www/registry/default/block/demo-sidebar-header.tsx +++ b/apps/www/registry/default/block/demo-sidebar-header.tsx @@ -28,7 +28,7 @@ export default function AppSidebar() { - + Select Workspace diff --git a/apps/www/registry/new-york/block/demo-sidebar-footer.tsx b/apps/www/registry/new-york/block/demo-sidebar-footer.tsx index 5c27b24ace..831581140d 100644 --- a/apps/www/registry/new-york/block/demo-sidebar-footer.tsx +++ b/apps/www/registry/new-york/block/demo-sidebar-footer.tsx @@ -32,8 +32,8 @@ export default function AppSidebar() { - - Username + + Username diff --git a/apps/www/registry/new-york/block/demo-sidebar-group-action.tsx b/apps/www/registry/new-york/block/demo-sidebar-group-action.tsx index 5b38363041..f1fb434f9a 100644 --- a/apps/www/registry/new-york/block/demo-sidebar-group-action.tsx +++ b/apps/www/registry/new-york/block/demo-sidebar-group-action.tsx @@ -9,6 +9,7 @@ import { Plus, Send, } from "lucide-react" +import { Toaster, toast } from "sonner" import { Sidebar, @@ -26,11 +27,20 @@ import { export default function AppSidebar() { return ( + Projects - + toast("You clicked the group action!")} + > Add Project diff --git a/apps/www/registry/new-york/block/demo-sidebar-header.tsx b/apps/www/registry/new-york/block/demo-sidebar-header.tsx index 38b41ee99e..5b496d2883 100644 --- a/apps/www/registry/new-york/block/demo-sidebar-header.tsx +++ b/apps/www/registry/new-york/block/demo-sidebar-header.tsx @@ -28,7 +28,7 @@ export default function AppSidebar() { - + Select Workspace diff --git a/apps/www/registry/new-york/ui/sidebar.tsx b/apps/www/registry/new-york/ui/sidebar.tsx index 999b778341..68c3a82683 100644 --- a/apps/www/registry/new-york/ui/sidebar.tsx +++ b/apps/www/registry/new-york/ui/sidebar.tsx @@ -52,14 +52,14 @@ const SidebarProvider = React.forwardRef< React.ComponentProps<"div"> & { defaultOpen?: boolean open?: boolean - setOpen?: (open: boolean) => void + onOpenChange?: (open: boolean) => void } >( ( { defaultOpen = true, open: openProp, - setOpen: setOpenProp, + onOpenChange: setOpenProp, className, style, children, @@ -75,12 +75,14 @@ const SidebarProvider = React.forwardRef< const [_open, _setOpen] = React.useState(defaultOpen) const open = openProp ?? _open const setOpen = React.useCallback( - (open: boolean) => { + (value: boolean | ((value: boolean) => boolean)) => { if (setOpenProp) { - return setOpenProp?.(open) + return setOpenProp?.( + typeof value === "function" ? value(open) : value + ) } - _setOpen(open) + _setOpen(value) // This sets the cookie to keep the sidebar state. document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` @@ -90,15 +92,14 @@ const SidebarProvider = React.forwardRef< // Helper to toggle the sidebar. const toggleSidebar = React.useCallback(() => { - if (isMobile) { - return setOpenMobile(!openMobile) - } - return setOpen(!open) - }, [isMobile, open, setOpen, openMobile, setOpenMobile]) + return isMobile + ? setOpenMobile((open) => !open) + : setOpen((open) => !open) + }, [isMobile, setOpen, setOpenMobile]) // Adds a keyboard shortcut to toggle the sidebar. - const handleKeyDown = React.useCallback( - (event: KeyboardEvent) => { + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { if ( event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) @@ -106,13 +107,11 @@ const SidebarProvider = React.forwardRef< event.preventDefault() toggleSidebar() } - }, - [toggleSidebar] - ) - React.useEffect(() => { + } + window.addEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown) - }, [handleKeyDown]) + }, [toggleSidebar]) // We add a state so that we can do data-state="expanded" or "collapsed". // This makes it easier to style the sidebar with Tailwind classes. @@ -630,7 +629,7 @@ const SidebarMenuBadge = React.forwardRef< ref={ref} data-sidebar="menu-badge" className={cn( - "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground", + "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none", "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", "peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=default]/menu-button:top-1.5",