"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { getLocalStorage, setLocalStorage } from "@/lib/local-storage"; const STORAGE_KEY = "kandev.quickChat.dialogWidth"; const DEFAULT_VIEWPORT_RATIO = 0.8; const MIN_WIDTH = 480; const VIEWPORT_MARGIN = 32; export const QUICK_CHAT_WIDTH_LIMITS = { storageKey: STORAGE_KEY, defaultViewportRatio: DEFAULT_VIEWPORT_RATIO, minWidth: MIN_WIDTH, viewportMargin: VIEWPORT_MARGIN, } as const; function viewportWidth(): number { return typeof window === "undefined" ? 1200 : window.innerWidth; } export function clampQuickChatWidth(width: number, availableWidth = viewportWidth()): number { const maximum = Math.max(0, availableWidth - VIEWPORT_MARGIN); const minimum = Math.min(MIN_WIDTH, maximum); return Math.round(Math.min(Math.max(width, minimum), maximum)); } function readInitialWidth(): number { const availableWidth = viewportWidth(); const stored = getLocalStorage(STORAGE_KEY, null); const preferred = typeof stored === "number" && Number.isFinite(stored) ? stored : availableWidth * DEFAULT_VIEWPORT_RATIO; return clampQuickChatWidth(preferred, availableWidth); } function restoreBodyStyles() { document.body.style.cursor = ""; document.body.style.userSelect = ""; } type ResizeEdge = "left" | "right"; export function useQuickChatWidth() { const [width, setWidth] = useState(readInitialWidth); const widthRef = useRef(width); const dragRef = useRef<{ edge: ResizeEdge; startX: number; lastX: number; startWidth: number; dialog: HTMLElement; } | null>(null); const updateWidth = useCallback((nextWidth: number) => { widthRef.current = nextWidth; setWidth(nextWidth); }, []); const startResize = useCallback((edge: ResizeEdge, event: React.MouseEvent) => { event.preventDefault(); const dialog = event.currentTarget.parentElement; if (!dialog) return; dragRef.current = { edge, startX: event.clientX, lastX: event.clientX, startWidth: widthRef.current, dialog, }; document.body.style.cursor = "ew-resize"; document.body.style.userSelect = "none"; }, []); const handleLeftMouseDown = useCallback( (event: React.MouseEvent) => startResize("left", event), [startResize], ); const handleRightMouseDown = useCallback( (event: React.MouseEvent) => startResize("right", event), [startResize], ); useEffect(() => { const handleMouseMove = (event: MouseEvent) => { const drag = dragRef.current; if (!drag) return; drag.lastX = event.clientX; const pointerDelta = event.clientX - drag.startX; const widthDelta = (drag.edge === "right" ? pointerDelta : -pointerDelta) * 2; const nextWidth = clampQuickChatWidth(drag.startWidth + widthDelta); widthRef.current = nextWidth; drag.dialog.style.setProperty("--quick-chat-width", `${nextWidth}px`); }; const handleMouseUp = () => { if (!dragRef.current) return; dragRef.current = null; restoreBodyStyles(); setWidth(widthRef.current); setLocalStorage(STORAGE_KEY, widthRef.current); }; const handleWindowResize = () => { const nextWidth = clampQuickChatWidth(widthRef.current); const drag = dragRef.current; if (drag) { widthRef.current = nextWidth; drag.startWidth = nextWidth; drag.startX = drag.lastX; drag.dialog.style.setProperty("--quick-chat-width", `${nextWidth}px`); return; } updateWidth(nextWidth); }; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); window.addEventListener("resize", handleWindowResize); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); window.removeEventListener("resize", handleWindowResize); if (dragRef.current) { dragRef.current = null; restoreBodyStyles(); } }; }, [updateWidth]); return { width, leftResizeHandleProps: { onMouseDown: handleLeftMouseDown }, rightResizeHandleProps: { onMouseDown: handleRightMouseDown }, }; }