Add a persistent chat overlay accessible from any authenticated view. The overlay wraps the existing Chat component and adds state management, keyboard shortcuts, and responsive design. Features: - Three states: Closed (floating button), Open (full panel), Minimized (header) - Keyboard shortcuts: - Cmd/Ctrl + K: Open chat (when closed) - Escape: Minimize chat (when open) - Cmd/Ctrl + Shift + J: Toggle chat panel - State persistence via localStorage - Responsive design (full-width mobile, sidebar desktop) - PDA-friendly design with calm colors - 32 comprehensive tests (14 hook tests + 18 component tests) Files added: - apps/web/src/hooks/useChatOverlay.ts - apps/web/src/hooks/useChatOverlay.test.ts - apps/web/src/components/chat/ChatOverlay.tsx - apps/web/src/components/chat/ChatOverlay.test.tsx Files modified: - apps/web/src/components/chat/index.ts (added export) - apps/web/src/app/(authenticated)/layout.tsx (integrated overlay) All tests passing (490 tests, 50 test files) All lint checks passing Build succeeds Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
110 lines
2.4 KiB
TypeScript
110 lines
2.4 KiB
TypeScript
/**
|
|
* @file useChatOverlay.ts
|
|
* @description Hook for managing the global chat overlay state
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
|
|
interface ChatOverlayState {
|
|
isOpen: boolean;
|
|
isMinimized: boolean;
|
|
}
|
|
|
|
interface UseChatOverlayResult extends ChatOverlayState {
|
|
open: () => void;
|
|
close: () => void;
|
|
minimize: () => void;
|
|
expand: () => void;
|
|
toggle: () => void;
|
|
toggleMinimize: () => void;
|
|
}
|
|
|
|
const STORAGE_KEY = "chatOverlayState";
|
|
|
|
const DEFAULT_STATE: ChatOverlayState = {
|
|
isOpen: false,
|
|
isMinimized: false,
|
|
};
|
|
|
|
/**
|
|
* Load state from localStorage
|
|
*/
|
|
function loadState(): ChatOverlayState {
|
|
if (typeof window === "undefined") {
|
|
return DEFAULT_STATE;
|
|
}
|
|
|
|
try {
|
|
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
if (stored) {
|
|
return JSON.parse(stored) as ChatOverlayState;
|
|
}
|
|
} catch (error) {
|
|
console.warn("Failed to load chat overlay state from localStorage:", error);
|
|
}
|
|
|
|
return DEFAULT_STATE;
|
|
}
|
|
|
|
/**
|
|
* Save state to localStorage
|
|
*/
|
|
function saveState(state: ChatOverlayState): void {
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
} catch (error) {
|
|
console.warn("Failed to save chat overlay state to localStorage:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom hook for managing chat overlay state
|
|
* Persists state to localStorage for consistency across page refreshes
|
|
*/
|
|
export function useChatOverlay(): UseChatOverlayResult {
|
|
const [state, setState] = useState<ChatOverlayState>(loadState);
|
|
|
|
// Persist state changes to localStorage
|
|
useEffect(() => {
|
|
saveState(state);
|
|
}, [state]);
|
|
|
|
const open = useCallback(() => {
|
|
setState({ isOpen: true, isMinimized: false });
|
|
}, []);
|
|
|
|
const close = useCallback(() => {
|
|
setState((prev) => ({ ...prev, isOpen: false }));
|
|
}, []);
|
|
|
|
const minimize = useCallback(() => {
|
|
setState((prev) => ({ ...prev, isMinimized: true }));
|
|
}, []);
|
|
|
|
const expand = useCallback(() => {
|
|
setState((prev) => ({ ...prev, isMinimized: false }));
|
|
}, []);
|
|
|
|
const toggle = useCallback(() => {
|
|
setState((prev) => ({ ...prev, isOpen: !prev.isOpen }));
|
|
}, []);
|
|
|
|
const toggleMinimize = useCallback(() => {
|
|
setState((prev) => ({ ...prev, isMinimized: !prev.isMinimized }));
|
|
}, []);
|
|
|
|
return {
|
|
...state,
|
|
open,
|
|
close,
|
|
minimize,
|
|
expand,
|
|
toggle,
|
|
toggleMinimize,
|
|
};
|
|
}
|