From fdbbdc2f22fff908b8aaf67a5585cd29428a97c8 Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Tue, 28 Apr 2026 12:09:38 +0200 Subject: [PATCH] feat: add sidebar component --- src/components/Sidebar.tsx | 112 +++++++++++++++++++++++++++++++++++++ src/globals.css | 11 +++- 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/components/Sidebar.tsx diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..083dc63 --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,112 @@ +'use client'; + +import { useState, useSyncExternalStore } from 'react'; + +export type SidebarPanel = { + id: string; + label: string; + icon: React.ReactNode; + component: React.ReactNode; +}; + +type SidebarProps = { + panels: SidebarPanel[]; +}; + +function useIsWide(): boolean { + return useSyncExternalStore( + (cb) => { + window.addEventListener('resize', cb); + return () => window.removeEventListener('resize', cb); + }, + () => window.innerWidth >= 768, // client snapshot + () => false, // server snapshot — must match initial SSR HTML + ); +} + +const CLOSED = '__closed__'; + +export default function Sidebar({ panels }: SidebarProps) { + const isWide = useIsWide(); + + const [activeId, setActiveId] = useState(null); + + const effectiveId = + activeId === CLOSED ? null + : activeId ?? (isWide ? (panels[0]?.id ?? null) : null); + + const activePanel = panels.find((p) => p.id === effectiveId); + const isOpen = !!activePanel; + + const toggle = (id: string) => + setActiveId((prev) => (prev === id || (effectiveId === id && prev === null) ? CLOSED : id)); + + return ( +
+ {/* Expanded panel */} +
+ {activePanel && ( +
+
+ + {activePanel.label} + + +
+
+ {activePanel.component} +
+
+ )} +
+ + {/* Back drop */} + {isOpen && !isWide && ( +
setActiveId(CLOSED)} + /> + )} + + {/* Icon rail */} +
+ {panels.map((panel) => ( + + ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/globals.css b/src/globals.css index 494b42f..b3b80af 100644 --- a/src/globals.css +++ b/src/globals.css @@ -29,6 +29,7 @@ body { --text-primary: #ffffff; --accent: #00d4ff; + --accent2: #7b2ff7; --muted-color: #ffffff33; @@ -52,7 +53,7 @@ body { --accent: #0077ff; - --muted-color: #11111133; + --muted-color: #ffffff33; --color-base: #ffffff; --color-inverse: #000000; @@ -70,6 +71,14 @@ body { background: var(--bg-secondary); } +.bg-accent { + background: var(--accent); +} + +.bg-accent2 { + background: var(--accent2); +} + .text-primary { color: var(--text-primary); }