"use client" import * as React from "react" type Layout = "fixed" | "full" interface LayoutProviderProps { children: React.ReactNode defaultLayout?: Layout forcedLayout?: Layout storageKey?: string attribute?: string | string[] value?: Record } interface LayoutProviderState { layout: Layout setLayout: (layout: Layout | ((prev: Layout) => Layout)) => void forcedLayout?: Layout } const isServer = typeof window === "undefined" const LayoutContext = React.createContext( undefined ) const saveToLS = (storageKey: string, value: string) => { try { localStorage.setItem(storageKey, value) } catch { // Unsupported } } const useLayout = () => { const context = React.useContext(LayoutContext) if (context === undefined) { throw new Error("useLayout must be used within a LayoutProvider") } return context } const Layout = ({ forcedLayout, storageKey = "layout", defaultLayout = "full", attribute = "class", value, children, }: LayoutProviderProps) => { const [layout, setLayoutState] = React.useState(() => { if (isServer) return defaultLayout try { const saved = localStorage.getItem(storageKey) if (saved === "fixed" || saved === "full") { return saved } return defaultLayout } catch { return defaultLayout } }) const attrs = React.useMemo( () => (!value ? ["layout-fixed", "layout-full"] : Object.values(value)), [value] ) const applyLayout = React.useCallback( (layout: Layout) => { if (!layout) return const name = value ? value[layout] : `layout-${layout}` const d = document.documentElement const handleAttribute = (attr: string) => { if (attr === "class") { d.classList.remove(...attrs) if (name) d.classList.add(name) } else if (attr.startsWith("data-")) { if (name) { d.setAttribute(attr, name) } else { d.removeAttribute(attr) } } } if (Array.isArray(attribute)) attribute.forEach(handleAttribute) else handleAttribute(attribute) }, [attrs, attribute, value] ) const setLayout = React.useCallback( (value: Layout | ((prev: Layout) => Layout)) => { if (typeof value === "function") { setLayoutState((prevLayout) => { const newLayout = value(prevLayout) saveToLS(storageKey, newLayout) return newLayout }) } else { setLayoutState(value) saveToLS(storageKey, value) } }, [storageKey] ) // localStorage event handling React.useEffect(() => { const handleStorage = (e: StorageEvent) => { if (e.key !== storageKey) return if (!e.newValue) { setLayout(defaultLayout) } else if (e.newValue === "fixed" || e.newValue === "full") { setLayoutState(e.newValue) } } window.addEventListener("storage", handleStorage) return () => window.removeEventListener("storage", handleStorage) }, [setLayout, storageKey, defaultLayout]) // Apply layout on mount and when it changes React.useEffect(() => { const currentLayout = forcedLayout ?? layout applyLayout(currentLayout) }, [forcedLayout, layout, applyLayout]) // Prevent layout changes during hydration const [isHydrated, setIsHydrated] = React.useState(false) React.useEffect(() => { setIsHydrated(true) }, []) const providerValue = React.useMemo( () => ({ layout: isHydrated ? layout : defaultLayout, setLayout, forcedLayout, }), [layout, setLayout, forcedLayout, isHydrated, defaultLayout] ) return ( {children} ) } const LayoutProvider = (props: LayoutProviderProps) => { const context = React.useContext(LayoutContext) // Ignore nested context providers, just passthrough children if (context) return <>{props.children} return } export { useLayout, LayoutProvider }