Skip to content

Commit 91bf3dc

Browse files
feat: add dashboard layout components
1 parent f88235a commit 91bf3dc

6 files changed

Lines changed: 115 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ReactNode } from 'react';
2+
3+
export function AppLayout({ children }: { children: ReactNode }) {
4+
return <div className="min-h-dvh">{children}</div>;
5+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ReactNode } from 'react';
2+
import { AppLayout } from './AppLayout';
3+
import { Sidebar } from './Sidebar';
4+
import { Topbar } from './Topbar';
5+
6+
export function DashboardLayout({ children, topbarRight }: { children: ReactNode; topbarRight?: ReactNode }) {
7+
return (
8+
<AppLayout>
9+
<div className="mx-auto max-w-7xl px-4 py-6 md:px-6 md:py-10">
10+
<div className="grid gap-6 md:grid-cols-[260px_1fr]">
11+
<Sidebar />
12+
<main className="space-y-6">
13+
<Topbar rightSlot={topbarRight} />
14+
{children}
15+
</main>
16+
</div>
17+
</div>
18+
</AppLayout>
19+
);
20+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use client';
2+
3+
import Link from 'next/link';
4+
import { usePathname } from 'next/navigation';
5+
6+
const items = [
7+
{ href: '/dashboard', label: 'Dashboard' },
8+
{ href: '/dashboard/groups', label: 'Groups' },
9+
{ href: '/dashboard/activity', label: 'Activity' },
10+
{ href: '/dashboard/profile', label: 'Profile' },
11+
{ href: '/dashboard/settings', label: 'Settings' },
12+
] as const;
13+
14+
function isActive(pathname: string, href: string): boolean {
15+
if (href === '/dashboard') {
16+
return pathname === href;
17+
}
18+
return pathname === href || pathname.startsWith(`${href}/`);
19+
}
20+
21+
export function Sidebar() {
22+
const pathname = usePathname();
23+
24+
return (
25+
<aside className="md:sticky md:top-6">
26+
<div className="rounded-2xl border border-border bg-card p-4 shadow-glass backdrop-blur-glass">
27+
<div className="flex items-center justify-between">
28+
<Link href="/dashboard" className="text-sm font-semibold tracking-wide text-text-primary">
29+
FairShare
30+
</Link>
31+
<span className="text-xs text-text-secondary">Web</span>
32+
</div>
33+
34+
<nav className="mt-4 flex gap-1 overflow-x-auto md:flex-col md:gap-1.5 md:overflow-visible">
35+
{items.map((item) => {
36+
const active = isActive(pathname, item.href);
37+
return (
38+
<Link
39+
key={item.href}
40+
href={item.href}
41+
className={[
42+
'whitespace-nowrap rounded-xl px-3 py-2 text-sm transition',
43+
active
44+
? 'bg-white/10 text-text-primary'
45+
: 'text-text-secondary hover:bg-white/5 hover:text-text-primary',
46+
].join(' ')}
47+
>
48+
{item.label}
49+
</Link>
50+
);
51+
})}
52+
</nav>
53+
</div>
54+
</aside>
55+
);
56+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client';
2+
3+
import { usePathname } from 'next/navigation';
4+
5+
function titleFromPath(pathname: string): string {
6+
if (pathname === '/dashboard') return 'Dashboard';
7+
if (pathname.startsWith('/dashboard/groups')) return 'Groups';
8+
if (pathname.startsWith('/dashboard/activity')) return 'Activity';
9+
if (pathname.startsWith('/dashboard/profile')) return 'Profile';
10+
if (pathname.startsWith('/dashboard/settings')) return 'Settings';
11+
return 'FairShare';
12+
}
13+
14+
export function Topbar({ rightSlot }: { rightSlot?: React.ReactNode }) {
15+
const pathname = usePathname();
16+
const title = titleFromPath(pathname);
17+
18+
return (
19+
<header className="flex items-center justify-between gap-3">
20+
<div>
21+
<h1 className="text-xl font-semibold tracking-tight text-text-primary md:text-2xl">{title}</h1>
22+
<p className="mt-1 text-sm text-text-secondary">Manage your groups and shared expenses.</p>
23+
</div>
24+
<div className="flex items-center gap-2">{rightSlot}</div>
25+
</header>
26+
);
27+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './AppLayout';
2+
export * from './DashboardLayout';
3+
export * from './Sidebar';
4+
export * from './Topbar';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const glassClassName =
2+
'rounded-2xl border border-border bg-card shadow-glass backdrop-blur-glass supports-[backdrop-filter]:bg-card';
3+

0 commit comments

Comments
 (0)