From 59c89c904c7ab76770f7c1aff6adfa06af4c6118 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Fri, 6 Mar 2026 23:58:50 +0800 Subject: [PATCH] Resizable calendar sidebar with localStorage persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar width adjustable via click-and-drag (180–400px range, default 224px). Width persists to localStorage across sessions. Co-Authored-By: Claude Opus 4.6 --- .../src/components/calendar/CalendarPage.tsx | 53 ++++++++++++++++++- .../components/calendar/CalendarSidebar.tsx | 5 +- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/calendar/CalendarPage.tsx b/frontend/src/components/calendar/CalendarPage.tsx index dab78d2..27b03c5 100644 --- a/frontend/src/components/calendar/CalendarPage.tsx +++ b/frontend/src/components/calendar/CalendarPage.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect, useMemo } from 'react'; +import { useState, useRef, useEffect, useMemo, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; @@ -47,6 +47,51 @@ export default function CalendarPage() { const [visibleSharedIds, setVisibleSharedIds] = useState>(new Set()); const calendarContainerRef = useRef(null); + // Resizable sidebar + const SIDEBAR_STORAGE_KEY = 'umbra-calendar-sidebar-width'; + const SIDEBAR_MIN = 180; + const SIDEBAR_MAX = 400; + const SIDEBAR_DEFAULT = 224; // w-56 + const [sidebarWidth, setSidebarWidth] = useState(() => { + const saved = localStorage.getItem(SIDEBAR_STORAGE_KEY); + if (saved) { + const n = parseInt(saved, 10); + if (!isNaN(n) && n >= SIDEBAR_MIN && n <= SIDEBAR_MAX) return n; + } + return SIDEBAR_DEFAULT; + }); + const isResizingRef = useRef(false); + + const handleSidebarMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + isResizingRef.current = true; + const startX = e.clientX; + const startWidth = sidebarWidth; + + const onMouseMove = (ev: MouseEvent) => { + const newWidth = Math.min(SIDEBAR_MAX, Math.max(SIDEBAR_MIN, startWidth + (ev.clientX - startX))); + setSidebarWidth(newWidth); + }; + + const onMouseUp = () => { + isResizingRef.current = false; + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + }, [sidebarWidth]); + + // Persist sidebar width on change + useEffect(() => { + localStorage.setItem(SIDEBAR_STORAGE_KEY, String(sidebarWidth)); + }, [sidebarWidth]); + // Location data for event panel const { data: locations = [] } = useQuery({ queryKey: ['locations'], @@ -423,7 +468,11 @@ export default function CalendarPage() { return (
- + +
{/* Custom toolbar */} diff --git a/frontend/src/components/calendar/CalendarSidebar.tsx b/frontend/src/components/calendar/CalendarSidebar.tsx index 689dc75..9838b1b 100644 --- a/frontend/src/components/calendar/CalendarSidebar.tsx +++ b/frontend/src/components/calendar/CalendarSidebar.tsx @@ -14,9 +14,10 @@ import SharedCalendarSection, { loadVisibility, saveVisibility } from './SharedC interface CalendarSidebarProps { onUseTemplate?: (template: EventTemplate) => void; onSharedVisibilityChange?: (visibleIds: Set) => void; + width: number; } -export default function CalendarSidebar({ onUseTemplate, onSharedVisibilityChange }: CalendarSidebarProps) { +export default function CalendarSidebar({ onUseTemplate, onSharedVisibilityChange, width }: CalendarSidebarProps) { const queryClient = useQueryClient(); const { data: calendars = [], sharedData: sharedCalendars = [] } = useCalendars(); const [showForm, setShowForm] = useState(false); @@ -93,7 +94,7 @@ export default function CalendarSidebar({ onUseTemplate, onSharedVisibilityChang }; return ( -
+
Calendars