Skip to content

Commit 66c0d17

Browse files
committed
feat: enhance MainSidebar and ProjectSwitcher components with internationalization support; update localization files for English and Chinese
1 parent ec949bd commit 66c0d17

File tree

5 files changed

+296
-170
lines changed

5 files changed

+296
-170
lines changed

packages/ui-vite/src/components/MainSidebar.tsx

Lines changed: 98 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,48 @@
11
import { useEffect, useState } from 'react';
22
import { Link, useLocation } from 'react-router-dom';
3+
import { useTranslation } from 'react-i18next';
34
import { Home, FileText, BarChart3, Network, Settings, ChevronLeft, ChevronRight, BookOpen, X } from 'lucide-react';
45
import { cn } from '../lib/utils';
56
import { ProjectSwitcher } from './ProjectSwitcher';
67

7-
const STORAGE_KEY = 'ui-vite-main-sidebar-collapsed';
8+
const STORAGE_KEY = 'main-sidebar-collapsed';
89

9-
interface NavItem {
10-
path: string;
11-
label: string;
10+
interface SidebarLinkProps {
11+
to: string;
1212
icon: typeof Home;
13+
label: string;
14+
description?: string;
15+
currentPath: string;
16+
isCollapsed: boolean;
1317
}
1418

15-
const navItems: NavItem[] = [
16-
{ path: '/', label: 'Dashboard', icon: Home },
17-
{ path: '/specs', label: 'All Specifications', icon: FileText },
18-
{ path: '/dependencies', label: 'Dependency Graph', icon: Network },
19-
{ path: '/stats', label: 'Analytics', icon: BarChart3 },
20-
{ path: '/context', label: 'Project Context', icon: BookOpen },
21-
{ path: '/settings', label: 'Settings', icon: Settings },
22-
];
19+
function SidebarLink({ to, icon: Icon, label, description, currentPath, isCollapsed }: SidebarLinkProps) {
20+
const normalize = (value: string) => value.replace(/\/$/, '') || '/';
21+
const normalizedTo = normalize(to);
22+
const normalizedPath = normalize(currentPath);
23+
const isHome = normalizedTo === '/';
24+
const isActive = isHome ? normalizedPath === '/' : normalizedPath.startsWith(normalizedTo);
25+
26+
return (
27+
<Link
28+
to={to}
29+
className={cn(
30+
'flex items-center gap-3 rounded-lg px-3 py-2 transition-colors',
31+
'hover:bg-accent hover:text-accent-foreground',
32+
isActive && 'bg-accent text-accent-foreground font-medium',
33+
isCollapsed && 'justify-center px-2'
34+
)}
35+
>
36+
<Icon className={cn('h-5 w-5 shrink-0', isActive && 'text-primary')} />
37+
{!isCollapsed && (
38+
<div className="flex flex-col">
39+
<span className="text-sm">{label}</span>
40+
{description && <span className="text-xs text-muted-foreground">{description}</span>}
41+
</div>
42+
)}
43+
</Link>
44+
);
45+
}
2346

2447
interface MainSidebarProps {
2548
mobileOpen?: boolean;
@@ -28,97 +51,97 @@ interface MainSidebarProps {
2851

2952
export function MainSidebar({ mobileOpen = false, onMobileClose }: MainSidebarProps) {
3053
const location = useLocation();
31-
const [collapsed, setCollapsed] = useState(false);
32-
33-
useEffect(() => {
54+
const { t } = useTranslation('common');
55+
const [collapsed, setCollapsed] = useState(() => {
56+
if (typeof window === 'undefined') return false;
3457
const stored = localStorage.getItem(STORAGE_KEY);
35-
setCollapsed(stored === 'true');
36-
}, []);
58+
return stored === 'true';
59+
});
3760

3861
useEffect(() => {
39-
document.documentElement.style.setProperty('--main-sidebar-width', collapsed ? '64px' : '240px');
40-
localStorage.setItem(STORAGE_KEY, String(collapsed));
62+
document.documentElement.style.setProperty('--main-sidebar-width', collapsed ? '60px' : '240px');
63+
if (typeof window !== 'undefined') {
64+
localStorage.setItem(STORAGE_KEY, String(collapsed));
65+
}
4166
}, [collapsed]);
4267

4368
// Close mobile sidebar when route changes
4469
useEffect(() => {
4570
if (mobileOpen && onMobileClose) {
4671
onMobileClose();
4772
}
48-
}, [location.pathname]);
73+
}, [location.pathname, mobileOpen, onMobileClose]);
74+
75+
const navItems = [
76+
{ path: '/', label: t('navigation.home'), description: t('navigation.dashboard'), icon: Home },
77+
{ path: '/specs', label: t('navigation.specs'), description: t('navigation.allSpecifications'), icon: FileText },
78+
{ path: '/dependencies', label: t('navigation.dependencies'), description: t('navigation.dependencyGraph'), icon: Network },
79+
{ path: '/stats', label: t('navigation.stats'), description: t('navigation.analytics'), icon: BarChart3 },
80+
{ path: '/context', label: t('navigation.context'), description: t('navigation.projectContext'), icon: BookOpen },
81+
{ path: '/settings', label: t('navigation.settings'), description: t('navigation.settingsDescription'), icon: Settings },
82+
];
4983

5084
return (
5185
<>
52-
{/* Mobile overlay */}
86+
{/* Mobile overlay backdrop */}
5387
{mobileOpen && (
5488
<div
5589
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
5690
onClick={onMobileClose}
5791
/>
5892
)}
5993

60-
{/* Sidebar */}
6194
<aside
6295
className={cn(
63-
'flex flex-col border-r bg-background h-[calc(100vh-3.5rem)] transition-all duration-300',
64-
// Desktop styles
65-
'hidden lg:flex sticky top-14',
66-
collapsed ? 'w-16' : 'w-60',
67-
// Mobile styles
68-
'lg:hidden fixed left-0 top-14 z-50 w-60',
69-
mobileOpen ? 'translate-x-0' : '-translate-x-full',
70-
// Show on desktop OR when mobile menu is open
71-
'lg:translate-x-0 lg:flex'
96+
'border-r bg-background transition-all duration-300 flex-shrink-0',
97+
// Desktop behavior
98+
"hidden lg:flex lg:sticky lg:top-14 lg:h-[calc(100vh-3.5rem)]",
99+
collapsed ? "lg:w-[60px]" : "lg:w-[240px]",
100+
// Mobile behavior - show as overlay when open
101+
mobileOpen && "fixed inset-y-0 left-0 z-[60] flex w-[280px]"
72102
)}
73103
>
74-
{/* Mobile close button */}
75-
<div className="lg:hidden flex justify-end p-2 border-b">
76-
<button
77-
onClick={onMobileClose}
78-
className="p-2 hover:bg-secondary rounded-md transition-colors"
79-
aria-label="Close menu"
80-
>
81-
<X className="h-5 w-5" />
82-
</button>
83-
</div>
84-
85-
<div className="px-3 py-4">
86-
<ProjectSwitcher />
87-
</div>
88-
89-
<nav className="flex-1 px-2 space-y-1">
90-
{navItems.map((item) => {
91-
const Icon = item.icon;
92-
const isActive = item.path === '/'
93-
? location.pathname === '/'
94-
: location.pathname.startsWith(item.path);
104+
<div className="flex flex-col h-full w-full">
105+
{/* Mobile close button */}
106+
<div className="lg:hidden flex justify-end p-2 border-b">
107+
<button
108+
onClick={onMobileClose}
109+
className="p-2 hover:bg-secondary rounded-md transition-colors"
110+
aria-label={t('navigation.closeMenu')}
111+
>
112+
<X className="h-5 w-5" />
113+
</button>
114+
</div>
95115

96-
return (
97-
<Link
116+
<nav className="flex-1 px-2 py-2 space-y-1">
117+
<div className="mb-4 flex items-center justify-center">
118+
<ProjectSwitcher collapsed={collapsed && !mobileOpen} />
119+
</div>
120+
{navItems.map((item) => (
121+
<SidebarLink
98122
key={item.path}
99123
to={item.path}
100-
className={cn(
101-
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors',
102-
'hover:bg-accent hover:text-accent-foreground',
103-
isActive && 'bg-accent text-accent-foreground font-medium',
104-
collapsed && 'justify-center px-2'
105-
)}
106-
>
107-
<Icon className={cn('h-5 w-5 shrink-0', isActive && 'text-primary')} />
108-
{!collapsed && <span className="truncate">{item.label}</span>}
109-
</Link>
110-
);
111-
})}
112-
</nav>
124+
icon={item.icon}
125+
label={item.label}
126+
description={!collapsed || mobileOpen ? item.description : undefined}
127+
currentPath={location.pathname}
128+
isCollapsed={collapsed && !mobileOpen}
129+
/>
130+
))}
131+
</nav>
113132

114-
<div className="p-2 border-t hidden lg:block">
115-
<button
116-
onClick={() => setCollapsed((prev) => !prev)}
117-
className={cn('w-full flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-secondary transition-colors', collapsed && 'px-2')}
118-
>
119-
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
120-
{!collapsed && <span className="text-xs">Collapse</span>}
121-
</button>
133+
<div className="hidden lg:block p-2 border-t">
134+
<button
135+
onClick={() => setCollapsed((prev) => !prev)}
136+
className={cn(
137+
'w-full flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-secondary transition-colors',
138+
collapsed && 'px-2'
139+
)}
140+
>
141+
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
142+
{!collapsed && <span className="text-xs">{t('navigation.collapse')}</span>}
143+
</button>
144+
</div>
122145
</div>
123146
</aside>
124147
</>

0 commit comments

Comments
 (0)