Files
stack/apps/web/src/components/layout/sidebar-context.tsx
Jason Woltje de64695ac5
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
feat(web): design system — ms-* tokens, ThemeProvider, MosaicLogo, sidebar (#221)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-03-21 12:57:24 +00:00

68 lines
1.6 KiB
TypeScript

'use client';
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
interface SidebarContextValue {
collapsed: boolean;
toggleCollapsed: () => void;
mobileOpen: boolean;
setMobileOpen: (open: boolean) => void;
isMobile: boolean;
}
const MOBILE_MAX_WIDTH = 767;
const SidebarContext = createContext<SidebarContextValue | undefined>(undefined);
export function SidebarProvider({ children }: { children: ReactNode }): React.JSX.Element {
const [collapsed, setCollapsed] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia(`(max-width: ${String(MOBILE_MAX_WIDTH)}px)`);
const syncState = (matches: boolean): void => {
setIsMobile(matches);
if (!matches) {
setMobileOpen(false);
}
};
syncState(mediaQuery.matches);
const handleChange = (event: MediaQueryListEvent): void => {
syncState(event.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, []);
return (
<SidebarContext.Provider
value={{
collapsed,
toggleCollapsed: () => setCollapsed((value) => !value),
mobileOpen,
setMobileOpen,
isMobile,
}}
>
{children}
</SidebarContext.Provider>
);
}
export function useSidebar(): SidebarContextValue {
const context = useContext(SidebarContext);
if (!context) {
throw new Error('useSidebar must be used within SidebarProvider');
}
return context;
}