Pārlūkot izejas kodu

refactor(dashboard): Extract app layout

Michael Bromley 10 mēneši atpakaļ
vecāks
revīzija
69092b9b6d

+ 28 - 0
packages/dashboard/src/components/layout/app-layout.tsx

@@ -0,0 +1,28 @@
+import { AppSidebar } from '@/components/layout/app-sidebar.js';
+import { GeneratedBreadcrumbs } from '@/components/layout/generated-breadcrumbs.js';
+import { Separator } from '@/components/ui/separator.js';
+import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar.js';
+import { Outlet } from '@tanstack/react-router';
+import * as React from 'react';
+
+export function AppLayout() {
+    return (
+        <SidebarProvider>
+            <AppSidebar />
+            <SidebarInset>
+                <div className="container mx-auto">
+                    <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 h-4" />
+                            <GeneratedBreadcrumbs />
+                        </div>
+                    </header>
+                    <div className="px-4">
+                        <Outlet />
+                    </div>
+                </div>
+            </SidebarInset>
+        </SidebarProvider>
+    );
+}

+ 81 - 0
packages/dashboard/src/components/layout/nav-projects.tsx

@@ -0,0 +1,81 @@
+import { Folder, Forward, MoreHorizontal, Trash2, type LucideIcon } from 'lucide-react';
+
+import {
+    DropdownMenu,
+    DropdownMenuContent,
+    DropdownMenuItem,
+    DropdownMenuSeparator,
+    DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu.js';
+import {
+    SidebarGroup,
+    SidebarGroupLabel,
+    SidebarMenu,
+    SidebarMenuAction,
+    SidebarMenuButton,
+    SidebarMenuItem,
+    useSidebar,
+} from '@/components/ui/sidebar.js';
+
+export function NavProjects({
+    projects,
+}: {
+    projects: {
+        name: string;
+        url: string;
+        icon: LucideIcon;
+    }[];
+}) {
+    const { isMobile } = useSidebar();
+
+    return (
+        <SidebarGroup className="group-data-[collapsible=icon]:hidden">
+            <SidebarGroupLabel>Projects</SidebarGroupLabel>
+            <SidebarMenu>
+                {projects.map(item => (
+                    <SidebarMenuItem key={item.name}>
+                        <SidebarMenuButton asChild>
+                            <a href={item.url}>
+                                <item.icon />
+                                <span>{item.name}</span>
+                            </a>
+                        </SidebarMenuButton>
+                        <DropdownMenu>
+                            <DropdownMenuTrigger asChild>
+                                <SidebarMenuAction showOnHover>
+                                    <MoreHorizontal />
+                                    <span className="sr-only">More</span>
+                                </SidebarMenuAction>
+                            </DropdownMenuTrigger>
+                            <DropdownMenuContent
+                                className="w-48 rounded-lg"
+                                side={isMobile ? 'bottom' : 'right'}
+                                align={isMobile ? 'end' : 'start'}
+                            >
+                                <DropdownMenuItem>
+                                    <Folder className="text-muted-foreground" />
+                                    <span>View Project</span>
+                                </DropdownMenuItem>
+                                <DropdownMenuItem>
+                                    <Forward className="text-muted-foreground" />
+                                    <span>Share Project</span>
+                                </DropdownMenuItem>
+                                <DropdownMenuSeparator />
+                                <DropdownMenuItem>
+                                    <Trash2 className="text-muted-foreground" />
+                                    <span>Delete Project</span>
+                                </DropdownMenuItem>
+                            </DropdownMenuContent>
+                        </DropdownMenu>
+                    </SidebarMenuItem>
+                ))}
+                <SidebarMenuItem>
+                    <SidebarMenuButton className="text-sidebar-foreground/70">
+                        <MoreHorizontal className="text-sidebar-foreground/70" />
+                        <span>More</span>
+                    </SidebarMenuButton>
+                </SidebarMenuItem>
+            </SidebarMenu>
+        </SidebarGroup>
+    );
+}

+ 78 - 0
packages/dashboard/src/components/layout/team-switcher.tsx

@@ -0,0 +1,78 @@
+import * as React from 'react';
+import { ChevronsUpDown, Plus } from 'lucide-react';
+
+import {
+    DropdownMenu,
+    DropdownMenuContent,
+    DropdownMenuItem,
+    DropdownMenuLabel,
+    DropdownMenuSeparator,
+    DropdownMenuShortcut,
+    DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu.js';
+import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar.js';
+
+export function TeamSwitcher({
+    teams,
+}: {
+    teams: {
+        name: string;
+        logo: React.ElementType;
+        plan: string;
+    }[];
+}) {
+    const { isMobile } = useSidebar();
+    const [activeTeam, setActiveTeam] = React.useState(teams[0]);
+
+    return (
+        <SidebarMenu>
+            <SidebarMenuItem>
+                <DropdownMenu>
+                    <DropdownMenuTrigger asChild>
+                        <SidebarMenuButton
+                            size="lg"
+                            className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
+                        >
+                            <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
+                                <activeTeam.logo className="size-4" />
+                            </div>
+                            <div className="grid flex-1 text-left text-sm leading-tight">
+                                <span className="truncate font-semibold">{activeTeam.name}</span>
+                                <span className="truncate text-xs">{activeTeam.plan}</span>
+                            </div>
+                            <ChevronsUpDown className="ml-auto" />
+                        </SidebarMenuButton>
+                    </DropdownMenuTrigger>
+                    <DropdownMenuContent
+                        className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
+                        align="start"
+                        side={isMobile ? 'bottom' : 'right'}
+                        sideOffset={4}
+                    >
+                        <DropdownMenuLabel className="text-muted-foreground text-xs">Teams</DropdownMenuLabel>
+                        {teams.map((team, index) => (
+                            <DropdownMenuItem
+                                key={team.name}
+                                onClick={() => setActiveTeam(team)}
+                                className="gap-2 p-2"
+                            >
+                                <div className="flex size-6 items-center justify-center rounded-xs border">
+                                    <team.logo className="size-4 shrink-0" />
+                                </div>
+                                {team.name}
+                                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
+                            </DropdownMenuItem>
+                        ))}
+                        <DropdownMenuSeparator />
+                        <DropdownMenuItem className="gap-2 p-2">
+                            <div className="bg-background flex size-6 items-center justify-center rounded-md border">
+                                <Plus className="size-4" />
+                            </div>
+                            <div className="text-muted-foreground font-medium">Add team</div>
+                        </DropdownMenuItem>
+                    </DropdownMenuContent>
+                </DropdownMenu>
+            </SidebarMenuItem>
+        </SidebarMenu>
+    );
+}

+ 0 - 87
packages/dashboard/src/components/nav-projects.tsx

@@ -1,87 +0,0 @@
-import {
-  Folder,
-  Forward,
-  MoreHorizontal,
-  Trash2,
-  type LucideIcon,
-} from "lucide-react"
-
-import {
-  DropdownMenu,
-  DropdownMenuContent,
-  DropdownMenuItem,
-  DropdownMenuSeparator,
-  DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
-  SidebarGroup,
-  SidebarGroupLabel,
-  SidebarMenu,
-  SidebarMenuAction,
-  SidebarMenuButton,
-  SidebarMenuItem,
-  useSidebar,
-} from "@/components/ui/sidebar"
-
-export function NavProjects({
-  projects,
-}: {
-  projects: {
-    name: string
-    url: string
-    icon: LucideIcon
-  }[]
-}) {
-  const { isMobile } = useSidebar()
-
-  return (
-    <SidebarGroup className="group-data-[collapsible=icon]:hidden">
-      <SidebarGroupLabel>Projects</SidebarGroupLabel>
-      <SidebarMenu>
-        {projects.map((item) => (
-          <SidebarMenuItem key={item.name}>
-            <SidebarMenuButton asChild>
-              <a href={item.url}>
-                <item.icon />
-                <span>{item.name}</span>
-              </a>
-            </SidebarMenuButton>
-            <DropdownMenu>
-              <DropdownMenuTrigger asChild>
-                <SidebarMenuAction showOnHover>
-                  <MoreHorizontal />
-                  <span className="sr-only">More</span>
-                </SidebarMenuAction>
-              </DropdownMenuTrigger>
-              <DropdownMenuContent
-                className="w-48 rounded-lg"
-                side={isMobile ? "bottom" : "right"}
-                align={isMobile ? "end" : "start"}
-              >
-                <DropdownMenuItem>
-                  <Folder className="text-muted-foreground" />
-                  <span>View Project</span>
-                </DropdownMenuItem>
-                <DropdownMenuItem>
-                  <Forward className="text-muted-foreground" />
-                  <span>Share Project</span>
-                </DropdownMenuItem>
-                <DropdownMenuSeparator />
-                <DropdownMenuItem>
-                  <Trash2 className="text-muted-foreground" />
-                  <span>Delete Project</span>
-                </DropdownMenuItem>
-              </DropdownMenuContent>
-            </DropdownMenu>
-          </SidebarMenuItem>
-        ))}
-        <SidebarMenuItem>
-          <SidebarMenuButton className="text-sidebar-foreground/70">
-            <MoreHorizontal className="text-sidebar-foreground/70" />
-            <span>More</span>
-          </SidebarMenuButton>
-        </SidebarMenuItem>
-      </SidebarMenu>
-    </SidebarGroup>
-  )
-}

+ 0 - 87
packages/dashboard/src/components/team-switcher.tsx

@@ -1,87 +0,0 @@
-import * as React from "react"
-import { ChevronsUpDown, Plus } from "lucide-react"
-
-import {
-  DropdownMenu,
-  DropdownMenuContent,
-  DropdownMenuItem,
-  DropdownMenuLabel,
-  DropdownMenuSeparator,
-  DropdownMenuShortcut,
-  DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
-  SidebarMenu,
-  SidebarMenuButton,
-  SidebarMenuItem,
-  useSidebar,
-} from "@/components/ui/sidebar"
-
-export function TeamSwitcher({
-  teams,
-}: {
-  teams: {
-    name: string
-    logo: React.ElementType
-    plan: string
-  }[]
-}) {
-  const { isMobile } = useSidebar()
-  const [activeTeam, setActiveTeam] = React.useState(teams[0])
-
-  return (
-    <SidebarMenu>
-      <SidebarMenuItem>
-        <DropdownMenu>
-          <DropdownMenuTrigger asChild>
-            <SidebarMenuButton
-              size="lg"
-              className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
-            >
-              <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
-                <activeTeam.logo className="size-4" />
-              </div>
-              <div className="grid flex-1 text-left text-sm leading-tight">
-                <span className="truncate font-semibold">
-                  {activeTeam.name}
-                </span>
-                <span className="truncate text-xs">{activeTeam.plan}</span>
-              </div>
-              <ChevronsUpDown className="ml-auto" />
-            </SidebarMenuButton>
-          </DropdownMenuTrigger>
-          <DropdownMenuContent
-            className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
-            align="start"
-            side={isMobile ? "bottom" : "right"}
-            sideOffset={4}
-          >
-            <DropdownMenuLabel className="text-muted-foreground text-xs">
-              Teams
-            </DropdownMenuLabel>
-            {teams.map((team, index) => (
-              <DropdownMenuItem
-                key={team.name}
-                onClick={() => setActiveTeam(team)}
-                className="gap-2 p-2"
-              >
-                <div className="flex size-6 items-center justify-center rounded-xs border">
-                  <team.logo className="size-4 shrink-0" />
-                </div>
-                {team.name}
-                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
-              </DropdownMenuItem>
-            ))}
-            <DropdownMenuSeparator />
-            <DropdownMenuItem className="gap-2 p-2">
-              <div className="bg-background flex size-6 items-center justify-center rounded-md border">
-                <Plus className="size-4" />
-              </div>
-              <div className="text-muted-foreground font-medium">Add team</div>
-            </DropdownMenuItem>
-          </DropdownMenuContent>
-        </DropdownMenu>
-      </SidebarMenuItem>
-    </SidebarMenu>
-  )
-}

+ 3 - 22
packages/dashboard/src/routes/_authenticated.tsx

@@ -1,8 +1,5 @@
-import { AppSidebar } from '@/components/layout/app-sidebar.js';
-import { GeneratedBreadcrumbs } from '@/components/layout/generated-breadcrumbs.js';
-import { Separator } from '@/components/ui/separator.js';
-import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar.js';
-import { createFileRoute, Outlet, redirect } from '@tanstack/react-router';
+import { AppLayout } from '@/components/layout/app-layout.js';
+import { createFileRoute, redirect } from '@tanstack/react-router';
 import * as React from 'react';
 
 export const AUTHENTICATED_ROUTE_PREFIX = '/_authenticated';
@@ -25,21 +22,5 @@ export const Route = createFileRoute(AUTHENTICATED_ROUTE_PREFIX)({
 });
 
 function AuthLayout() {
-    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 h-4" />
-                        <GeneratedBreadcrumbs />
-                    </div>
-                </header>
-                <div className="m-4">
-                    <Outlet />
-                </div>
-            </SidebarInset>
-        </SidebarProvider>
-    );
+    return <AppLayout />;
 }