From ecbd3438f4e955129c96736f96fe66e73700fd10 Mon Sep 17 00:00:00 2001 From: liangfung <1098486429@qq.com> Date: Thu, 7 Nov 2024 08:21:08 +0700 Subject: [PATCH 01/10] refactor(ui): admin sidebar refactoring --- .../(dashboard)/components/main-content.tsx | 8 +- .../app/(dashboard)/components/sidebar.tsx | 160 ++-- ee/tabby-ui/app/(dashboard)/layout.tsx | 9 +- ee/tabby-ui/app/dashboard/page.tsx | 52 ++ ee/tabby-ui/app/globals.css | 32 + ee/tabby-ui/components/app-sidebar.tsx | 176 ++++ ee/tabby-ui/components/header.tsx | 2 + ee/tabby-ui/components/nav-main.tsx | 74 ++ ee/tabby-ui/components/nav-projects.tsx | 89 ++ ee/tabby-ui/components/nav-user.tsx | 114 +++ ee/tabby-ui/components/team-switcher.tsx | 89 ++ ee/tabby-ui/components/ui/breadcrumb.tsx | 115 +++ ee/tabby-ui/components/ui/icons.tsx | 13 +- ee/tabby-ui/components/ui/sidebar.tsx | 764 ++++++++++++++++++ ee/tabby-ui/hooks/use-mobile.tsx | 19 + ee/tabby-ui/lib/hooks/use-server-info.ts | 3 +- ee/tabby-ui/package.json | 4 +- ee/tabby-ui/tailwind.config.js | 198 ++--- pnpm-lock.yaml | 18 +- 19 files changed, 1746 insertions(+), 193 deletions(-) create mode 100644 ee/tabby-ui/app/dashboard/page.tsx create mode 100644 ee/tabby-ui/components/app-sidebar.tsx create mode 100644 ee/tabby-ui/components/nav-main.tsx create mode 100644 ee/tabby-ui/components/nav-projects.tsx create mode 100644 ee/tabby-ui/components/nav-user.tsx create mode 100644 ee/tabby-ui/components/team-switcher.tsx create mode 100644 ee/tabby-ui/components/ui/breadcrumb.tsx create mode 100644 ee/tabby-ui/components/ui/sidebar.tsx create mode 100644 ee/tabby-ui/hooks/use-mobile.tsx diff --git a/ee/tabby-ui/app/(dashboard)/components/main-content.tsx b/ee/tabby-ui/app/(dashboard)/components/main-content.tsx index df24707bb3f2..210e52b02326 100644 --- a/ee/tabby-ui/app/(dashboard)/components/main-content.tsx +++ b/ee/tabby-ui/app/(dashboard)/components/main-content.tsx @@ -1,9 +1,9 @@ 'use client' -import { ScrollArea } from '@/components/ui/scroll-area' import { BANNER_HEIGHT, useShowDemoBanner } from '@/components/demo-banner' import { Header } from '@/components/header' import { useShowLicenseBanner } from '@/components/license-banner' +import { SidebarInset } from '@/components/ui/sidebar' export default function MainContent({ children @@ -24,13 +24,13 @@ export default function MainContent({ return ( <> {/* Wraps right hand side into ScrollArea, making scroll bar consistent across all browsers */} -
{children}
- + ) } diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx index e44e83bcee60..c533011ee418 100644 --- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx +++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx @@ -10,7 +10,6 @@ import { cva } from 'class-variance-authority' import { escapeRegExp } from 'lodash-es' import { useMe } from '@/lib/hooks/use-me' -import { cn } from '@/lib/utils' import { Collapsible, CollapsibleContent, @@ -23,10 +22,10 @@ import { IconLightingBolt, IconUser } from '@/components/ui/icons' -import { ScrollArea } from '@/components/ui/scroll-area' import { BANNER_HEIGHT, useShowDemoBanner } from '@/components/demo-banner' import { useShowLicenseBanner } from '@/components/license-banner' import LoadingWrapper from '@/components/loading-wrapper' +import { Sidebar, SidebarContent, SidebarHeader } from '@/components/ui/sidebar' export interface SidebarProps { children?: React.ReactNode @@ -41,18 +40,18 @@ type MenuLeaf = { type Menu = | { - title: string - icon: React.ReactNode - allowUser?: boolean - children: MenuLeaf[] - } + title: string + icon: React.ReactNode + allowUser?: boolean + children: MenuLeaf[] + } | { - title: string - href: string - icon: React.ReactNode - allowUser?: boolean - children?: never - } + title: string + href: string + icon: React.ReactNode + allowUser?: boolean + children?: never + } const menus: Menu[] = [ { @@ -123,7 +122,7 @@ const menus: Menu[] = [ } ] -export default function Sidebar({ children, className }: SidebarProps) { +export default function AppSidebar({ children, className }: SidebarProps) { const [{ data, fetching: fetchingMe }] = useMe() const isAdmin = data?.me.isAdmin const [isShowDemoBanner] = useShowDemoBanner() @@ -131,81 +130,76 @@ export default function Sidebar({ children, className }: SidebarProps) { const showBanner = isShowDemoBanner || isShowLicenseBanner const style = showBanner ? { - height: `calc(100vh - ${isShowDemoBanner ? BANNER_HEIGHT : '0rem'} - ${ - isShowLicenseBanner ? BANNER_HEIGHT : '0rem' - })` - } + height: `calc(100vh - ${isShowDemoBanner ? BANNER_HEIGHT : '0rem'} - ${isShowLicenseBanner ? BANNER_HEIGHT : '0rem' + })`, + top: isShowDemoBanner ? BANNER_HEIGHT : 0 + } : { height: '100vh' } return ( - -
- -
-
+ + + ) } diff --git a/ee/tabby-ui/app/(dashboard)/layout.tsx b/ee/tabby-ui/app/(dashboard)/layout.tsx index af0feb7b041c..9b2e9edec2b8 100644 --- a/ee/tabby-ui/app/(dashboard)/layout.tsx +++ b/ee/tabby-ui/app/(dashboard)/layout.tsx @@ -3,7 +3,8 @@ import { Metadata } from 'next' import { LicenseBanner } from '@/components/license-banner' import MainContent from './components/main-content' -import Sidebar from './components/sidebar' +import AppSidebar from './components/sidebar' +import { SidebarProvider } from '@/components/ui/sidebar' export const metadata: Metadata = { title: { @@ -20,10 +21,10 @@ export default function RootLayout({ return ( <> -
- + + {children} -
+ ) } diff --git a/ee/tabby-ui/app/dashboard/page.tsx b/ee/tabby-ui/app/dashboard/page.tsx new file mode 100644 index 000000000000..23034abef3f5 --- /dev/null +++ b/ee/tabby-ui/app/dashboard/page.tsx @@ -0,0 +1,52 @@ +import { AppSidebar } from "@/components/app-sidebar" +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb" +import { Separator } from "@/components/ui/separator" +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar" + +export default function Page() { + return ( + + + +
+
+ + + + + + + Building Your Application + + + + + Data Fetching + + + +
+
+
+
+
+
+
+
+
+
+ + + ) +} diff --git a/ee/tabby-ui/app/globals.css b/ee/tabby-ui/app/globals.css index dfda4f729c33..ea890ab24e54 100644 --- a/ee/tabby-ui/app/globals.css +++ b/ee/tabby-ui/app/globals.css @@ -41,6 +41,22 @@ --selection: 200, 100%, 90%; --link: 38, 44%, 41%; + + --sidebar-background: 38 96.55% 98.5%; + + --sidebar-foreground: 38 3.1% 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 { @@ -79,6 +95,22 @@ --selection: 221, 13%, 28%; --link: 37, 34%, 57%; + + --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%; } } diff --git a/ee/tabby-ui/components/app-sidebar.tsx b/ee/tabby-ui/components/app-sidebar.tsx new file mode 100644 index 000000000000..4b95c832bf45 --- /dev/null +++ b/ee/tabby-ui/components/app-sidebar.tsx @@ -0,0 +1,176 @@ +"use client" + +import * as React from "react" +import { + AudioWaveform, + BookOpen, + Bot, + Command, + Frame, + GalleryVerticalEnd, + Map, + PieChart, + Settings2, + SquareTerminal, +} from "lucide-react" + +import { NavMain } from "@/components/nav-main" +import { NavProjects } from "@/components/nav-projects" +import { NavUser } from "@/components/nav-user" +import { TeamSwitcher } from "@/components/team-switcher" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarRail, +} from "@/components/ui/sidebar" + +// This is sample data. +const data = { + user: { + name: "shadcn", + email: "m@example.com", + avatar: "/avatars/shadcn.jpg", + }, + teams: [ + { + name: "Acme Inc", + logo: GalleryVerticalEnd, + plan: "Enterprise", + }, + { + name: "Acme Corp.", + logo: AudioWaveform, + plan: "Startup", + }, + { + name: "Evil Corp.", + logo: Command, + plan: "Free", + }, + ], + navMain: [ + { + title: "Porfile", + icon: SquareTerminal, + url: '/profile', + }, + { + title: "Playground", + icon: SquareTerminal, + isActive: true, + items: [ + { + title: "History", + url: "#", + isActive: true + }, + { + title: "Starred", + url: "#", + }, + { + title: "Settings", + url: "#", + }, + ], + }, + { + title: "Models", + url: "#", + icon: Bot, + items: [ + { + title: "Genesis", + url: "#", + }, + { + title: "Explorer", + url: "#", + }, + { + title: "Quantum", + url: "#", + }, + ], + }, + { + title: "Documentation", + url: "#", + icon: BookOpen, + items: [ + { + title: "Introduction", + url: "#", + }, + { + title: "Get Started", + url: "#", + }, + { + title: "Tutorials", + url: "#", + }, + { + title: "Changelog", + url: "#", + }, + ], + }, + { + title: "Settings", + url: "#", + icon: Settings2, + items: [ + { + title: "General", + url: "#", + }, + { + title: "Team", + url: "#", + }, + { + title: "Billing", + url: "#", + }, + { + title: "Limits", + url: "#", + }, + ], + }, + ], + projects: [ + { + name: "Design Engineering", + url: "#", + icon: Frame, + }, + { + name: "Sales & Marketing", + url: "#", + icon: PieChart, + }, + { + name: "Travel", + url: "#", + icon: Map, + }, + ], +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + + + + + ) +} diff --git a/ee/tabby-ui/components/header.tsx b/ee/tabby-ui/components/header.tsx index 1b6fcfd88c39..3c6978c5ae08 100644 --- a/ee/tabby-ui/components/header.tsx +++ b/ee/tabby-ui/components/header.tsx @@ -13,6 +13,7 @@ import { ClientOnly } from './client-only' import { ThemeToggle } from './theme-toggle' import { MyAvatar } from './user-avatar' import UserPanel from './user-panel' +import { SidebarTrigger } from './ui/sidebar' export function Header() { const { data } = useHealth() @@ -23,6 +24,7 @@ export function Header() { return (
+ {newVersionAvailable && ( + + {items.map((item) => ( + + + + + {item.icon && } + {item.title} + {!!item.items?.length && ( + + )} + + + + + {item.items?.map((subItem) => ( + + + + {subItem.title} + + + + ))} + + + + + ))} + + + ) +} diff --git a/ee/tabby-ui/components/nav-projects.tsx b/ee/tabby-ui/components/nav-projects.tsx new file mode 100644 index 000000000000..f50b20de15c4 --- /dev/null +++ b/ee/tabby-ui/components/nav-projects.tsx @@ -0,0 +1,89 @@ +"use client" + +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 ( + + Projects + + {projects.map((item) => ( + + + + + {item.name} + + + + + + + More + + + + + + View Project + + + + Share Project + + + + + Delete Project + + + + + ))} + + + + More + + + + + ) +} diff --git a/ee/tabby-ui/components/nav-user.tsx b/ee/tabby-ui/components/nav-user.tsx new file mode 100644 index 000000000000..d12ef780c278 --- /dev/null +++ b/ee/tabby-ui/components/nav-user.tsx @@ -0,0 +1,114 @@ +"use client" + +import { + BadgeCheck, + Bell, + ChevronsUpDown, + CreditCard, + LogOut, + Sparkles, +} from "lucide-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavUser({ + user, +}: { + user: { + name: string + email: string + avatar: string + } +}) { + const { isMobile } = useSidebar() + + return ( + + + + + + + + CN + +
+ {user.name} + {user.email} +
+ +
+
+ + +
+ + + CN + +
+ {user.name} + {user.email} +
+
+
+ + + + + Upgrade to Pro + + + + + + + Account + + + + Billing + + + + Notifications + + + + + + Log out + +
+
+
+
+ ) +} diff --git a/ee/tabby-ui/components/team-switcher.tsx b/ee/tabby-ui/components/team-switcher.tsx new file mode 100644 index 000000000000..2808e0a4f1c8 --- /dev/null +++ b/ee/tabby-ui/components/team-switcher.tsx @@ -0,0 +1,89 @@ +"use client" + +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 ( + + + + + +
+ +
+
+ + {activeTeam.name} + + {activeTeam.plan} +
+ +
+
+ + + Teams + + {teams.map((team, index) => ( + setActiveTeam(team)} + className="gap-2 p-2" + > +
+ +
+ {team.name} + ⌘{index + 1} +
+ ))} + + +
+ +
+
Add team
+
+
+
+
+
+ ) +} diff --git a/ee/tabby-ui/components/ui/breadcrumb.tsx b/ee/tabby-ui/components/ui/breadcrumb.tsx new file mode 100644 index 000000000000..60e6c96f72f0 --- /dev/null +++ b/ee/tabby-ui/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>