"use client";import { useEffect } from "react";import { usePathname } from "next/navigation";import { savePanelWidth } from "@/utilities/localStorage";export default function Panels() { const pathname = usePathname(); useEffect(() => { const desktopMediaQuery = window.matchMedia("(min-width: 480px)"); function isDisplayBlock(panel: HTMLElement) { return getComputedStyle(panel).display === "block"; } function isStateOpen(panel: HTMLElement) { const panelState = panel.dataset.state; if (panelState === "open") return true; if (panelState === "closed") return false; return isDisplayBlock(panel); } function getPanelWithName(panelName: string) { return document.querySelector<HTMLElement>('[data-panel="' + panelName + '"]'); } function updateTriggerAriaAttributes() { document.querySelectorAll<HTMLElement>("[data-trigger]").forEach((trigger) => { const panel = getPanelWithName(trigger.dataset.trigger!); if (panel) trigger.setAttribute("aria-expanded", String(isDisplayBlock(panel))); }); } function closeSiblingPanels(currentPanel: HTMLElement) { const isDesktop = desktopMediaQuery.matches; const group = currentPanel.dataset.group; let siblingPanels: NodeListOf<HTMLElement>; if (isDesktop && !group) return; if (isDesktop) { siblingPanels = document.querySelectorAll<HTMLElement>('[data-panel][data-group="' + group + '"]'); } else { siblingPanels = document.querySelectorAll<HTMLElement>("[data-panel]"); } siblingPanels.forEach((siblingPanel) => { if (siblingPanel === currentPanel) return; if (isDisplayBlock(siblingPanel)) siblingPanel.dataset.state = "closed"; }); } function togglePanels(event: MouseEvent) { const trigger = (event.target as HTMLElement).closest<HTMLElement>("[data-trigger]"); if (!trigger) return; const panel = getPanelWithName(trigger.dataset.trigger!); if (!panel) return; if (isStateOpen(panel)) { panel.dataset.state = "closed"; } else { panel.dataset.state = "open"; closeSiblingPanels(panel); } updateTriggerAriaAttributes(); } function restorePanelDefaults() { document .querySelectorAll<HTMLElement>('[data-panel][data-state="open"], [data-panel][data-state="closed"]') .forEach((panel) => { panel.dataset.state = "default"; }); updateTriggerAriaAttributes(); } let isDragging = false; let startX = 0; let startWidthPercent = 0; let activePanel: HTMLElement | null = null; let directionMultiplier = 1; function startResize(event: MouseEvent | TouchEvent) { const isDesktop = desktopMediaQuery.matches; if (!isDesktop) return; const target = event.target as HTMLElement; const handle = target.closest<HTMLElement>("[data-handle]"); if (!handle) return; activePanel = getPanelWithName(handle.dataset.handle!); if (!activePanel) return; isDragging = true; if (window.TouchEvent && event instanceof TouchEvent) { startX = event.touches[0].clientX; } else { startX = (event as MouseEvent).clientX; } directionMultiplier = activePanel.dataset.side === "right" ? -1 : 1; const panelPixels = activePanel.getBoundingClientRect().width; startWidthPercent = (panelPixels / window.innerWidth) * 100; document.body.style.userSelect = "none"; window.addEventListener("mousemove", resizePanel, { passive: false }); window.addEventListener("touchmove", resizePanel, { passive: false }); window.addEventListener("mouseup", stopResize); window.addEventListener("touchend", stopResize); } function resizePanel(event: MouseEvent | TouchEvent) { if (!isDragging || !activePanel) return; event.preventDefault(); const windowPixels = window.innerWidth; let clientX; if (window.TouchEvent && event instanceof TouchEvent) { clientX = event.touches[0].clientX; } else { clientX = (event as MouseEvent).clientX; } const pixelsMoved = clientX - startX; const percentageChange = ((pixelsMoved * directionMultiplier) / windowPixels) * 100; let newWidthPercent = startWidthPercent + percentageChange; const panelStyles = getComputedStyle(activePanel); function parseBoundary(styleValue: string, fallbackPercent: number) { if ( !styleValue || styleValue === "none" || styleValue === "auto" || parseFloat(styleValue) === 0 ) { return fallbackPercent; } if (styleValue.includes("%")) return parseFloat(styleValue); return (parseFloat(styleValue) / windowPixels) * 100; } const minPercent = parseBoundary(panelStyles.minWidth, 15); const maxPercent = parseBoundary(panelStyles.maxWidth, 30); if (newWidthPercent < minPercent) newWidthPercent = minPercent; if (newWidthPercent > maxPercent) newWidthPercent = maxPercent; const cssVarName = `--${activePanel.dataset.panel}-width`; document.body.style.setProperty(cssVarName, newWidthPercent + "%"); } function stopResize() { if (activePanel) { const cssVarName = `--${activePanel.dataset.panel}-width`; const finalWidth = document.body.style.getPropertyValue(cssVarName); if (finalWidth) { savePanelWidth(activePanel.dataset.panel!, finalWidth); } } isDragging = false; activePanel = null; document.body.style.removeProperty("user-select"); window.removeEventListener("mousemove", resizePanel); window.removeEventListener("touchmove", resizePanel); window.removeEventListener("mouseup", stopResize); window.removeEventListener("touchend", stopResize); } function onPointerDown(event: MouseEvent | TouchEvent) { if ((event.target as HTMLElement).closest("[data-handle]")) { startResize(event); } } updateTriggerAriaAttributes(); document.addEventListener("click", togglePanels); desktopMediaQuery.addEventListener("change", restorePanelDefaults); document.addEventListener("mousedown", onPointerDown); document.addEventListener("touchstart", onPointerDown, { passive: false }); return () => { document.removeEventListener("click", togglePanels); desktopMediaQuery.removeEventListener("change", restorePanelDefaults); document.removeEventListener("mousedown", onPointerDown); document.removeEventListener("touchstart", onPointerDown); window.removeEventListener("mousemove", resizePanel); window.removeEventListener("touchmove", resizePanel); window.removeEventListener("mouseup", stopResize); window.removeEventListener("touchend", stopResize); }; }, []); useEffect(() => { const isDesktop = window.matchMedia("(min-width: 480px)").matches; if (isDesktop) return; document.querySelectorAll<HTMLElement>("[data-panel]").forEach((panel) => { panel.dataset.state = "closed"; }); document.querySelectorAll<HTMLElement>("[data-trigger]").forEach((trigger) => { const panelName = trigger.dataset.trigger; if (!panelName) return; const panel = document.querySelector<HTMLElement>(`[data-panel="${panelName}"]`); if (panel) trigger.setAttribute("aria-expanded", "false"); }); }, [pathname]); return null;}