diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b847623..bbcf83c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, useRef } from 'react'; import type { Site } from '@/types/index.ts'; import type { Preset } from '@/services/api.ts'; import { api } from '@/services/api.ts'; @@ -117,6 +117,46 @@ export default function App() { const [showShortcuts, setShowShortcuts] = useState(false); const [kbDeleteTarget, setKbDeleteTarget] = useState<{ id: string; name: string } | null>(null); + // Resizable sidebar + const PANEL_MIN = 300; + const PANEL_MAX = 600; + const PANEL_DEFAULT = 380; + const [panelWidth, setPanelWidth] = useState(() => { + const saved = localStorage.getItem('rfcp-panel-width'); + const n = saved ? Number(saved) : PANEL_DEFAULT; + return n >= PANEL_MIN && n <= PANEL_MAX ? n : PANEL_DEFAULT; + }); + const isDragging = useRef(false); + + const handleDragStart = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + isDragging.current = true; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + + const onMove = (ev: MouseEvent) => { + if (!isDragging.current) return; + const newWidth = window.innerWidth - ev.clientX; + const clamped = Math.max(PANEL_MIN, Math.min(PANEL_MAX, newWidth)); + setPanelWidth(clamped); + }; + + const onUp = () => { + isDragging.current = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + setPanelWidth((w) => { + localStorage.setItem('rfcp-panel-width', String(w)); + return w; + }); + }; + + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }, []); + // Load sites from IndexedDB on mount useEffect(() => { loadSites(); @@ -587,9 +627,16 @@ export default function App() {