'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useLocalStorage, useMediaQuery } from '@djangocfg/ui-core/hooks'; import { CSS_VARS, DEFAULT_SIDEBAR, STORAGE_KEYS } from '../constants'; import type { ChatDisplayMode } from '../types'; export interface UseChatLayoutConfig { defaultMode?: ChatDisplayMode; storageKey?: string; sidebarStorageKey?: string; reserveCssVar?: string; defaultSidebarWidth?: number; minSidebarWidth?: number; maxSidebarWidth?: number; /** Mobile breakpoint, e.g. '(max-width: 640px)'. */ mobileQuery?: string; } export interface UseChatLayoutReturn { mode: ChatDisplayMode; setMode: (m: ChatDisplayMode) => void; open: () => void; close: () => void; toggle: () => void; sidebarWidth: number; setSidebarWidth: (w: number) => void; isMobile: boolean; /** Mode after mobile collapse rules — sidebar/floating become fullscreen on mobile. */ effectiveMode: ChatDisplayMode; } const DEFAULT_MOBILE_QUERY = '(max-width: 640px)'; export function useChatLayout(config: UseChatLayoutConfig = {}): UseChatLayoutReturn { const { defaultMode = 'closed', storageKey = STORAGE_KEYS.mode, sidebarStorageKey = STORAGE_KEYS.sidebarWidth, reserveCssVar = CSS_VARS.reserve, defaultSidebarWidth = DEFAULT_SIDEBAR.width, minSidebarWidth = DEFAULT_SIDEBAR.min, maxSidebarWidth = DEFAULT_SIDEBAR.max, mobileQuery = DEFAULT_MOBILE_QUERY, } = config; const [mode, setMode] = useLocalStorage(storageKey, defaultMode); const [sidebarWidthRaw, setSidebarWidthRaw] = useLocalStorage( sidebarStorageKey, defaultSidebarWidth, ); const isMobile = useMediaQuery(mobileQuery); const setSidebarWidth = useCallback( (w: number) => { const clamped = Math.max(minSidebarWidth, Math.min(maxSidebarWidth, w)); setSidebarWidthRaw(clamped); }, [setSidebarWidthRaw, minSidebarWidth, maxSidebarWidth], ); const [previousOpenMode, setPreviousOpenMode] = useState( mode === 'closed' ? 'embedded' : mode, ); useEffect(() => { if (mode !== 'closed') setPreviousOpenMode(mode); }, [mode]); const open = useCallback(() => { setMode(previousOpenMode === 'closed' ? 'embedded' : previousOpenMode); }, [setMode, previousOpenMode]); const close = useCallback(() => setMode('closed'), [setMode]); const toggle = useCallback(() => { setMode((mode === 'closed' ? previousOpenMode : 'closed') as ChatDisplayMode); }, [setMode, mode, previousOpenMode]); const effectiveMode = useMemo(() => { if (mode === 'closed') return 'closed'; if (isMobile && (mode === 'sidebar' || mode === 'floating')) return 'fullscreen'; return mode; }, [mode, isMobile]); // Reserve right padding when in sidebar mode. useEffect(() => { if (typeof document === 'undefined') return; const root = document.documentElement; if (effectiveMode === 'sidebar') { root.style.setProperty(reserveCssVar, `${sidebarWidthRaw}px`); } else { root.style.removeProperty(reserveCssVar); } return () => { root.style.removeProperty(reserveCssVar); }; }, [effectiveMode, sidebarWidthRaw, reserveCssVar]); return { mode, setMode, open, close, toggle, sidebarWidth: sidebarWidthRaw, setSidebarWidth, isMobile, effectiveMode, }; }