mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
interface SidebarContextValue {
|
|
// Mobile drawer + back-compat (existing behavior, unchanged).
|
|
sidebarOpen: boolean;
|
|
setSidebarOpen: (open: boolean) => void;
|
|
toggleSidebar: () => void;
|
|
isMobile: boolean;
|
|
// Back-compat collapse API. The global navigation is permanently expanded;
|
|
// these values remain for callers that have not removed old requests.
|
|
collapsed: boolean;
|
|
setCollapsed: (next: boolean) => void;
|
|
toggleCollapsed: () => void;
|
|
// Retained compatibility value; always false now that the rail is retired.
|
|
collapseLocked: boolean;
|
|
// Retained compatibility value; the retired rail can no longer peek.
|
|
peeking: boolean;
|
|
setPeeking: (next: boolean) => void;
|
|
// Legacy requests remain observable without affecting the global nav.
|
|
forceCollapsed: boolean;
|
|
setForceCollapsed: (next: boolean) => void;
|
|
// Route-requested collapse retained for integration compatibility.
|
|
routeRequestsCollapsed: boolean;
|
|
setRouteRequestsCollapsed: (next: boolean) => void;
|
|
}
|
|
|
|
const SidebarContext = createContext<SidebarContextValue | null>(null);
|
|
|
|
const MOBILE_BREAKPOINT = 768;
|
|
|
|
export function SidebarProvider({ children }: { children: ReactNode }) {
|
|
const [isMobile, setIsMobile] = useState(() => window.innerWidth < MOBILE_BREAKPOINT);
|
|
const [sidebarOpen, setSidebarOpen] = useState(() => window.innerWidth >= MOBILE_BREAKPOINT);
|
|
|
|
const [routeRequestsCollapsed, setRouteRequestsCollapsed] = useState(false);
|
|
const [forceCollapsed, setForceCollapsed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
const onChange = (e: MediaQueryListEvent) => {
|
|
setIsMobile(e.matches);
|
|
setSidebarOpen(!e.matches);
|
|
};
|
|
mql.addEventListener("change", onChange);
|
|
return () => mql.removeEventListener("change", onChange);
|
|
}, []);
|
|
|
|
// The icon rail has been retired. Keep the old API inert so routes and
|
|
// plugins compiled against it cannot collapse the global navigation.
|
|
const collapsed = false;
|
|
const collapseLocked = false;
|
|
const peeking = false;
|
|
const setCollapsed = useCallback((_next: boolean) => {}, []);
|
|
const toggleCollapsed = useCallback(() => {}, []);
|
|
const setPeeking = useCallback((_next: boolean) => {}, []);
|
|
|
|
const toggleSidebar = useCallback(() => setSidebarOpen((v) => !v), []);
|
|
|
|
const value = useMemo<SidebarContextValue>(
|
|
() => ({
|
|
sidebarOpen,
|
|
setSidebarOpen,
|
|
toggleSidebar,
|
|
isMobile,
|
|
collapsed,
|
|
setCollapsed,
|
|
toggleCollapsed,
|
|
collapseLocked,
|
|
peeking,
|
|
setPeeking,
|
|
forceCollapsed,
|
|
setForceCollapsed,
|
|
routeRequestsCollapsed,
|
|
setRouteRequestsCollapsed,
|
|
}),
|
|
[
|
|
sidebarOpen,
|
|
setSidebarOpen,
|
|
toggleSidebar,
|
|
isMobile,
|
|
collapsed,
|
|
setCollapsed,
|
|
toggleCollapsed,
|
|
collapseLocked,
|
|
peeking,
|
|
setPeeking,
|
|
forceCollapsed,
|
|
setForceCollapsed,
|
|
routeRequestsCollapsed,
|
|
setRouteRequestsCollapsed,
|
|
],
|
|
);
|
|
|
|
return <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>;
|
|
}
|
|
|
|
export function useSidebar() {
|
|
const ctx = useContext(SidebarContext);
|
|
if (!ctx) {
|
|
throw new Error("useSidebar must be used within SidebarProvider");
|
|
}
|
|
return ctx;
|
|
}
|