diff --git a/apps/www/__registry__/default/block/demo-sidebar-rsc.tsx b/apps/www/__registry__/default/block/demo-sidebar-rsc.tsx new file mode 100644 index 0000000000..8912b2e18b --- /dev/null +++ b/apps/www/__registry__/default/block/demo-sidebar-rsc.tsx @@ -0,0 +1,104 @@ +import * as React from "react" +import { Frame, LifeBuoy, Map, PieChart, Send } from "lucide-react" + +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSkeleton, + SidebarProvider, +} from "@/registry/default/ui/sidebar" + +const projects = [ + { + name: "Design Engineering", + url: "#", + icon: Frame, + badge: "24", + }, + { + name: "Sales & Marketing", + url: "#", + icon: PieChart, + badge: "12", + }, + { + name: "Travel", + url: "#", + icon: Map, + badge: "3", + }, + { + name: "Support", + url: "#", + icon: LifeBuoy, + badge: "21", + }, + { + name: "Feedback", + url: "#", + icon: Send, + badge: "8", + }, +] + +// Dummy fetch function +async function fetchProjects() { + await new Promise((resolve) => setTimeout(resolve, 3000)) + return projects +} + +export default function AppSidebar() { + return ( + + + + + Projects + + }> + + + + + + + + ) +} + +function NavProjectsSkeleton() { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) +} + +async function NavProjects() { + const projects = await fetchProjects() + + return ( + + {projects.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} diff --git a/apps/www/__registry__/default/block/sidebar-02/page.tsx b/apps/www/__registry__/default/block/sidebar-02/page.tsx index c7271d9abd..58926063f6 100644 --- a/apps/www/__registry__/default/block/sidebar-02/page.tsx +++ b/apps/www/__registry__/default/block/sidebar-02/page.tsx @@ -23,7 +23,7 @@ export default function Page() { -
+
diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 2c22c76eed..7e75572b17 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -429,7 +429,7 @@ export const Index: Record = { name: "sidebar", description: "", type: "registry:ui", - registryDependencies: ["button","separator","sheet","tooltip","input","use-mobile"], + registryDependencies: ["button","separator","sheet","tooltip","input","use-mobile","skeleton"], files: ["registry/new-york/ui/sidebar.tsx"], component: React.lazy(() => import("@/registry/new-york/ui/sidebar.tsx")), source: "", @@ -2477,6 +2477,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "demo-sidebar-rsc": { + name: "demo-sidebar-rsc", + description: "", + type: "registry:block", + registryDependencies: undefined, + files: ["registry/new-york/block/demo-sidebar-rsc.tsx"], + component: React.lazy(() => import("@/registry/new-york/block/demo-sidebar-rsc.tsx")), + source: "__registry__/new-york/block/demo-sidebar-rsc.tsx", + category: "", + subcategory: "", + chunks: [] + }, "sidebar-01": { name: "sidebar-01", description: "A simple sidebar with navigation grouped by section.", @@ -3970,7 +3982,7 @@ export const Index: Record = { name: "sidebar", description: "", type: "registry:ui", - registryDependencies: ["button","separator","sheet","tooltip","input","use-mobile"], + registryDependencies: ["button","separator","sheet","tooltip","input","use-mobile","skeleton"], files: ["registry/default/ui/sidebar.tsx"], component: React.lazy(() => import("@/registry/default/ui/sidebar.tsx")), source: "", @@ -6018,6 +6030,18 @@ export const Index: Record = { subcategory: "", chunks: [] }, + "demo-sidebar-rsc": { + name: "demo-sidebar-rsc", + description: "", + type: "registry:block", + registryDependencies: undefined, + files: ["registry/default/block/demo-sidebar-rsc.tsx"], + component: React.lazy(() => import("@/registry/default/block/demo-sidebar-rsc.tsx")), + source: "__registry__/default/block/demo-sidebar-rsc.tsx", + category: "", + subcategory: "", + chunks: [] + }, "sidebar-01": { name: "sidebar-01", description: "A simple sidebar with navigation grouped by section.", diff --git a/apps/www/__registry__/new-york/block/demo-sidebar-menu-badge.tsx b/apps/www/__registry__/new-york/block/demo-sidebar-menu-badge.tsx index 4eef27bfff..9fc6013449 100644 --- a/apps/www/__registry__/new-york/block/demo-sidebar-menu-badge.tsx +++ b/apps/www/__registry__/new-york/block/demo-sidebar-menu-badge.tsx @@ -1,20 +1,7 @@ "use client" -import { - Frame, - LifeBuoy, - Map, - MoreHorizontal, - PieChart, - Send, -} from "lucide-react" +import { Frame, LifeBuoy, Map, PieChart, Send } from "lucide-react" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/registry/new-york/ui/dropdown-menu" import { Sidebar, SidebarContent, @@ -22,7 +9,6 @@ import { SidebarGroupContent, SidebarGroupLabel, SidebarMenu, - SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, diff --git a/apps/www/__registry__/new-york/block/demo-sidebar-rsc.tsx b/apps/www/__registry__/new-york/block/demo-sidebar-rsc.tsx new file mode 100644 index 0000000000..61a6237591 --- /dev/null +++ b/apps/www/__registry__/new-york/block/demo-sidebar-rsc.tsx @@ -0,0 +1,104 @@ +import * as React from "react" +import { Frame, LifeBuoy, Map, PieChart, Send } from "lucide-react" + +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSkeleton, + SidebarProvider, +} from "@/registry/new-york/ui/sidebar" + +const projects = [ + { + name: "Design Engineering", + url: "#", + icon: Frame, + badge: "24", + }, + { + name: "Sales & Marketing", + url: "#", + icon: PieChart, + badge: "12", + }, + { + name: "Travel", + url: "#", + icon: Map, + badge: "3", + }, + { + name: "Support", + url: "#", + icon: LifeBuoy, + badge: "21", + }, + { + name: "Feedback", + url: "#", + icon: Send, + badge: "8", + }, +] + +// Dummy fetch function +async function fetchProjects() { + await new Promise((resolve) => setTimeout(resolve, 3000)) + return projects +} + +export default function AppSidebar() { + return ( + + + + + Projects + + }> + + + + + + + + ) +} + +function NavProjectsSkeleton() { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) +} + +async function NavProjects() { + const projects = await fetchProjects() + + return ( + + {projects.map((project) => ( + + + + + {project.name} + + + + ))} + + ) +} diff --git a/apps/www/__registry__/new-york/block/sidebar-02/page.tsx b/apps/www/__registry__/new-york/block/sidebar-02/page.tsx index dca9547605..e5ae98e9fd 100644 --- a/apps/www/__registry__/new-york/block/sidebar-02/page.tsx +++ b/apps/www/__registry__/new-york/block/sidebar-02/page.tsx @@ -23,7 +23,7 @@ export default function Page() { -
+
diff --git a/apps/www/content/docs/components/sidebar.mdx b/apps/www/content/docs/components/sidebar.mdx index 000f63a200..f650d76aa7 100644 --- a/apps/www/content/docs/components/sidebar.mdx +++ b/apps/www/content/docs/components/sidebar.mdx @@ -13,7 +13,7 @@ component: true className="w-full" />
- A simple collapsible sidebar. + A sidebar that collapses to icons.
@@ -50,15 +50,15 @@ npx shadcn@latest add sidebar The command above should install the colors for you. It not, copy and paste the following in 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 @layer base { :root { --sidebar-background: 0 0% 98%; --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 220 100% 50%; - --sidebar-primary-foreground: 0 0% 100%; + --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%; @@ -68,8 +68,8 @@ We'll go over the colors later in the theming section. .dark { --sidebar-background: 240 5.9% 10%; --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 220 100% 50%; - --sidebar-primary-foreground: 0 0% 100%; + --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%; @@ -101,8 +101,8 @@ We'll go over the colors later in the theming section. :root { --sidebar-background: 0 0% 98%; --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 220 100% 50%; - --sidebar-primary-foreground: 0 0% 100%; + --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%; @@ -112,8 +112,8 @@ We'll go over the colors later in the theming section. .dark { --sidebar-background: 240 5.9% 10%; --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 220 100% 50%; - --sidebar-primary-foreground: 0 0% 100%; + --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%; @@ -156,7 +156,7 @@ A `Sidebar` component is composed of the following parts: ## Usage -```tsx showLineNumbers {12-20,22} +```tsx showLineNumbers title="app/layout.tsx" import { Sidebar, SidebarContent, @@ -164,6 +164,7 @@ import { SidebarGroup, SidebarHeader, SidebarProvider, + SidebarTrigger, } from "@/components/ui/sidebar" export default function Layout({ children }: { children: React.ReactNode }) { @@ -177,7 +178,10 @@ export default function Layout({ children }: { children: React.ReactNode }) { -
{children}
+
+ + {children} +
) } @@ -238,8 +242,6 @@ export default function Layout({ children }: { children: React.ReactNode }) { } ``` -If you click the trigger, the sidebar should slide in and out from the left. - Now, let's add a `SidebarMenu` to the sidebar. We'll use the `SidebarMenu` component in a `SidebarGroup`. @@ -317,6 +319,8 @@ export function AppSidebar() { } ``` +You've created your first sidebar. +
@@ -1021,6 +1025,24 @@ The `SidebarMenuBadge` component is used to render a badge within a `SidebarMenu
+## 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. + +```tsx showLineNumbers +function NavProjectsSkeleton() { + return ( + + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ) +} +``` + ## SidebarSeparator The `SidebarSeparator` component is used to render a separator within a `Sidebar`. @@ -1081,6 +1103,157 @@ The `SidebarRail` component is used to render a rail within a `Sidebar`. This ra ``` +## 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 ( + + {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) => ( + + + + + {project.name} + + + + ))} + + ) +} +``` + +```tsx showLineNumbers {8-10} title="Usage with React Suspense." +function AppSidebar() { + return ( + + + + Projects + + }> + + + + + + + ) +} +``` + +
+ +
+ 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). + +```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} + + + + ))} + + ) +} +``` + ## Theming We use the following CSS variables to theme the sidebar. @@ -1090,8 +1263,8 @@ We use the following CSS variables to theme the sidebar. :root { --sidebar-background: 0 0% 98%; --sidebar-foreground: 240 5.3% 26.1%; - --sidebar-primary: 220 100% 50%; - --sidebar-primary-foreground: 0 0% 100%; + --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%; @@ -1101,8 +1274,8 @@ We use the following CSS variables to theme the sidebar. .dark { --sidebar-background: 240 5.9% 10%; --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 220 100% 50%; - --sidebar-primary-foreground: 0 0% 100%; + --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%; diff --git a/apps/www/contentlayer.config.js b/apps/www/contentlayer.config.js index f86f4f8f26..3394d28b28 100644 --- a/apps/www/contentlayer.config.js +++ b/apps/www/contentlayer.config.js @@ -1,5 +1,4 @@ -import path from "path" -import { getHighlighter, loadTheme } from "@shikijs/compat" +import { getHighlighter } from "@shikijs/compat" import { defineDocumentType, defineNestedType, @@ -114,12 +113,8 @@ export default makeSource({ [ rehypePrettyCode, { - getHighlighter: async () => { - const theme = await loadTheme( - path.join(process.cwd(), "/lib/highlighter-theme.json") - ) - return await getHighlighter({ theme }) - }, + theme: "github-dark", + getHighlighter, onVisitLine(node) { // Prevent lines from collapsing in `display: grid` mode, and allow empty // lines to be copy/pasted diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json index 6b80f41c27..b6d784f565 100644 --- a/apps/www/public/r/index.json +++ b/apps/www/public/r/index.json @@ -512,7 +512,8 @@ "sheet", "tooltip", "input", - "use-mobile" + "use-mobile", + "skeleton" ], "files": [ { @@ -524,8 +525,8 @@ "light": { "sidebar-background": "0 0% 98%", "sidebar-foreground": "240 5.3% 26.1%", - "sidebar-primary": "220 100% 50%", - "sidebar-primary-foreground": "0 0% 100%", + "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%", @@ -534,8 +535,8 @@ "dark": { "sidebar-background": "240 5.9% 10%", "sidebar-foreground": "240 4.8% 95.9%", - "sidebar-primary": "220 100% 50%", - "sidebar-primary-foreground": "0 0% 100%", + "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%", diff --git a/apps/www/public/r/styles/default/demo-sidebar-rsc.json b/apps/www/public/r/styles/default/demo-sidebar-rsc.json new file mode 100644 index 0000000000..08683f4923 --- /dev/null +++ b/apps/www/public/r/styles/default/demo-sidebar-rsc.json @@ -0,0 +1,12 @@ +{ + "name": "demo-sidebar-rsc", + "type": "registry:block", + "description": "", + "files": [ + { + "path": "block/demo-sidebar-rsc.tsx", + "content": "import * as React from \"react\"\nimport { Frame, LifeBuoy, Map, PieChart, Send } from \"lucide-react\"\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarProvider,\n} from \"@/registry/default/ui/sidebar\"\n\nconst projects = [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n badge: \"24\",\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n badge: \"12\",\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n badge: \"3\",\n },\n {\n name: \"Support\",\n url: \"#\",\n icon: LifeBuoy,\n badge: \"21\",\n },\n {\n name: \"Feedback\",\n url: \"#\",\n icon: Send,\n badge: \"8\",\n },\n]\n\n// Dummy fetch function\nasync function fetchProjects() {\n await new Promise((resolve) => setTimeout(resolve, 3000))\n return projects\n}\n\nexport default function AppSidebar() {\n return (\n \n \n \n \n Projects\n \n }>\n \n \n \n \n \n \n \n )\n}\n\nfunction NavProjectsSkeleton() {\n return (\n \n {Array.from({ length: 5 }).map((_, index) => (\n \n \n \n ))}\n \n )\n}\n\nasync function NavProjects() {\n const projects = await fetchProjects()\n\n return (\n \n {projects.map((project) => (\n \n \n \n \n {project.name}\n \n \n \n ))}\n \n )\n}\n", + "type": "registry:component" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/sidebar-02.json b/apps/www/public/r/styles/default/sidebar-02.json index 34fc1ffe60..06db8437b1 100644 --- a/apps/www/public/r/styles/default/sidebar-02.json +++ b/apps/www/public/r/styles/default/sidebar-02.json @@ -12,7 +12,7 @@ "files": [ { "path": "block/sidebar-02/page.tsx", - "content": "import { AppSidebar } from \"@/registry/default/block/sidebar-02/components/app-sidebar\"\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\nimport { Separator } from \"@/registry/default/ui/separator\"\nimport {\n SidebarInset,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function Page() {\n return (\n \n \n \n
\n \n \n \n \n \n \n Building Your Application\n \n \n \n \n Data Fetching\n \n \n \n
\n
\n {Array.from({ length: 24 }).map((_, index) => (\n \n ))}\n
\n
\n
\n )\n}\n", + "content": "import { AppSidebar } from \"@/registry/default/block/sidebar-02/components/app-sidebar\"\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@/registry/default/ui/breadcrumb\"\nimport { Separator } from \"@/registry/default/ui/separator\"\nimport {\n SidebarInset,\n SidebarProvider,\n SidebarTrigger,\n} from \"@/registry/default/ui/sidebar\"\n\nexport default function Page() {\n return (\n \n \n \n
\n \n \n \n \n \n \n Building Your Application\n \n \n \n \n Data Fetching\n \n \n \n
\n
\n {Array.from({ length: 24 }).map((_, index) => (\n \n ))}\n
\n
\n
\n )\n}\n", "type": "registry:page", "target": "app/dashboard/page.tsx" }, diff --git a/apps/www/public/r/styles/default/sidebar-07.json b/apps/www/public/r/styles/default/sidebar-07.json index bb0d60d341..e2ac2a9a56 100644 --- a/apps/www/public/r/styles/default/sidebar-07.json +++ b/apps/www/public/r/styles/default/sidebar-07.json @@ -19,12 +19,12 @@ }, { "path": "block/sidebar-07/components/app-sidebar.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n BookOpen,\n Bot,\n Command,\n Frame,\n Map,\n PieChart,\n Settings2,\n SquareTerminal,\n} from \"lucide-react\"\n\nimport { NavMain } from \"@/registry/default/block/sidebar-07/components/nav-main\"\nimport { NavProjects } from \"@/registry/default/block/sidebar-07/components/nav-projects\"\nimport { NavUser } from \"@/registry/default/block/sidebar-07/components/nav-user\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n navMain: [\n {\n title: \"Playground\",\n url: \"#\",\n icon: SquareTerminal,\n isActive: true,\n items: [\n {\n title: \"History\",\n url: \"#\",\n },\n {\n title: \"Starred\",\n url: \"#\",\n },\n {\n title: \"Settings\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Models\",\n url: \"#\",\n icon: Bot,\n items: [\n {\n title: \"Genesis\",\n url: \"#\",\n },\n {\n title: \"Explorer\",\n url: \"#\",\n },\n {\n title: \"Quantum\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Documentation\",\n url: \"#\",\n icon: BookOpen,\n items: [\n {\n title: \"Introduction\",\n url: \"#\",\n },\n {\n title: \"Get Started\",\n url: \"#\",\n },\n {\n title: \"Tutorials\",\n url: \"#\",\n },\n {\n title: \"Changelog\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Settings\",\n url: \"#\",\n icon: Settings2,\n items: [\n {\n title: \"General\",\n url: \"#\",\n },\n {\n title: \"Team\",\n url: \"#\",\n },\n {\n title: \"Billing\",\n url: \"#\",\n },\n {\n title: \"Limits\",\n url: \"#\",\n },\n ],\n },\n ],\n projects: [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps) {\n return (\n \n \n \n \n \n \n
\n \n
\n
\n Acme Inc\n Enterprise\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n
\n )\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n AudioWaveform,\n BookOpen,\n Bot,\n Command,\n Frame,\n GalleryVerticalEnd,\n Map,\n PieChart,\n Settings2,\n SquareTerminal,\n} from \"lucide-react\"\n\nimport { NavMain } from \"@/registry/default/block/sidebar-07/components/nav-main\"\nimport { NavProjects } from \"@/registry/default/block/sidebar-07/components/nav-projects\"\nimport { NavUser } from \"@/registry/default/block/sidebar-07/components/nav-user\"\nimport { TeamSwitcher } from \"@/registry/default/block/sidebar-07/components/team-switcher\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarRail,\n} from \"@/registry/default/ui/sidebar\"\n\n// This is sample data.\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n teams: [\n {\n name: \"Acme Inc\",\n logo: GalleryVerticalEnd,\n plan: \"Enterprise\",\n },\n {\n name: \"Acme Corp.\",\n logo: AudioWaveform,\n plan: \"Startup\",\n },\n {\n name: \"Evil Corp.\",\n logo: Command,\n plan: \"Free\",\n },\n ],\n navMain: [\n {\n title: \"Playground\",\n url: \"#\",\n icon: SquareTerminal,\n isActive: true,\n items: [\n {\n title: \"History\",\n url: \"#\",\n },\n {\n title: \"Starred\",\n url: \"#\",\n },\n {\n title: \"Settings\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Models\",\n url: \"#\",\n icon: Bot,\n items: [\n {\n title: \"Genesis\",\n url: \"#\",\n },\n {\n title: \"Explorer\",\n url: \"#\",\n },\n {\n title: \"Quantum\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Documentation\",\n url: \"#\",\n icon: BookOpen,\n items: [\n {\n title: \"Introduction\",\n url: \"#\",\n },\n {\n title: \"Get Started\",\n url: \"#\",\n },\n {\n title: \"Tutorials\",\n url: \"#\",\n },\n {\n title: \"Changelog\",\n url: \"#\",\n },\n ],\n },\n {\n title: \"Settings\",\n url: \"#\",\n icon: Settings2,\n items: [\n {\n title: \"General\",\n url: \"#\",\n },\n {\n title: \"Team\",\n url: \"#\",\n },\n {\n title: \"Billing\",\n url: \"#\",\n },\n {\n title: \"Limits\",\n url: \"#\",\n },\n ],\n },\n ],\n projects: [\n {\n name: \"Design Engineering\",\n url: \"#\",\n icon: Frame,\n },\n {\n name: \"Sales & Marketing\",\n url: \"#\",\n icon: PieChart,\n },\n {\n name: \"Travel\",\n url: \"#\",\n icon: Map,\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps) {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n )\n}\n", "type": "registry:component" }, { "path": "block/sidebar-07/components/nav-main.tsx", - "content": "\"use client\"\n\nimport { ChevronRight, type LucideIcon } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n SidebarGroup,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavMain({\n items,\n}: {\n items: {\n title: string\n url: string\n icon?: LucideIcon\n isActive?: boolean\n items?: {\n title: string\n url: string\n }[]\n }[]\n}) {\n return (\n \n Platform\n \n {items.map((item) => (\n \n \n \n \n {item.icon && }\n {item.title}\n \n \n {item.items?.length ? (\n <>\n \n \n \n Toggle\n \n \n \n \n {item.items?.map((subItem) => (\n \n \n \n {subItem.title}\n \n \n \n ))}\n \n \n \n ) : null}\n \n \n ))}\n \n \n )\n}\n", + "content": "\"use client\"\n\nimport { ChevronRight, type LucideIcon } from \"lucide-react\"\n\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@/registry/default/ui/collapsible\"\nimport {\n SidebarGroup,\n SidebarGroupLabel,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n} from \"@/registry/default/ui/sidebar\"\n\nexport function NavMain({\n items,\n}: {\n items: {\n title: string\n url: string\n icon?: LucideIcon\n isActive?: boolean\n items?: {\n title: string\n url: string\n }[]\n }[]\n}) {\n return (\n \n Platform\n \n {items.map((item) => (\n \n \n \n \n {item.icon && }\n {item.title}\n \n \n \n \n \n {item.items?.map((subItem) => (\n \n \n \n {subItem.title}\n \n \n \n ))}\n \n \n \n \n ))}\n \n \n )\n}\n", "type": "registry:component" }, { diff --git a/apps/www/public/r/styles/default/sidebar-09.json b/apps/www/public/r/styles/default/sidebar-09.json index 2c8a3d70e0..9b16f19b0d 100644 --- a/apps/www/public/r/styles/default/sidebar-09.json +++ b/apps/www/public/r/styles/default/sidebar-09.json @@ -19,7 +19,7 @@ }, { "path": "block/sidebar-09/components/app-sidebar.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArchiveX, Command, File, Inbox, Send, Trash2 } from \"lucide-react\"\n\nimport { NavUser } from \"@/registry/default/block/sidebar-09/components/nav-user\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarHeader,\n SidebarInput,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\nimport { Switch } from \"@/registry/default/ui/switch\"\n\n// This is sample data\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n navMain: [\n {\n title: \"Inbox\",\n url: \"#\",\n icon: Inbox,\n isActive: true,\n },\n {\n title: \"Drafts\",\n url: \"#\",\n icon: File,\n isActive: false,\n },\n {\n title: \"Sent\",\n url: \"#\",\n icon: Send,\n isActive: false,\n },\n {\n title: \"Junk\",\n url: \"#\",\n icon: ArchiveX,\n isActive: false,\n },\n {\n title: \"Trash\",\n url: \"#\",\n icon: Trash2,\n isActive: false,\n },\n ],\n mails: [\n {\n name: \"William Smith\",\n email: \"williamsmith@example.com\",\n subject: \"Meeting Tomorrow\",\n date: \"09:34 AM\",\n teaser:\n \"Hi team, just a reminder about our meeting tomorrow at 10 AM.\\nPlease come prepared with your project updates.\",\n },\n {\n name: \"Alice Smith\",\n email: \"alicesmith@example.com\",\n subject: \"Re: Project Update\",\n date: \"Yesterday\",\n teaser:\n \"Thanks for the update. The progress looks great so far.\\nLet's schedule a call to discuss the next steps.\",\n },\n {\n name: \"Bob Johnson\",\n email: \"bobjohnson@example.com\",\n subject: \"Weekend Plans\",\n date: \"2 days ago\",\n teaser:\n \"Hey everyone! I'm thinking of organizing a team outing this weekend.\\nWould you be interested in a hiking trip or a beach day?\",\n },\n {\n name: \"Emily Davis\",\n email: \"emilydavis@example.com\",\n subject: \"Re: Question about Budget\",\n date: \"2 days ago\",\n teaser:\n \"I've reviewed the budget numbers you sent over.\\nCan we set up a quick call to discuss some potential adjustments?\",\n },\n {\n name: \"Michael Wilson\",\n email: \"michaelwilson@example.com\",\n subject: \"Important Announcement\",\n date: \"1 week ago\",\n teaser:\n \"Please join us for an all-hands meeting this Friday at 3 PM.\\nWe have some exciting news to share about the company's future.\",\n },\n {\n name: \"Sarah Brown\",\n email: \"sarahbrown@example.com\",\n subject: \"Re: Feedback on Proposal\",\n date: \"1 week ago\",\n teaser:\n \"Thank you for sending over the proposal. I've reviewed it and have some thoughts.\\nCould we schedule a meeting to discuss my feedback in detail?\",\n },\n {\n name: \"David Lee\",\n email: \"davidlee@example.com\",\n subject: \"New Project Idea\",\n date: \"1 week ago\",\n teaser:\n \"I've been brainstorming and came up with an interesting project concept.\\nDo you have time this week to discuss its potential impact and feasibility?\",\n },\n {\n name: \"Olivia Wilson\",\n email: \"oliviawilson@example.com\",\n subject: \"Vacation Plans\",\n date: \"1 week ago\",\n teaser:\n \"Just a heads up that I'll be taking a two-week vacation next month.\\nI'll make sure all my projects are up to date before I leave.\",\n },\n {\n name: \"James Martin\",\n email: \"jamesmartin@example.com\",\n subject: \"Re: Conference Registration\",\n date: \"1 week ago\",\n teaser:\n \"I've completed the registration for the upcoming tech conference.\\nLet me know if you need any additional information from my end.\",\n },\n {\n name: \"Sophia White\",\n email: \"sophiawhite@example.com\",\n subject: \"Team Dinner\",\n date: \"1 week ago\",\n teaser:\n \"To celebrate our recent project success, I'd like to organize a team dinner.\\nAre you available next Friday evening? Please let me know your preferences.\",\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps) {\n // Note: I'm using state to show active item.\n // IRL you should use the url/router.\n const [activeItem, setActiveItem] = React.useState(data.navMain[0])\n const [mails, setMails] = React.useState(data.mails)\n const { setOpen } = useSidebar()\n\n return (\n [data-sidebar=sidebar]]:flex-row\"\n {...props}\n >\n {/* This is the first sidebar */}\n {/* We disable collapsible and adjust width to icon. */}\n {/* This will make the sidebar appear as icons. */}\n \n \n \n \n \n \n
\n \n
\n
\n Acme Inc\n Enterprise\n
\n
\n
\n
\n
\n
\n \n \n \n \n {data.navMain.map((item) => (\n \n {\n setActiveItem(item)\n const mail = data.mails.sort(() => Math.random() - 0.5)\n setMails(\n mail.slice(\n 0,\n Math.max(5, Math.floor(Math.random() * 10) + 1)\n )\n )\n setOpen(true)\n }}\n isActive={activeItem.title === item.title}\n className=\"px-2.5 md:px-2\"\n >\n \n {item.title}\n \n \n ))}\n \n \n \n \n \n \n \n \n\n {/* This is the second sidebar */}\n {/* We disable collapsible and let it fill remaining space */}\n \n \n
\n
\n {activeItem.title}\n
\n \n
\n \n
\n \n \n \n {mails.map((mail) => (\n \n
\n {mail.name}{\" \"}\n {mail.date}\n
\n {mail.subject}\n \n {mail.teaser}\n \n \n ))}\n
\n
\n
\n
\n \n )\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArchiveX, Command, File, Inbox, Send, Trash2 } from \"lucide-react\"\n\nimport { NavUser } from \"@/registry/default/block/sidebar-09/components/nav-user\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarHeader,\n SidebarInput,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"@/registry/default/ui/sidebar\"\nimport { Switch } from \"@/registry/default/ui/switch\"\n\n// This is sample data\nconst data = {\n user: {\n name: \"shadcn\",\n email: \"m@example.com\",\n avatar: \"/avatars/shadcn.jpg\",\n },\n navMain: [\n {\n title: \"Inbox\",\n url: \"#\",\n icon: Inbox,\n isActive: true,\n },\n {\n title: \"Drafts\",\n url: \"#\",\n icon: File,\n isActive: false,\n },\n {\n title: \"Sent\",\n url: \"#\",\n icon: Send,\n isActive: false,\n },\n {\n title: \"Junk\",\n url: \"#\",\n icon: ArchiveX,\n isActive: false,\n },\n {\n title: \"Trash\",\n url: \"#\",\n icon: Trash2,\n isActive: false,\n },\n ],\n mails: [\n {\n name: \"William Smith\",\n email: \"williamsmith@example.com\",\n subject: \"Meeting Tomorrow\",\n date: \"09:34 AM\",\n teaser:\n \"Hi team, just a reminder about our meeting tomorrow at 10 AM.\\nPlease come prepared with your project updates.\",\n },\n {\n name: \"Alice Smith\",\n email: \"alicesmith@example.com\",\n subject: \"Re: Project Update\",\n date: \"Yesterday\",\n teaser:\n \"Thanks for the update. The progress looks great so far.\\nLet's schedule a call to discuss the next steps.\",\n },\n {\n name: \"Bob Johnson\",\n email: \"bobjohnson@example.com\",\n subject: \"Weekend Plans\",\n date: \"2 days ago\",\n teaser:\n \"Hey everyone! I'm thinking of organizing a team outing this weekend.\\nWould you be interested in a hiking trip or a beach day?\",\n },\n {\n name: \"Emily Davis\",\n email: \"emilydavis@example.com\",\n subject: \"Re: Question about Budget\",\n date: \"2 days ago\",\n teaser:\n \"I've reviewed the budget numbers you sent over.\\nCan we set up a quick call to discuss some potential adjustments?\",\n },\n {\n name: \"Michael Wilson\",\n email: \"michaelwilson@example.com\",\n subject: \"Important Announcement\",\n date: \"1 week ago\",\n teaser:\n \"Please join us for an all-hands meeting this Friday at 3 PM.\\nWe have some exciting news to share about the company's future.\",\n },\n {\n name: \"Sarah Brown\",\n email: \"sarahbrown@example.com\",\n subject: \"Re: Feedback on Proposal\",\n date: \"1 week ago\",\n teaser:\n \"Thank you for sending over the proposal. I've reviewed it and have some thoughts.\\nCould we schedule a meeting to discuss my feedback in detail?\",\n },\n {\n name: \"David Lee\",\n email: \"davidlee@example.com\",\n subject: \"New Project Idea\",\n date: \"1 week ago\",\n teaser:\n \"I've been brainstorming and came up with an interesting project concept.\\nDo you have time this week to discuss its potential impact and feasibility?\",\n },\n {\n name: \"Olivia Wilson\",\n email: \"oliviawilson@example.com\",\n subject: \"Vacation Plans\",\n date: \"1 week ago\",\n teaser:\n \"Just a heads up that I'll be taking a two-week vacation next month.\\nI'll make sure all my projects are up to date before I leave.\",\n },\n {\n name: \"James Martin\",\n email: \"jamesmartin@example.com\",\n subject: \"Re: Conference Registration\",\n date: \"1 week ago\",\n teaser:\n \"I've completed the registration for the upcoming tech conference.\\nLet me know if you need any additional information from my end.\",\n },\n {\n name: \"Sophia White\",\n email: \"sophiawhite@example.com\",\n subject: \"Team Dinner\",\n date: \"1 week ago\",\n teaser:\n \"To celebrate our recent project success, I'd like to organize a team dinner.\\nAre you available next Friday evening? Please let me know your preferences.\",\n },\n ],\n}\n\nexport function AppSidebar({ ...props }: React.ComponentProps) {\n // Note: I'm using state to show active item.\n // IRL you should use the url/router.\n const [activeItem, setActiveItem] = React.useState(data.navMain[0])\n const [mails, setMails] = React.useState(data.mails)\n const { setOpen } = useSidebar()\n\n return (\n [data-sidebar=sidebar]]:flex-row\"\n {...props}\n >\n {/* This is the first sidebar */}\n {/* We disable collapsible and adjust width to icon. */}\n {/* This will make the sidebar appear as icons. */}\n \n \n \n \n \n \n
\n \n
\n
\n Acme Inc\n Enterprise\n
\n
\n
\n
\n
\n
\n \n \n \n \n {data.navMain.map((item) => (\n \n {\n setActiveItem(item)\n const mail = data.mails.sort(() => Math.random() - 0.5)\n setMails(\n mail.slice(\n 0,\n Math.max(5, Math.floor(Math.random() * 10) + 1)\n )\n )\n setOpen(true)\n }}\n isActive={activeItem.title === item.title}\n className=\"px-2.5 md:px-2\"\n >\n \n {item.title}\n \n \n ))}\n \n \n \n \n \n \n \n \n\n {/* This is the second sidebar */}\n {/* We disable collapsible and let it fill remaining space */}\n \n \n
\n
\n {activeItem.title}\n
\n \n
\n \n
\n \n \n \n {mails.map((mail) => (\n \n
\n {mail.name}{\" \"}\n {mail.date}\n
\n {mail.subject}\n \n {mail.teaser}\n \n \n ))}\n
\n
\n
\n
\n \n )\n}\n", "type": "registry:component" }, { diff --git a/apps/www/public/r/styles/default/sidebar.json b/apps/www/public/r/styles/default/sidebar.json index 6d9410a9a5..ebbfaac83f 100644 --- a/apps/www/public/r/styles/default/sidebar.json +++ b/apps/www/public/r/styles/default/sidebar.json @@ -12,12 +12,13 @@ "sheet", "tooltip", "input", - "use-mobile" + "use-mobile", + "skeleton" ], "files": [ { "path": "ui/sidebar.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { PanelLeft } from \"lucide-react\"\n\nimport { useIsMobile } from \"@/registry/default/hooks/use-mobile\"\nimport { cn } from \"@/registry/default/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport { Separator } from \"@/registry/default/ui/separator\"\nimport { Sheet, SheetContent } from \"@/registry/default/ui/sheet\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\"\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar:state\"\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7\nconst SIDEBAR_WIDTH = \"16rem\"\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\"\nconst SIDEBAR_WIDTH_ICON = \"3rem\"\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\"\n\ntype SidebarContext = {\n state: \"expanded\" | \"collapsed\"\n open: boolean\n setOpen: (open: boolean) => void\n openMobile: boolean\n setOpenMobile: (open: boolean) => void\n isMobile: boolean\n toggleSidebar: () => void\n}\n\nconst SidebarContext = React.createContext(null)\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext)\n if (!context) {\n throw new Error(\"useSidebar must be used within a Sidebar.\")\n }\n\n return context\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean\n open?: boolean\n setOpen?: (open: boolean) => void\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n setOpen: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref\n ) => {\n const isMobile = useIsMobile()\n const [openMobile, setOpenMobile] = React.useState(false)\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen)\n const open = openProp ?? _open\n const setOpen = React.useCallback(\n (open: boolean) => {\n if (setOpenProp) {\n return setOpenProp?.(open)\n }\n\n _setOpen(open)\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`\n },\n [setOpenProp]\n )\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n if (isMobile) {\n return setOpenMobile(!openMobile)\n }\n return setOpen(!open)\n }, [isMobile, open, setOpen, openMobile, setOpenMobile])\n\n // Adds a keyboard shortcut to toggle the sidebar.\n const handleKeyDown = React.useCallback(\n (event: KeyboardEvent) => {\n if (\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n (event.metaKey || event.ctrlKey)\n ) {\n event.preventDefault()\n toggleSidebar()\n }\n },\n [toggleSidebar]\n )\n React.useEffect(() => {\n window.addEventListener(\"keydown\", handleKeyDown)\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [handleKeyDown])\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\"\n\n const contextValue = React.useMemo(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]\n )\n\n return (\n \n \n \n {children}\n \n \n \n )\n }\n)\nSidebarProvider.displayName = \"SidebarProvider\"\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\"\n variant?: \"sidebar\" | \"floating\" | \"inset\"\n collapsible?: \"offcanvas\" | \"icon\" | \"none\"\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar()\n\n if (collapsible === \"none\") {\n return (\n \n {children}\n \n )\n }\n\n if (isMobile) {\n return (\n \n button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n
{children}
\n \n
\n )\n }\n\n return (\n