feat: optimize mobile UX with simplified header and quick actions

Replace crowded 7-element mobile header with clean 3-element layout:
hamburger menu (left), tappable date display (center), search (right).
All touch targets are 44px+ per Apple HIG.

Move navigation, quick actions, view controls, and utilities into a
new mobile quick actions panel at the top of the settings sidebar.
Add safe area insets for notch and home indicator on iOS devices.

v1.34.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-14 23:39:17 +01:00
parent de483ed64e
commit d28a02c5b5
3 changed files with 294 additions and 189 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.33.8", "version": "1.34.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -3847,65 +3847,99 @@ h3 {
} }
/* --- Mobile overflow menu --- */ /* --- Mobile overflow menu --- */
.mobile-overflow-menu { /* --- Mobile Header (simplified 3-element layout) --- */
position: absolute; .mobile-header {
right: 0;
top: 100%;
margin-top: 4px;
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
z-index: 1000;
padding: 8px;
min-width: 220px;
max-height: 70vh;
overflow-y: auto;
}
.dark .mobile-overflow-menu {
background: #1f2937;
border-color: #374151;
}
.mobile-overflow-menu button,
.mobile-overflow-menu .mobile-menu-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; justify-content: space-between;
padding: 4px 12px;
padding-top: calc(4px + env(safe-area-inset-top, 0px));
min-height: 48px;
gap: 8px;
position: sticky;
top: 0;
z-index: 100;
}
.mobile-header-btn {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
border: none;
background: transparent;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.mobile-header-btn:active {
background: rgba(0, 0, 0, 0.06);
transform: scale(0.92);
}
.dark-mode .mobile-header-btn:active {
background: rgba(255, 255, 255, 0.08);
}
.mobile-header-date {
flex: 1;
text-align: center;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
border: none;
background: transparent;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
white-space: nowrap;
}
.mobile-header-date:active {
opacity: 0.7;
}
/* --- Mobile Quick Actions in Sidebar --- */
.mobile-quick-nav-btn {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
border: none;
background: var(--weekly-settings-toggle-bg, #f3f4f6);
color: var(--weekly-text, #333);
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.mobile-quick-nav-btn:active {
background: var(--weekly-teal, #0ea5e9);
color: #fff;
transform: scale(0.92);
}
.mobile-quick-action-btn {
display: flex;
align-items: center;
gap: 12px;
width: 100%; width: 100%;
min-height: 48px;
padding: 10px 12px; padding: 10px 12px;
border: none; border: none;
background: transparent; background: transparent;
border-radius: 8px; border-radius: 10px;
font-size: 0.85rem; font-size: 0.95rem;
color: #374151; color: var(--weekly-text, #374151);
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
-webkit-tap-highlight-color: transparent;
} }
.dark .mobile-overflow-menu button, .mobile-quick-action-btn:active {
.dark .mobile-overflow-menu .mobile-menu-item { background: var(--weekly-settings-toggle-bg, #f3f4f6);
color: #e5e7eb;
}
.mobile-overflow-menu button:active,
.mobile-overflow-menu .mobile-menu-item:active {
background: #f3f4f6;
}
.dark .mobile-overflow-menu button:active,
.dark .mobile-overflow-menu .mobile-menu-item:active {
background: #374151;
}
.mobile-menu-divider {
height: 1px;
background: #e5e7eb;
margin: 4px 8px;
}
.dark .mobile-menu-divider {
background: #374151;
} }
/* --- Floating Action Button --- */ /* --- Floating Action Button --- */
.mobile-fab { .mobile-fab {
position: fixed; position: fixed;
bottom: 24px; bottom: calc(24px + env(safe-area-inset-bottom, 0px));
right: 24px; right: 24px;
width: 56px; width: 56px;
height: 56px; height: 56px;

View File

@ -20,6 +20,7 @@ import FocusModeOverlay from "./FocusModeOverlay";
import { import {
LayoutGrid, LayoutGrid,
Calendar, Calendar,
ChevronDown,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
ChevronsLeft, ChevronsLeft,
@ -1737,10 +1738,10 @@ export default function WeeklyView() {
// Mobile detection // Mobile detection
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
const [showMobileMenu, setShowMobileMenu] = useState(false);
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false); const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
const [fabTaskTitle, setFabTaskTitle] = useState(""); const [fabTaskTitle, setFabTaskTitle] = useState("");
const mobileMenuRef = useRef<HTMLDivElement>(null);
const fabTextareaRef = useRef<HTMLTextAreaElement>(null); const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
// Moved state definitions to the top // Moved state definitions to the top
@ -5533,42 +5534,26 @@ export default function WeeklyView() {
{/* Mobile Header */} {/* Mobile Header */}
{isMobile && ( {isMobile && (
<header className="flex items-center justify-between w-full px-3 py-2 border-b border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-white" style={{ minHeight: "48px" }}> <header className="mobile-header" style={{ background: darkMode ? "#111827" : "#ffffff", borderBottom: `1px solid ${darkMode ? "#374151" : "#e5e7eb"}` }}>
{/* Left: Navigation */} {/* Left: Menu button */}
<div className="flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5"> <button className="mobile-header-btn" onClick={() => setShowSettings(true)} title="Menu" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToPrevWeek} title="Previous Week"> <Menu size={22} />
<ChevronsLeft size={16} />
</button> </button>
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToPrevDay} title="Previous Day">
<ChevronLeft size={16} />
</button>
<button className="w-7 h-7 flex items-center justify-center text-gray-600 dark:text-gray-300 rounded active:bg-white active:shadow-sm" onClick={goToToday} title="Today">
<span style={{ fontSize: "8px" }}></span>
</button>
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToNextDay} title="Next Day">
<ChevronLeft size={16} className="rotate-180" />
</button>
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToNextWeek} title="Next Week">
<ChevronsLeft size={16} className="rotate-180" />
</button>
</div>
{/* Center: Week info */} {/* Center: Date display (tap to jump to date) */}
<div className="flex items-center gap-1 text-sm" style={{ <button className="mobile-header-date" onClick={() => setShowDatePicker(true)} style={{
fontFamily: profile.cwFontFamily || "Inter", fontFamily: profile.cwFontFamily || "Inter",
fontSize: profile.cwFontSize || "0.9rem",
fontWeight: Number(profile.cwFontWeight || "700"), fontWeight: Number(profile.cwFontWeight || "700"),
color: adjustColorForDarkMode(profile.cwColor || (darkMode ? "#e5e7eb" : "#333333"), darkMode), color: adjustColorForDarkMode(profile.cwColor || (darkMode ? "#e5e7eb" : "#333333"), darkMode),
}}> }}>
{(isLoading || isSyncing || syncStatus === "syncing") ? ( {(isLoading || isSyncing || syncStatus === "syncing") && (
<div className="weekly-spinner" title="Syncing..."></div> <div className="weekly-spinner" title="Syncing..." style={{ width: 14, height: 14 }}></div>
) : syncError ? ( )}
<AlertCircle size={14} className="text-red-500" /> {syncError && <AlertCircle size={14} className="text-red-500" />}
) : null} <span>
<div className="flex flex-col items-center justify-center leading-tight" style={{ whiteSpace: "nowrap", fontSize: profile.cwFontSize || "0.8rem" }}> {profile.headerDisplay === "none" ? "" :
{profile.headerDisplay === "none" ? ( profile.headerDisplay === "date" ? (() => {
null
) : profile.headerDisplay === "date" ? (
<span>{(() => {
const today = new Date(); const today = new Date();
const isTodayInWeek = getVisibleDays().some(d => const isTodayInWeek = getVisibleDays().some(d =>
d.getDate() === today.getDate() && d.getDate() === today.getDate() &&
@ -5577,109 +5562,23 @@ export default function WeeklyView() {
); );
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays()); const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' }); return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
})()}</span> })() :
) : profile.headerDisplay === "month_year" ? ( profile.headerDisplay === "month_year" ?
<span>{getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' })}</span> getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' }) :
) : profile.headerDisplay === "custom" ? ( profile.headerDisplay === "custom" ?
<span>{formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)}</span> formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t) :
) : ( profile.headerDisplay === "month" ?
<> getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long" }) :
<span>KW{getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}</span> `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")} | ${getCWReferenceDate(getVisibleDays()).getFullYear()}`
<span style={{ }
fontFamily: profile.yearFontFamily || "Inter", </span>
fontWeight: Number(profile.yearFontWeight || "700"), <ChevronDown size={12} style={{ opacity: 0.5 }} />
color: adjustColorForDarkMode(profile.yearColor || "#333333", darkMode), </button>
}}>{getCWReferenceDate(getVisibleDays()).getFullYear()}</span>
</>
)}
</div>
</div>
{/* Right: Settings + Overflow */} {/* Right: Search */}
<div className="flex items-center gap-1" ref={mobileMenuRef}> <button className="mobile-header-btn" onClick={() => setIsSearchOpen(true)} title="Search" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
<button className="p-2 rounded-md text-gray-500 active:bg-gray-100" onClick={() => setShowSettings(true)} title="Settings"> <Search size={20} />
<Settings size={18} />
</button> </button>
<div className="relative">
<button className="p-2 rounded-md text-gray-500 active:bg-gray-100" onClick={() => setShowMobileMenu(!showMobileMenu)} title="More">
<MoreVertical size={18} />
</button>
{showMobileMenu && (
<div className="mobile-overflow-menu" onClick={() => setShowMobileMenu(false)}>
<button onClick={() => { setShowDatePicker(true); }}>
<Calendar size={16} /> Jump to date
</button>
<button onClick={() => setIsSearchOpen(true)}>
<Search size={16} /> Search
</button>
<div className="mobile-menu-divider" />
<button onClick={() => {
const now = new Date();
setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` });
}}>
<Plus size={16} /> Add Calendar Event
</button>
<button onClick={() => setIsRecurringTasksOpen(true)}>
<Repeat size={16} /> Recurring Tasks
</button>
<div className="mobile-menu-divider" />
<button onClick={() => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }}>
{showNextTask ? <Play size={16} className="text-teal-600" /> : <Target size={16} />}
{showNextTask ? "Showing Next Task" : "Showing Goal"}
</button>
<button onClick={() => setShowFocusMode(true)}>
<Zap size={16} /> Focus Mode
</button>
<button onClick={() => setDarkMode(!darkMode)}>
{darkMode ? <Sun size={16} className="text-yellow-500" /> : <Moon size={16} />}
{darkMode ? "Light Mode" : "Dark Mode"}
</button>
<div className="mobile-menu-divider" />
<div className="mobile-menu-item" style={{ flexDirection: "column", alignItems: "flex-start", gap: "6px" }}>
<span style={{ fontSize: "0.75rem", color: "#9ca3af" }}>Days to show</span>
<div className="flex items-center gap-1">
{[1, 3, 5, 7].map((num) => (
<button
key={num}
onClick={(e) => { e.stopPropagation(); setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); setShowMobileMenu(false); }}
className={`px-3 py-1 text-xs rounded ${viewDays === num ? "bg-sky-500 text-white font-bold" : "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300"}`}
>
{num}
</button>
))}
</div>
</div>
{showTimeGrid && (
<div className="mobile-menu-item" style={{ flexDirection: "column", alignItems: "flex-start", gap: "6px" }}>
<span style={{ fontSize: "0.75rem", color: "#9ca3af" }}>Slot duration</span>
<div className="flex items-center gap-1">
{[15, 30, 60].map((d) => (
<button
key={d}
onClick={(e) => { e.stopPropagation(); setCellDuration(d as CellDuration); saveSetting("cellDuration", d); setShowMobileMenu(false); }}
className={`px-3 py-1 text-xs rounded ${cellDuration === d ? "bg-sky-500 text-white font-bold" : "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300"}`}
>
{d}m
</button>
))}
</div>
</div>
)}
<div className="mobile-menu-divider" />
<button onClick={handleUndo} disabled={undoCount === 0} style={undoCount === 0 ? { opacity: 0.3 } : {}}>
<Undo2 size={16} /> Undo
</button>
<button onClick={handleRedo} disabled={redoCount === 0} style={redoCount === 0 ? { opacity: 0.3 } : {}}>
<Redo2 size={16} /> Redo
</button>
<div className="mobile-menu-divider" />
<button onClick={() => { fetchCalendarEvents(true); fetchTasks(); }}>
<RefreshCcw size={16} /> Refresh
</button>
</div>
)}
</div>
</div>
</header> </header>
)} )}
@ -8204,6 +8103,35 @@ export default function WeeklyView() {
saveKanbanStages={saveKanbanStages} saveKanbanStages={saveKanbanStages}
profile={profile} profile={profile}
setProfile={setProfile} setProfile={setProfile}
isMobile={isMobile}
mobileActions={isMobile ? {
goToPrevWeek,
goToPrevDay,
goToToday,
goToNextDay,
goToNextWeek,
onJumpToDate: () => setShowDatePicker(true),
onAddCalendarEvent: () => {
const now = new Date();
setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` });
},
onRecurringTasks: () => setIsRecurringTasksOpen(true),
onToggleNextTask: () => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); },
onFocusMode: () => setShowFocusMode(true),
onToggleDarkMode: () => setDarkMode(!darkMode),
onUndo: handleUndo,
onRedo: handleRedo,
onRefresh: () => { fetchCalendarEvents(true); fetchTasks(); },
darkMode,
showNextTask,
undoCount,
redoCount,
viewDays,
onViewDaysChange: (num: number) => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); },
showTimeGrid,
cellDuration,
onCellDurationChange: (d: CellDuration) => { setCellDuration(d); saveSetting("cellDuration", d); },
} : undefined}
/> />
) )
} }
@ -9480,6 +9408,33 @@ interface SettingsSidebarProps {
onProjectsChanged: () => void; onProjectsChanged: () => void;
kanbanStages: KanbanStage[]; kanbanStages: KanbanStage[];
saveKanbanStages: (stages: KanbanStage[]) => Promise<void>; saveKanbanStages: (stages: KanbanStage[]) => Promise<void>;
// Mobile quick actions
isMobile?: boolean;
mobileActions?: {
goToPrevWeek: () => void;
goToPrevDay: () => void;
goToToday: () => void;
goToNextDay: () => void;
goToNextWeek: () => void;
onJumpToDate: () => void;
onAddCalendarEvent: () => void;
onRecurringTasks: () => void;
onToggleNextTask: () => void;
onFocusMode: () => void;
onToggleDarkMode: () => void;
onUndo: () => void;
onRedo: () => void;
onRefresh: () => void;
darkMode: boolean;
showNextTask: boolean;
undoCount: number;
redoCount: number;
viewDays: number;
onViewDaysChange: (days: number) => void;
showTimeGrid: boolean;
cellDuration: CellDuration;
onCellDurationChange: (d: CellDuration) => void;
};
} }
// Notes Sidebar Component // Notes Sidebar Component
interface NotesSidebarProps { interface NotesSidebarProps {
@ -9702,6 +9657,8 @@ function SettingsSidebar({
saveKanbanStages, saveKanbanStages,
profile, profile,
setProfile, setProfile,
isMobile: isMobileSidebar,
mobileActions,
}: SettingsSidebarProps) { }: SettingsSidebarProps) {
const [activeTab, setActiveTab] = useState< const [activeTab, setActiveTab] = useState<
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation" "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
@ -10162,6 +10119,120 @@ function SettingsSidebar({
</button> </button>
</header> </header>
{/* Mobile Quick Actions Panel */}
{isMobileSidebar && mobileActions && (
<div className="mobile-quick-actions" style={{
padding: "12px 16px",
borderBottom: "1px solid var(--weekly-border, #eee)",
display: "flex",
flexDirection: "column",
gap: "8px",
}}>
{/* Navigation Row */}
<div style={{ display: "flex", justifyContent: "center", gap: "4px", padding: "4px 0" }}>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToPrevWeek(); handleClose(); }} title="Previous Week">
<ChevronsLeft size={18} />
</button>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToPrevDay(); handleClose(); }} title="Previous Day">
<ChevronLeft size={18} />
</button>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToToday(); handleClose(); }} title="Today"
style={{ fontWeight: 700, fontSize: "0.75rem", minWidth: 64 }}>
{t.today || "Today"}
</button>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToNextDay(); handleClose(); }} title="Next Day">
<ChevronRight size={18} />
</button>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.goToNextWeek(); handleClose(); }} title="Next Week">
<ChevronsRight size={18} />
</button>
</div>
{/* Quick Action Buttons */}
<div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onJumpToDate(); handleClose(); }}>
<Calendar size={18} /> <span>{t.jumpToDate || "Jump to date"}</span>
</button>
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onAddCalendarEvent(); handleClose(); }}>
<Plus size={18} /> <span>{t.addCalendarEvent || "Add Calendar Event"}</span>
</button>
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onRecurringTasks(); handleClose(); }}>
<Repeat size={18} /> <span>{t.recurringTasks || "Recurring Tasks"}</span>
</button>
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onToggleNextTask(); }}>
{mobileActions.showNextTask ? <Play size={18} className="text-teal-600" /> : <Target size={18} />}
<span>{mobileActions.showNextTask ? (t.showingNextTask || "Showing Next Task") : (t.showingGoal || "Showing Goal")}</span>
</button>
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onFocusMode(); handleClose(); }}>
<Zap size={18} /> <span>{t.focusMode || "Focus Mode"}</span>
</button>
<button className="mobile-quick-action-btn" onClick={() => { mobileActions.onToggleDarkMode(); }}>
{mobileActions.darkMode ? <Sun size={18} className="text-yellow-500" /> : <Moon size={18} />}
<span>{mobileActions.darkMode ? (t.lightMode || "Light Mode") : (t.darkMode || "Dark Mode")}</span>
</button>
</div>
{/* View Controls */}
<div style={{ display: "flex", flexDirection: "column", gap: "8px", padding: "4px 0" }}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{ fontSize: "0.75rem", color: "var(--weekly-text-light, #9ca3af)", minWidth: "40px" }}>{t.days || "Days"}</span>
<div style={{ display: "flex", gap: "4px", flex: 1 }}>
{[1, 3, 5, 7].map((num) => (
<button
key={num}
onClick={() => { mobileActions.onViewDaysChange(num); }}
style={{
flex: 1, padding: "6px 0", borderRadius: "6px", border: "none", cursor: "pointer",
fontSize: "0.8rem", fontWeight: mobileActions.viewDays === num ? 700 : 400,
background: mobileActions.viewDays === num ? "#0ea5e9" : "var(--weekly-settings-toggle-bg, #f3f4f6)",
color: mobileActions.viewDays === num ? "#fff" : "var(--weekly-text, #333)",
}}
>
{num}
</button>
))}
</div>
</div>
{mobileActions.showTimeGrid && (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{ fontSize: "0.75rem", color: "var(--weekly-text-light, #9ca3af)", minWidth: "40px" }}>{t.slot || "Slot"}</span>
<div style={{ display: "flex", gap: "4px", flex: 1 }}>
{[15, 30, 60].map((d) => (
<button
key={d}
onClick={() => { mobileActions.onCellDurationChange(d as CellDuration); }}
style={{
flex: 1, padding: "6px 0", borderRadius: "6px", border: "none", cursor: "pointer",
fontSize: "0.8rem", fontWeight: mobileActions.cellDuration === d ? 700 : 400,
background: mobileActions.cellDuration === d ? "#0ea5e9" : "var(--weekly-settings-toggle-bg, #f3f4f6)",
color: mobileActions.cellDuration === d ? "#fff" : "var(--weekly-text, #333)",
}}
>
{d}m
</button>
))}
</div>
</div>
)}
</div>
{/* Utility Row */}
<div style={{ display: "flex", justifyContent: "center", gap: "8px", padding: "4px 0", borderTop: "1px solid var(--weekly-border, #eee)", paddingTop: "8px" }}>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.onUndo(); }} disabled={mobileActions.undoCount === 0}
style={{ opacity: mobileActions.undoCount === 0 ? 0.3 : 1 }} title="Undo">
<Undo2 size={16} />
</button>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.onRedo(); }} disabled={mobileActions.redoCount === 0}
style={{ opacity: mobileActions.redoCount === 0 ? 0.3 : 1 }} title="Redo">
<Redo2 size={16} />
</button>
<button className="mobile-quick-nav-btn" onClick={() => { mobileActions.onRefresh(); handleClose(); }} title="Refresh">
<RefreshCcw size={16} />
</button>
</div>
</div>
)}
<div <div
className="weekly-settings-tabs" className="weekly-settings-tabs"
style={{ style={{