diff --git a/apps/design-system/components/component-preview.tsx b/apps/design-system/components/component-preview.tsx index 1381d131a38..b9898033daa 100644 --- a/apps/design-system/components/component-preview.tsx +++ b/apps/design-system/components/component-preview.tsx @@ -97,7 +97,7 @@ export function ComponentPreview({
{showGrid && ( @@ -141,7 +141,7 @@ export function ComponentPreview({ return (
& ({ doc, children, ...props }, ref) => { const ShadcnPanel = () => { if (doc.source?.shadcn) { + const shadcnDocsUrl = doc.slugAsParams?.startsWith('components/') + ? `https://ui.shadcn.com/docs/${doc.slugAsParams}` + : 'https://ui.shadcn.com/' + return (
& shadcn/ui
- - This component is based on ui.shadcn - +
+ This component uses shadcn/ui + +
) } @@ -299,11 +313,11 @@ const SourcePanel = forwardRef & return (
+ - {/* */}
) } diff --git a/apps/design-system/content/docs/components/sidebar.mdx b/apps/design-system/content/docs/components/sidebar.mdx index db88a7747b3..9fe3ef86115 100644 --- a/apps/design-system/content/docs/components/sidebar.mdx +++ b/apps/design-system/content/docs/components/sidebar.mdx @@ -1,173 +1,32 @@ --- title: Sidebar -description: A composable, themeable and customizable sidebar component. +description: A composable, themeable sidebar for app navigation. component: true +source: + shadcn: true --- -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 - - - - - CLI - Manual - - - - - -Run the following command to install `sidebar.tsx` - -```bash -npx shadcn@latest add sidebar -``` - -Add the following colors to your CSS file - -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 title="app/globals.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: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --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%; - } -} -``` - - - - - - - - - -Copy and paste the following code into your project. - - - -Update the import paths to match your project setup. - -Add the following colors to your CSS file - -We'll go over the colors later in the [theming section](/docs/components/sidebar#theming). - -```css title="app/globals.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: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --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%; - } -} -``` - -Add the Sidebar tailwind config into `tailwind.config.js` - -Add the following object in the `theme.extend.colors` section of your `tailwind.config.js` file -this config enable sidebar related style utilities like bg-sidebar - -```javascript title="tailwind.config.js" -// ... -sidebar: { - DEFAULT: 'hsl(var(--sidebar-background))', - foreground: 'hsl(var(--sidebar-foreground))', - primary: 'hsl(var(--sidebar-primary))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - accent: 'hsl(var(--sidebar-accent))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', - border: 'hsl(var(--sidebar-border))', - ring: 'hsl(var(--sidebar-ring))', -}, -// ... -``` - - - - - - - -## 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`. - -Sidebar Structure -Sidebar Structure +Composable sidebar primitives for app chrome. The implementation lives in +`packages/ui` and is used by Studio. Import from `'ui'`. ## Usage -```tsx showLineNumbers title="app/layout.tsx" -import { AppSidebar } from '@/components/app-sidebar' -import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' +```tsx +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarHeader, + SidebarProvider, + SidebarTrigger, +} from 'ui' +``` -export default function Layout({ children }: { children: React.ReactNode }) { +```tsx +export function Layout({ children }: { children: React.ReactNode }) { return ( @@ -180,22 +39,13 @@ export default function Layout({ children }: { children: React.ReactNode }) { } ``` -```tsx showLineNumbers title="components/app-sidebar.tsx" -import { - Sidebar, - SidebarContent, - SidebarFooter, - SidebarGroup, - SidebarHeader, -} from '@/components/ui/sidebar' - +```tsx export function AppSidebar() { return ( - @@ -203,269 +53,101 @@ export function AppSidebar() { } ``` -## Your First Sidebar +## Structure -Let's start with the most basic sidebar. A collapsible sidebar with a menu. +| Part | Role | +| --------------------------------- | ---------------------------------------------------- | +| `SidebarProvider` | Open / collapsed state and keyboard shortcut | +| `Sidebar` | Collapsible shell (`side`, `variant`, `collapsible`) | +| `SidebarHeader` / `SidebarFooter` | Sticky top and bottom regions | +| `SidebarContent` | Scrollable middle | +| `SidebarGroup` | Section inside content | +| `SidebarMenu` and friends | Nav items, actions, badges, submenus | +| `SidebarTrigger` / `SidebarRail` | Toggle controls | - +## Theming -Add a `SidebarProvider` and `SidebarTrigger` at the root of your application. +Sidebar colours use **full colour** CSS variables (aliases into the semantic +system), not classic shadcn HSL channel lists. Do not wrap them in `hsl()`. -```tsx showLineNumbers title="app/layout.tsx" -import { AppSidebar } from '@/components/app-sidebar' -import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' +Shared defaults (also in `packages/ui` compat CSS, matching Studio): -export default function Layout({ children }: { children: React.ReactNode }) { - return ( - - -
- - {children} -
-
- ) +```css +:root { + --sidebar-background: var(--background-dash-sidebar); + --sidebar-foreground: var(--foreground-default); + --sidebar-primary: var(--foreground-default); + --sidebar-primary-foreground: var(--warning); + --sidebar-accent: var(--background-selection); + --sidebar-accent-foreground: var(--foreground-default); + --sidebar-border: var(--border-default); + --sidebar-ring: var(--ring); } ``` -Create a new sidebar component at `components/app-sidebar.tsx`. +Apps that import `config/tailwind.config.css` already map these to +`--color-sidebar*` in `packages/config/css/theme.css`, so utilities like +`bg-sidebar` and `text-sidebar-foreground` work without a local Tailwind colour +entry. -```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`. - -```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 ( - - - - Application - - - {items.map((item) => ( - - - - - {item.title} - - - - ))} - - - - - - ) -} -``` - -You've created your first sidebar. - -
- -## 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. +Override any `--sidebar-*` token when the sidebar should diverge from the main +app surface. ## 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. +Always wrap the sidebar and its trigger in `SidebarProvider`. -### 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). | +| Prop | Type | Description | +| -------------- | ------------------------- | ------------------------------- | +| `defaultOpen` | `boolean` | Uncontrolled initial open state | +| `open` | `boolean` | Controlled open state | +| `onOpenChange` | `(open: boolean) => void` | Controlled open setter | ### 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. +Override desktop width with `--sidebar-width` on the provider (also used for +layout spacing): -```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" +```tsx ``` -This will handle the width of the sidebar but also the layout spacing. +Defaults live as `SIDEBAR_WIDTH` / `SIDEBAR_WIDTH_ICON` in +`packages/ui/src/components/shadcn/ui/sidebar.tsx`. On mobile, the sheet sets +`--sidebar-width` from the hard-coded `SIDEBAR_WIDTH_MOBILE` constant, so there +is no separate CSS variable to override mobile width today. -### Keyboard Shortcut +### Persisted state -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 { AppSidebar } from '@/components/app-sidebar' -import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' - -export async function Layout({ children }: { children: React.ReactNode }) { - const cookieStore = await cookies() - const defaultOpen = cookieStore.get('sidebar_state')?.value === 'true' - - return ( - - -
- - {children} -
-
- ) -} -``` - -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' -``` +`SidebarProvider` writes open state to a cookie (`SIDEBAR_COOKIE_NAME`, default +`sidebar:state` in this repo). For Next.js App Router you can seed +`defaultOpen` from that cookie on the server. ## Sidebar -The main `Sidebar` component used to render a collapsible sidebar. +| Prop | Type | Description | +| ------------- | ------------------------------------ | ------------------ | +| `side` | `'left' \| 'right'` | Which edge | +| `variant` | `'sidebar' \| 'floating' \| 'inset'` | Visual treatment | +| `collapsible` | `'offcanvas' \| 'icon' \| 'none'` | Collapse behaviour | -```tsx showLineNumbers -import { Sidebar } from '@/components/ui/sidebar' +| `collapsible` | Behaviour | +| ------------- | ------------------ | +| `offcanvas` | Slides off-canvas | +| `icon` | Collapses to icons | +| `none` | Always expanded | -export function AppSidebar() { - return -} -``` +For `variant="inset"`, wrap main content in `SidebarInset`. -### 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 -} -``` - -### 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 -} -``` - - - **Note:** If you use the `inset` variant, remember to wrap your main content in a `SidebarInset` - component. - - -```tsx showLineNumbers +```tsx @@ -474,625 +156,52 @@ export function AppSidebar() { ``` -### 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 -} -``` - -| 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 `` to the `SidebarHeader`. - -```tsx showLineNumbers title="components/app-sidebar.tsx" - - - - - - - - Select Workspace - - - - - - Acme Inc - - - Acme Corp. - - - - - - - -``` - -## SidebarFooter - -Use the `SidebarFooter` component to add a sticky footer to the sidebar. - -The following example adds a `` to the `SidebarFooter`. - -```tsx showLineNumbers title="components/app-sidebar.tsx" -export function AppSidebar() { - return ( - - - - - - - - - - - Username - - - - - - Account - - - Billing - - - Sign out - - - - - - - - - ) -} -``` - -## 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 ( - - - - - - - ) -} -``` - -## SidebarGroup - -Use the `SidebarGroup` component to create a section within the sidebar. - -A `SidebarGroup` has a `SidebarGroupLabel`, a `SidebarGroupContent` and an optional `SidebarGroupAction`. - -```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`. - -```tsx showLineNumbers -export function AppSidebar() { - return ( - - - - - Help - - - - - - - - - ) -} -``` - - - **Note:** We wrap the `CollapsibleTrigger` in a `SidebarGroupLabel` to render a button. - - -## SidebarGroupAction - -Use the `SidebarGroupAction` component to add an action button to the `SidebarGroup`. - -```tsx showLineNumbers {5-7} -export function AppSidebar() { - return ( - - Projects - - Add Project - - - - ) -} -``` - -## SidebarMenu - -The `SidebarMenu` component is used for building a menu within a `SidebarGroup`. - -A `SidebarMenu` component is composed of `SidebarMenuItem`, `SidebarMenuButton`, `` and `` components. - -Sidebar Menu -Sidebar Menu - -Here's an example of a `SidebarMenu` component rendering 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`. - -### 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 - - Home - -``` - -### Icon and Label - -You can render an icon and a truncated label inside the button. Remember to wrap the label in a ``. - -```tsx showLineNumbers - - - - Home - - -``` - -### isActive - -Use the `isActive` prop to mark a menu item as active. - -```tsx showLineNumbers - - Home - -``` - -## 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 `` as a clickable link and the `` as a button. - -```tsx showLineNumbers - - - - - Home - - - - Add Project - - -``` - -### DropdownMenu - -Here's an example of a `SidebarMenuAction` component rendering a `DropdownMenu`. - -```tsx showLineNumbers - - - - - Home - - - - - - - - - - - Edit Project - - - Delete Project - - - - -``` - -## SidebarMenuSub - -The `SidebarMenuSub` component is used to render a submenu within a `SidebarMenu`. - -Use `` and `` to render a submenu item. - -```tsx showLineNumbers - - - - - - - - - - - -``` - -## Collapsible SidebarMenu - -To make a `SidebarMenu` component collapsible, wrap it and the `SidebarMenuSub` components in a `Collapsible`. - -```tsx showLineNumbers - - - - - - - - - - - - - - -``` - -## SidebarMenuBadge - -The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenuItem`. - -```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, SWR or react-query. - -```tsx showLineNumbers -function NavProjectsSkeleton() { - return ( - - {Array.from({ length: 5 }).map((_, index) => ( - - - - ))} - - ) -} -``` - -## SidebarSeparator - -The `SidebarSeparator` component is used to render a separator within a `Sidebar`. - -```tsx showLineNumbers - - - - - - - - - -``` - -## SidebarTrigger - -Use the `SidebarTrigger` component to render a button that toggles the sidebar. - -The `SidebarTrigger` component must be used within a `SidebarProvider`. - -```tsx showLineNumbers - - -
- -
-
-``` - -### Custom Trigger - -To create a custom trigger, you can use the `useSidebar` hook. - -```tsx showLineNumbers -import { useSidebar } from '@/components/ui/sidebar' +```tsx +import { useSidebar } from 'ui' export function CustomTrigger() { const { toggleSidebar } = useSidebar() - return } ``` -## SidebarRail +Must be used under `SidebarProvider`. -The `SidebarRail` component is used to render a rail within a `Sidebar`. This rail can be used to toggle the sidebar. +## Menu building blocks -```tsx showLineNumbers - - - - - - - - -``` +Typical nav group: -## Data Fetching - -### React Server Components - -Here's an example of a `SidebarMenu` component rendering a list of projects using React Server Components. - -```tsx showLineNumbers {6} title="Skeleton to show loading state." -function NavProjectsSkeleton() { - return ( +```tsx + + Application + - {Array.from({ length: 5 }).map((_, index) => ( - - - - ))} - - ) -} -``` - -```tsx showLineNumbers {2} title="Server component fetching data." -async function NavProjects() { - const projects = await fetchProjects() - - return ( - - {projects.map((project) => ( - + {items.map((item) => ( + - - - {project.name} + + + {item.title} ))} - ) -} + + ``` -```tsx showLineNumbers {8-10} title="Usage with React Suspense." -function AppSidebar() { - return ( - - - - Projects - - }> - - - - - - - ) -} -``` +Also available: `SidebarMenuAction`, `SidebarMenuBadge`, `SidebarMenuSkeleton`, +`SidebarMenuSub` / `SidebarMenuSubButton`, `SidebarGroupAction`, +`SidebarSeparator`, `SidebarRail`. -### SWR and React Query +## Controlled sidebar -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 ( - - {Array.from({ length: 5 }).map((_, index) => ( - - - - ))} - - ) - } - - if (!data) { - return ... - } - - return ( - - {data.map((project) => ( - - - - - {project.name} - - - - ))} - - ) -} -``` - -```tsx showLineNumbers title="React Query" -function NavProjects() { - const { data, isLoading } = useQuery() - - if (isLoading) { - return ( - - {Array.from({ length: 5 }).map((_, index) => ( - - - - ))} - - ) - } - - if (!data) { - return ... - } - - return ( - - {data.map((project) => ( - - - - - {project.name} - - - - ))} - - ) -} -``` - -## Controlled Sidebar - -Use the `open` and `onOpenChange` props to control the sidebar. - -```tsx showLineNumbers +```tsx export function AppSidebar() { - const [open, setOpen] = React.useState(false) + const [open, setOpen] = React.useState(true) return ( @@ -1102,43 +211,9 @@ export function AppSidebar() { } ``` -## Theming +## Styling with state -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. +Hide a group when the sidebar is icon-collapsed: ```tsx @@ -1148,7 +223,7 @@ Here are some tips for styling the sidebar based on different states. ``` -- **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. +Keep a menu action visible when its button is active: ```tsx @@ -1157,42 +232,6 @@ Here are some tips for styling the sidebar based on different states. ``` -You can find more tips on using states for styling in this [Twitter thread](https://x.com/shadcn/status/1842329158879420864). +## Source -## Changelog - -### 2024-10-30 Cookie handling in setOpen - -- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in ``. - -Update the `setOpen` callback in `` 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 `` to `` 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.") -``` +`packages/ui/src/components/shadcn/ui/sidebar.tsx` diff --git a/apps/design-system/registry/default/example/sidebar-demo.tsx b/apps/design-system/registry/default/example/sidebar-demo.tsx new file mode 100644 index 00000000000..d8d8a14450b --- /dev/null +++ b/apps/design-system/registry/default/example/sidebar-demo.tsx @@ -0,0 +1,69 @@ +'use client' + +import { Calendar, Home, Inbox, Search, Settings } from 'lucide-react' +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarInset, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarProvider, + SidebarRail, + SidebarTrigger, +} from 'ui' + +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 }, +] + +const menuButtonClassName = + '[&>svg]:size-4 text-foreground-muted data-[active=true]:text-foreground' + +export default function SidebarDemo() { + return ( +
+ + + + + Application + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + + +
+ +
+
+
+
+ ) +} diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index 0a47eeaa6c8..dc2f26e11be 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -880,6 +880,12 @@ export const examples: Registry = [ registryDependencies: ['separator'], files: ['example/separator-demo.tsx'], }, + { + name: 'sidebar-demo', + type: 'components:example', + registryDependencies: ['sidebar'], + files: ['example/sidebar-demo.tsx'], + }, { name: 'sheet-confirm-on-close-demo', type: 'components:example', diff --git a/apps/studio/styles/globals.css b/apps/studio/styles/globals.css index 7c2183b1fc0..b515ccbe0c8 100644 --- a/apps/studio/styles/globals.css +++ b/apps/studio/styles/globals.css @@ -148,29 +148,10 @@ } :root { - --sidebar-background: var(--background-dash-sidebar); - --sidebar-foreground: var(--foreground-default); - --sidebar-primary: var(--foreground-default); - --sidebar-primary-foreground: var(--warning); - --sidebar-accent: var(--background-selection); - --sidebar-accent-foreground: var(--foreground-default); - --sidebar-border: var(--border-default); - --sidebar-ring: var(--ring); + /* --sidebar-* aliases live in packages/ui compat.css (shared with other apps). */ --header-height: 3rem; } -[data-theme='dark'], -.dark { - --sidebar-background: var(--background-dash-sidebar); - --sidebar-foreground: var(--foreground-default); - --sidebar-primary: var(--foreground-default); - --sidebar-primary-foreground: var(--warning); - --sidebar-accent: var(--background-selection); - --sidebar-accent-foreground: var(--foreground-default); - --sidebar-border: var(--border-default); - --sidebar-ring: var(--ring); -} - @layer base { *, ::after, diff --git a/packages/ui/build/css/source/compat.css b/packages/ui/build/css/source/compat.css index 23ea0b3b44d..788f8e219eb 100644 --- a/packages/ui/build/css/source/compat.css +++ b/packages/ui/build/css/source/compat.css @@ -53,4 +53,16 @@ ); --border-button-default: var(--border); --border-button-hover: var(--border-stronger); + + /* Sidebar aliases — full colours for bg-sidebar / text-sidebar-* utilities. + * Classic shadcn stores these as HSL channel lists and wraps them in hsl(). + * In this monorepo they alias the semantic system (same as Studio). */ + --sidebar-background: var(--background-dash-sidebar); + --sidebar-foreground: var(--foreground-default); + --sidebar-primary: var(--foreground-default); + --sidebar-primary-foreground: var(--warning); + --sidebar-accent: var(--background-selection); + --sidebar-accent-foreground: var(--foreground-default); + --sidebar-border: var(--border-default); + --sidebar-ring: var(--ring); } diff --git a/packages/ui/src/components/shadcn/ui/sidebar.tsx b/packages/ui/src/components/shadcn/ui/sidebar.tsx index 24fdc0b08cc..724312aaec2 100644 --- a/packages/ui/src/components/shadcn/ui/sidebar.tsx +++ b/packages/ui/src/components/shadcn/ui/sidebar.tsx @@ -277,7 +277,7 @@ const SidebarTrigger = React.forwardRef< }} {...props} > - + Toggle Sidebar )