From c8890f33c2e9e15fdf89bb2b467eda2c836d95d2 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Sun, 7 Dec 2025 17:21:24 -0500 Subject: [PATCH] feat(ui): redesign sidebar and fix disclaimer - Redesign AppSidebar with collapsible groups ("Identity & Access", "System") - Nest "Shared Data" under "Accounts" in sidebar - Add collapsible UI component for sidebar nesting - Fix LocalhostDisclaimer by moving it inside SidebarProvider - Add roadmap entry for sidebar redesign --- docs/project-roadmap.md | 10 ++ ui/bun.lock | 3 + ui/package.json | 1 + ui/src/App.tsx | 2 +- ui/src/components/app-sidebar.tsx | 127 +++++++++++++++++---- ui/src/components/localhost-disclaimer.tsx | 32 ++++-- ui/src/components/ui/collapsible.tsx | 11 ++ 7 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 ui/src/components/ui/collapsible.tsx diff --git a/docs/project-roadmap.md b/docs/project-roadmap.md index 483509e4..7625d7ca 100644 --- a/docs/project-roadmap.md +++ b/docs/project-roadmap.md @@ -437,6 +437,16 @@ src/types/ - Deployment pipelines with type checks - Infrastructure as Code with TypeScript +#### Phase 4: UI Enhancements - Sidebar Redesign (v4.7.0) +**Timeline**: 1-2 months (TBD) + +**Priorities**: +1. **Sidebar Redesign Implementation** + - Implement new sidebar component with modern UI/UX principles. + - Ensure responsiveness across different screen sizes. + - Integrate with existing routing and navigation logic. + - Improve accessibility features for the sidebar. + ### Technical Debt Resolution #### Immediate Priorities (v4.4.1) diff --git a/ui/bun.lock b/ui/bun.lock index 481fbc61..4d5fd5d2 100644 --- a/ui/bun.lock +++ b/ui/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@hookform/resolvers": "^5.2.2", "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", @@ -189,6 +190,8 @@ "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], diff --git a/ui/package.json b/ui/package.json index d4d847dc..fef4ae5e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,6 +17,7 @@ "dependencies": { "@hookform/resolvers": "^5.2.2", "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index bf31d3d8..52f572ba 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -29,6 +29,7 @@ function Layout() { + ); @@ -50,7 +51,6 @@ export default function App() { - ); diff --git a/ui/src/components/app-sidebar.tsx b/ui/src/components/app-sidebar.tsx index 4a1617bf..670570e6 100644 --- a/ui/src/components/app-sidebar.tsx +++ b/ui/src/components/app-sidebar.tsx @@ -1,51 +1,136 @@ import { Link, useLocation } from 'react-router-dom'; -import { Home, Key, Zap, Users, Settings, Activity, FolderOpen } from 'lucide-react'; +import { Home, Key, Zap, Users, Settings, Activity, FolderOpen, ChevronRight } from 'lucide-react'; import { Sidebar, SidebarContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, + SidebarMenuSub, + SidebarMenuSubItem, + SidebarMenuSubButton, + SidebarGroup, + SidebarGroupLabel, SidebarHeader, SidebarFooter, SidebarTrigger, + SidebarGroupContent, } from '@/components/ui/sidebar'; import { CcsLogo } from '@/components/ccs-logo'; import { useSidebar } from '@/hooks/use-sidebar'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -const navItems = [ - { path: '/', icon: Home, label: 'Home' }, - { path: '/api', icon: Key, label: 'API Profiles' }, - { path: '/cliproxy', icon: Zap, label: 'CLIProxy' }, - { path: '/accounts', icon: Users, label: 'Accounts' }, - { path: '/settings', icon: Settings, label: 'Settings' }, - { path: '/health', icon: Activity, label: 'Health' }, - { path: '/shared', icon: FolderOpen, label: 'Shared Data' }, +// Define navigation groups +const navGroups = [ + { + title: 'General', + items: [{ path: '/', icon: Home, label: 'Home' }], + }, + { + title: 'Identity & Access', + items: [ + { path: '/api', icon: Key, label: 'API Profiles' }, + { path: '/cliproxy', icon: Zap, label: 'CLIProxy' }, + { + path: '/accounts', + icon: Users, + label: 'Accounts', + isCollapsible: true, + children: [ + { path: '/accounts', label: 'All Accounts' }, + { path: '/shared', icon: FolderOpen, label: 'Shared Data' }, + ], + }, + ], + }, + { + title: 'System', + items: [ + { path: '/health', icon: Activity, label: 'Health' }, + { path: '/settings', icon: Settings, label: 'Settings' }, + ], + }, ]; export function AppSidebar() { const location = useLocation(); const { state } = useSidebar(); + // Helper to check if a route is active (including sub-routes if needed) + const isRouteActive = (path: string) => location.pathname === path; + + // Helper to check if a group/parent should be open based on active child + const isParentActive = (children: { path: string }[]) => { + return children.some((child) => isRouteActive(child.path)); + }; + return ( + - - {navItems.map((item) => ( - - - - - {item.label} - - - - ))} - + {navGroups.map((group, index) => ( + + {group.title && {group.title}} + + + {group.items.map((item) => ( + + {item.isCollapsible && item.children ? ( + + + + + {item.icon && } + + {item.label} + + + + + + + {item.children.map((child) => ( + + + + {child.label} + + + + ))} + + + + + ) : ( + + + {item.icon && } + {item.label} + + + )} + + ))} + + + + ))} + diff --git a/ui/src/components/localhost-disclaimer.tsx b/ui/src/components/localhost-disclaimer.tsx index 8e83b90c..8368143b 100644 --- a/ui/src/components/localhost-disclaimer.tsx +++ b/ui/src/components/localhost-disclaimer.tsx @@ -1,24 +1,42 @@ import { Shield, X } from 'lucide-react'; import { useState } from 'react'; +import { useSidebar } from '@/hooks/use-sidebar'; export function LocalhostDisclaimer() { const [dismissed, setDismissed] = useState(false); + const { state, isMobile } = useSidebar(); if (dismissed) return null; + // Calculate the left margin based on sidebar state + // When expanded: sidebar width is 16rem + // When collapsed: sidebar width is 3rem + // On mobile: sidebar is overlay, no margin needed + const getLeftMargin = () => { + if (isMobile) return '0'; + return state === 'expanded' ? '16rem' : '3rem'; + }; + return ( -
-
+
+
- - - This dashboard runs locally. All data stays on your machine. Never expose this server to - the internet. + + + This dashboard runs locally. All data stays on your machine. + Local dashboard - data stays on your device.
diff --git a/ui/src/components/ui/collapsible.tsx b/ui/src/components/ui/collapsible.tsx new file mode 100644 index 00000000..86ab87d8 --- /dev/null +++ b/ui/src/components/ui/collapsible.tsx @@ -0,0 +1,11 @@ +'use client'; + +import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent };