From c3dd70a4380776f6a36376186711f3172b460b8f Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Mon, 11 May 2026 08:37:09 +0200 Subject: [PATCH] feat(design): collapsible left rail toolbar with flyout menus Replace the overlay quick-settings panel with a persistent fixed-left toolbar on desktop. Collapsed (44 px) shows icon-only buttons; expanded (240 px) shows the full labelled panel. Hovering a settings icon in collapsed mode opens a flyout panel (view, columns, slot, visibility, appearance). Mobile overlay behaviour is unchanged. v1.106.0 --- package.json | 2 +- src/components/WeeklyView.tsx | 419 +++++++++++++++++----------------- 2 files changed, 213 insertions(+), 208 deletions(-) diff --git a/package.json b/package.json index 8581cc6..25e12f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.105.2", + "version": "1.106.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 10a74da..0b8347d 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1249,6 +1249,10 @@ export default function WeeklyView() { const [showDatePicker, setShowDatePicker] = useState(false); const datePickerBtnRef = useRef(null); const [showQuickSettings, setShowQuickSettings] = useState(false); + const [leftRailExpanded, setLeftRailExpanded] = useState(false); + const [flyoutSection, setFlyoutSection] = useState(null); + const [flyoutY, setFlyoutY] = useState(0); + const flyoutTimerRef = useRef(null); const [showProjectsSidebar, setShowProjectsSidebar] = useState(false); const [activeProjectFilter, setActiveProjectFilter] = useState(null); const [showIntroHints, setShowIntroHints] = useState(true); @@ -5446,6 +5450,7 @@ export default function WeeklyView() { "--weekly-past-color": activeTheme?.color8 || (darkMode ? invertColor(profile.pastDayColor || "#a6a6a7") : profile.pastDayColor || "#a6a6a7"), + ...(!isMobile ? { paddingLeft: leftRailExpanded ? "240px" : "44px", transition: "padding-left 0.2s ease" } : {}), } as React.CSSProperties; if (isLoading) { @@ -5475,6 +5480,39 @@ export default function WeeklyView() { background: "none", color: dm ? "#d1d5db" : "#333", textAlign: "left" as const, }); + const railIconBtnStyle: React.CSSProperties = { + width: "36px", height: "36px", display: "flex", alignItems: "center", justifyContent: "center", + background: "none", border: "none", cursor: "pointer", borderRadius: "8px", + color: darkMode ? "#9ca3af" : "#6b7280", flexShrink: 0, + }; + const flyoutPanelStyle: React.CSSProperties = { + position: "fixed", left: "48px", + top: `${Math.min(flyoutY, (typeof window !== "undefined" ? window.innerHeight : 800) - 260)}px`, + background: darkMode ? "#1a1a2e" : "var(--paper)", + border: "1px solid var(--line)", borderRadius: "10px", padding: "12px 14px", + zIndex: 200, boxShadow: "2px 4px 16px rgba(0,0,0,0.10)", minWidth: "200px", + display: "flex", flexDirection: "column", gap: "10px", + }; + const flyoutLabelStyle: React.CSSProperties = { + fontSize: "0.68rem", fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase" as const, + color: darkMode ? "#6b7280" : "var(--ink-3)", marginBottom: "2px", + }; + const openFlyout = (section: string, e: React.MouseEvent) => { + if (flyoutTimerRef.current) clearTimeout(flyoutTimerRef.current); + const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); + setFlyoutY(rect.top); + setFlyoutSection(section); + }; + const closeFlyoutDelayed = () => { + flyoutTimerRef.current = setTimeout(() => setFlyoutSection(null), 180); + }; + const keepFlyoutOpen = () => { + if (flyoutTimerRef.current) clearTimeout(flyoutTimerRef.current); + }; + const railSep = ( +
+ ); + // All-Day Events Section (reusable for above/below positioning) const allDaySection = (() => { if (!effectiveShowAllDay) return null; @@ -5667,36 +5705,12 @@ export default function WeeklyView() {
)} - {/* Quick Settings Sidebar (TeuxDeux-style) */} - {showQuickSettings && ( + {/* Mobile Quick Settings — full overlay triggered from mobile header */} + {isMobile && showQuickSettings && ( <> -
setShowQuickSettings(false)} - style={{ position: "fixed", inset: 0, zIndex: 999 }} - /> -
-
- {profile.language === "de" ? "Einstellungen" : "Preferences"} -
- - {/* Navigation Row */} +
setShowQuickSettings(false)} style={{ position: "fixed", inset: 0, zIndex: 999 }} /> +
+
{profile.language === "de" ? "Einstellungen" : "Preferences"}
@@ -5704,198 +5718,201 @@ export default function WeeklyView() {
- - {/* Quick Actions */}
- +
- - {/* Separator */}
- - {/* View Style */}
- - {profile.language === "de" ? "Ansicht" : "View"} - -
- {[ - { key: "simple", icon: }, - { key: "calendar", icon: }, - { key: "list", icon: }, - { key: "kanban", icon: }, - { key: "priority", icon: }, - ].map((v) => ( - - ))} -
+ {profile.language === "de" ? "Ansicht" : "View"} +
{[{ key: "simple", icon: }, { key: "calendar", icon: }, { key: "list", icon: }, { key: "kanban", icon: }, { key: "priority", icon: }].map((v) => ())}
- - {/* Columns / Days */}
- - {profile.language === "de" ? "Spalten" : "Days"} - -
- {[1, 2, 3, 5, 7].map((num) => ( - - ))} -
+ {profile.language === "de" ? "Spalten" : "Days"} +
{[1, 2, 3, 5, 7].map((num) => ())}
- - {/* Slot Duration + sub-hour slots (only with time grid) */} - {profile.showTimeGrid && ( -
- - {profile.language === "de" ? "Zeitfenster" : "Slot"} - -
- {(([15, 20, 30, 60] as CellDuration[])).map((d) => ( - - ))} -
-
- )} - {profile.showTimeGrid && ( -
- {profile.language === "de" ? ":15/:30/:45" : ":15/:30/:45"} - -
- )} - - {/* Text size */} -
- - {profile.language === "de" ? "Textgröße" : "Text size"} - -
- {(["S", "M", "L"] as const).map((size) => ( - - ))} -
-
- - {/* Separator */} + {profile.showTimeGrid && (
{profile.language === "de" ? "Zeitfenster" : "Slot"}
{(([15, 20, 30, 60] as CellDuration[])).map((d) => ())}
)} + {profile.showTimeGrid && (
:15/:30/:45
)} +
{profile.language === "de" ? "Textgröße" : "Text size"}
{(["S", "M", "L"] as const).map((size) => ())}
- - {/* Toggle switches */} -
- {profile.language === "de" ? "Irgendwann" : "Someday"} - -
-
- {profile.language === "de" ? "Ganztägig" : "All-day"} - -
-
- {profile.language === "de" ? "Checkboxen" : "Checkboxes"} - -
-
- {profile.language === "de" ? "Projekt-Icons" : "Project Icons"} - -
-
- {profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons"} - -
- {(profile.weatherEnabled || profile.weatherLat || profile.weatherLon) && ( -
- {profile.language === "de" ? "Wetter" : "Weather"} - -
- )} - - {/* Start on */} -
- {profile.language === "de" ? "Starten mit" : "Start on"} -
- - -
-
- - {/* Display mode */} -
- {profile.language === "de" ? "Anzeige" : "Display"} -
- - -
-
- - {/* Separator */} + {[{ label: profile.language === "de" ? "Irgendwann" : "Someday", value: effectiveShowSomeday, toggle: () => saveViewSetting("showSomeday", !effectiveShowSomeday, true) }, { label: profile.language === "de" ? "Ganztägig" : "All-day", value: effectiveShowAllDay, toggle: () => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true) }, { label: profile.language === "de" ? "Checkboxen" : "Checkboxes", value: effectiveShowTaskCheckboxes, toggle: () => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true) }, { label: profile.language === "de" ? "Projekt-Icons" : "Project Icons", value: effectiveShowProjectIcons, toggle: () => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true) }, { label: profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons", value: effectiveShowPriorityIcons, toggle: () => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true) }, ...((profile.weatherEnabled || profile.weatherLat || profile.weatherLon) ? [{ label: profile.language === "de" ? "Wetter" : "Weather", value: effectiveWeatherEnabled, toggle: () => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); } }] : []) ].map(({ label, value, toggle }) => (
{label}
))} +
{profile.language === "de" ? "Starten mit" : "Start on"}
+
{profile.language === "de" ? "Anzeige" : "Display"}
- - {/* Undo / Redo / Refresh */}
- - {/* Spacer */}
- - {/* Close button at bottom */} -
)} + {/* Desktop Left Rail — persistent, collapses to icon strip */} + {!isMobile && ( + <> + {/* The rail panel */} +
+ {leftRailExpanded ? ( + // ── EXPANDED MODE ── +
+
+ {profile.language === "de" ? "Einstellungen" : "Preferences"} + +
+ {/* Navigation */} +
+ + + + + +
+ {/* Quick Actions */} +
+ + + + + + + +
+
+ {/* View Style */} +
+ {profile.language === "de" ? "Ansicht" : "View"} +
{[{ key: "simple", icon: }, { key: "calendar", icon: }, { key: "list", icon: }, { key: "kanban", icon: }, { key: "priority", icon: }].map((v) => ())}
+
+ {/* Columns / Days */} +
+ {profile.language === "de" ? "Spalten" : "Days"} +
{[1, 2, 3, 5, 7].map((num) => ())}
+
+ {/* Slot Duration */} + {profile.showTimeGrid && (
{profile.language === "de" ? "Zeitfenster" : "Slot"}
{(([15, 20, 30, 60] as CellDuration[])).map((d) => ())}
)} + {profile.showTimeGrid && (
:15/:30/:45
)} + {/* Text size */} +
{profile.language === "de" ? "Textgröße" : "Text size"}
{(["S", "M", "L"] as const).map((size) => ())}
+
+ {/* Visibility toggles */} + {[{ label: profile.language === "de" ? "Irgendwann" : "Someday", value: effectiveShowSomeday, toggle: () => saveViewSetting("showSomeday", !effectiveShowSomeday, true) }, { label: profile.language === "de" ? "Ganztägig" : "All-day", value: effectiveShowAllDay, toggle: () => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true) }, { label: profile.language === "de" ? "Checkboxen" : "Checkboxes", value: effectiveShowTaskCheckboxes, toggle: () => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true) }, { label: profile.language === "de" ? "Projekt-Icons" : "Project Icons", value: effectiveShowProjectIcons, toggle: () => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true) }, { label: profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons", value: effectiveShowPriorityIcons, toggle: () => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true) }, ...((profile.weatherEnabled || profile.weatherLat || profile.weatherLon) ? [{ label: profile.language === "de" ? "Wetter" : "Weather", value: effectiveWeatherEnabled, toggle: () => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); } }] : []) ].map(({ label, value, toggle }) => (
{label}
))} + {/* Start on */} +
{profile.language === "de" ? "Starten mit" : "Start on"}
+ {/* Display */} +
{profile.language === "de" ? "Anzeige" : "Display"}
+
+ {/* Undo / Redo / Refresh */} +
+ + + + +
+
+
+ ) : ( + // ── COLLAPSED MODE — icon strip ── +
+ {/* Expand toggle */} + + {railSep} + {/* Navigation */} + + + + + + {railSep} + {/* View flyout */} + + {/* Columns flyout */} + + {/* Slot flyout */} + {profile.showTimeGrid && ()} + {railSep} + {/* Quick Actions */} + + + + + + + + {railSep} + {/* Visibility flyout */} + + {/* Appearance flyout */} + + {railSep} + {/* History & Utilities */} + + + + + +
+ {railSep} + {/* Settings / User at bottom */} + +
+ )} +
+ + {/* Flyout panels — rendered outside the rail so they can overflow */} + {!leftRailExpanded && flyoutSection && ( +
+ {flyoutSection === "view" && ( + <> +
{profile.language === "de" ? "Ansicht" : "View"}
+
{[{ key: "simple", icon: , label: profile.language === "de" ? "Einfach" : "Simple" }, { key: "calendar", icon: , label: profile.language === "de" ? "Kalender" : "Calendar" }, { key: "list", icon: , label: profile.language === "de" ? "Liste" : "List" }, { key: "kanban", icon: , label: "Kanban" }, { key: "priority", icon: , label: profile.language === "de" ? "Priorität" : "Priority" }].map((v) => ())}
+ + )} + {flyoutSection === "columns" && ( + <> +
{profile.language === "de" ? "Spalten" : "Days"}
+
{[1, 2, 3, 5, 7].map((num) => ())}
+ + )} + {flyoutSection === "slot" && ( + <> +
{profile.language === "de" ? "Zeitfenster" : "Slot Duration"}
+
{(([15, 20, 30, 60] as CellDuration[])).map((d) => ())}
+
:15/:30/:45
+ + )} + {flyoutSection === "visibility" && ( + <> +
{profile.language === "de" ? "Sichtbarkeit" : "Visibility"}
+ {[{ label: profile.language === "de" ? "Irgendwann" : "Someday", value: effectiveShowSomeday, toggle: () => saveViewSetting("showSomeday", !effectiveShowSomeday, true) }, { label: profile.language === "de" ? "Ganztägig" : "All-day", value: effectiveShowAllDay, toggle: () => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true) }, { label: profile.language === "de" ? "Checkboxen" : "Checkboxes", value: effectiveShowTaskCheckboxes, toggle: () => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true) }, { label: profile.language === "de" ? "Projekt-Icons" : "Project Icons", value: effectiveShowProjectIcons, toggle: () => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true) }, { label: profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons", value: effectiveShowPriorityIcons, toggle: () => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true) }, ...((profile.weatherEnabled || profile.weatherLat || profile.weatherLon) ? [{ label: profile.language === "de" ? "Wetter" : "Weather", value: effectiveWeatherEnabled, toggle: () => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); } }] : []) ].map(({ label, value, toggle }) => (
{label}
))} + + )} + {flyoutSection === "display" && ( + <> +
{profile.language === "de" ? "Darstellung" : "Appearance"}
+
{profile.language === "de" ? "Textgröße" : "Text size"}
{(["S", "M", "L"] as const).map((size) => ())}
+
{profile.language === "de" ? "Starten mit" : "Start on"}
+
{profile.language === "de" ? "Anzeige" : "Display"}
+ + )} +
+ )} + + )} + {/* Projects Sidebar */} {showProjectsSidebar && ( )} - {/* Quick Settings toggle button is now in the desktop header toolbar */} - {/* View Transitions Style Block */}