Skip to content

Commit db3ece4

Browse files
committed
refactor: implement layout store for sidebar state management; update AppLayout and NavigationSidebar components to utilize new store
1 parent 3c33db3 commit db3ece4

File tree

4 files changed

+72
-90
lines changed

4 files changed

+72
-90
lines changed

packages/web/app/AppLayout.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use client';
22

33
import React, { useEffect, useState } from 'react';
4-
import { NavigationSidebar, ErrorBoundary, AppLayoutSkeleton, TopNavbar } from '@/components';
4+
import { AppLayoutSkeleton, ErrorBoundary, NavigationSidebar, TopNavbar } from '@/components';
55
import { SidebarProvider } from '@/components/ui/sidebar';
6+
import { useLayoutStore } from '@/stores';
67

78
interface AppLayoutProps {
89
children: React.ReactNode;
@@ -11,6 +12,8 @@ interface AppLayoutProps {
1112
export function AppLayout({ children }: AppLayoutProps) {
1213
const [mounted, setMounted] = useState(false);
1314

15+
const { sidebarOpen, setSidebarOpen } = useLayoutStore();
16+
1417
// Handle client-side hydration
1518
useEffect(() => {
1619
setMounted(true);
@@ -25,7 +28,13 @@ export function AppLayout({ children }: AppLayoutProps) {
2528
<ErrorBoundary>
2629
<div className="min-h-screen bg-background w-full">
2730
<TopNavbar />
28-
<SidebarProvider>
31+
<SidebarProvider
32+
defaultOpen={sidebarOpen}
33+
open={sidebarOpen}
34+
onOpenChange={(open) => {
35+
setSidebarOpen(open);
36+
}}
37+
>
2938
<div className="flex w-full h-[calc(100vh-3rem)]">
3039
<NavigationSidebar />
3140
<div className="flex-1 flex flex-col w-full">

packages/web/app/components/layout/NavigationSidebar.tsx

Lines changed: 39 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import {
1111
SidebarMenuItem,
1212
SidebarTrigger,
1313
} from '@/components/ui/sidebar';
14-
import { Home, Package, SquareKanban } from 'lucide-react';
14+
import { Home, SquareKanban } from 'lucide-react';
1515

1616
interface SidebarItem {
1717
key: string;
1818
label: string;
1919
icon: React.ReactNode;
20-
path: string;
20+
onClick?: () => void;
2121
}
2222

2323
export function NavigationSidebar() {
@@ -37,98 +37,48 @@ export function NavigationSidebar() {
3737
return false;
3838
};
3939

40-
// Get contextual menu items based on current path
41-
const getMenuItems = () => {
42-
if (!mounted) return [];
43-
44-
const pathParts = pathname.split('/').filter(Boolean);
45-
46-
// Overview page (/)
47-
if (pathname === '/') {
48-
return [
49-
{
50-
key: 'projects',
51-
label: 'Projects',
52-
icon: <Package size={16} />,
53-
},
54-
];
55-
}
56-
57-
// Projects page (/projects)
58-
if (pathname === '/projects') {
59-
return [
60-
{
61-
key: 'projects',
62-
label: 'Projects',
63-
icon: <Package size={16} />,
64-
},
65-
];
40+
const getProjectId = () => {
41+
const matched = pathname.match(/\/projects\/(\w+)/);
42+
if (matched) {
43+
return matched[1];
6644
}
45+
return null;
46+
};
6747

68-
if (pathname.match(/^\/projects\/\w+\/devlogs/)) {
69-
}
48+
const projectsMenuItems = [
49+
{
50+
key: 'overview',
51+
label: 'Overview',
52+
icon: <Home size={16} />,
53+
onClick: () => router.push('/projects'),
54+
},
55+
];
56+
const projectDetailMenuItems = [
57+
{
58+
key: 'overview',
59+
label: 'Overview',
60+
icon: <Home size={16} />,
61+
onClick: () => router.push(`/projects/${getProjectId()}`),
62+
},
63+
{
64+
key: 'list',
65+
label: 'Devlogs',
66+
icon: <SquareKanban size={16} />,
67+
onClick: () => router.push(`/projects/${getProjectId()}/devlogs`),
68+
},
69+
];
7070

71-
// Project detail page (/projects/[id])
72-
if (pathParts.length === 2 && pathParts[0] === 'projects') {
73-
return [
74-
{
75-
key: 'overview',
76-
label: 'Overview',
77-
icon: <Home size={16} />,
78-
},
79-
{
80-
key: 'list',
81-
label: 'Devlogs',
82-
icon: <SquareKanban size={16} />,
83-
},
84-
];
85-
}
71+
// Get contextual menu items based on current path
72+
const getMenuItems = (): SidebarItem[] => {
73+
if (!mounted) return [];
8674

87-
// Project devlogs page (/projects/[id]/devlogs)
88-
if (pathParts.length === 3 && pathParts[0] === 'projects' && pathParts[2] === 'devlogs') {
89-
return [
90-
{
91-
key: 'overview',
92-
label: 'Overview',
93-
icon: <Home size={16} />,
94-
},
95-
{
96-
key: 'list',
97-
label: 'Devlogs',
98-
icon: <SquareKanban size={16} />,
99-
},
100-
];
101-
}
75+
const pathParts = pathname.split('/').filter(Boolean);
10276

103-
// Devlog detail page (/projects/[id]/devlogs/[devlogId])
104-
if (pathParts.length === 4 && pathParts[0] === 'projects' && pathParts[2] === 'devlogs') {
105-
return [
106-
{
107-
key: 'overview',
108-
label: 'Overview',
109-
icon: <Home size={16} />,
110-
},
111-
{
112-
key: 'list',
113-
label: 'Devlogs',
114-
icon: <SquareKanban size={16} />,
115-
},
116-
];
77+
if (pathParts.length < 2) {
78+
return projectsMenuItems;
79+
} else {
80+
return projectDetailMenuItems;
11781
}
118-
119-
// Default fallback
120-
return [
121-
{
122-
key: 'overview',
123-
label: 'Overview',
124-
icon: <Home size={16} />,
125-
},
126-
{
127-
key: 'projects',
128-
label: 'Projects',
129-
icon: <Package size={16} />,
130-
},
131-
];
13282
};
13383

13484
// Determine selected key based on current pathname and menu items
@@ -161,6 +111,7 @@ export function NavigationSidebar() {
161111
<SidebarMenuButton
162112
isActive={getSelectedKey() === item.key}
163113
className="flex items-center gap-3 px-4 py-3 text-sm font-medium min-h-[44px] rounded-md"
114+
onClick={item.onClick}
164115
>
165116
{item.icon}
166117
<span>{item.label}</span>

packages/web/app/stores/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Export all stores
2+
export * from './layout-store';
23
export * from './project-store';
34
export * from './devlog-store';
45
export * from './realtime-store';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client';
2+
3+
import { create } from 'zustand';
4+
import { subscribeWithSelector } from 'zustand/middleware';
5+
6+
interface LayoutState {
7+
// State
8+
sidebarOpen: boolean;
9+
10+
// Actions
11+
setSidebarOpen: (open: boolean) => void;
12+
}
13+
14+
export const useLayoutStore = create<LayoutState>()(
15+
subscribeWithSelector((set) => ({
16+
sidebarOpen: false,
17+
18+
// Action to set sidebar open state
19+
setSidebarOpen: (open: boolean) => set({ sidebarOpen: open }),
20+
})),
21+
);

0 commit comments

Comments
 (0)