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:
parent
de483ed64e
commit
d28a02c5b5
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -3847,65 +3847,99 @@ h3 {
|
||||
}
|
||||
|
||||
/* --- Mobile overflow menu --- */
|
||||
.mobile-overflow-menu {
|
||||
position: absolute;
|
||||
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 {
|
||||
/* --- Mobile Header (simplified 3-element layout) --- */
|
||||
.mobile-header {
|
||||
display: flex;
|
||||
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%;
|
||||
min-height: 48px;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: #374151;
|
||||
border-radius: 10px;
|
||||
font-size: 0.95rem;
|
||||
color: var(--weekly-text, #374151);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.dark .mobile-overflow-menu button,
|
||||
.dark .mobile-overflow-menu .mobile-menu-item {
|
||||
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;
|
||||
.mobile-quick-action-btn:active {
|
||||
background: var(--weekly-settings-toggle-bg, #f3f4f6);
|
||||
}
|
||||
|
||||
/* --- Floating Action Button --- */
|
||||
.mobile-fab {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
bottom: calc(24px + env(safe-area-inset-bottom, 0px));
|
||||
right: 24px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
|
||||
@ -20,6 +20,7 @@ import FocusModeOverlay from "./FocusModeOverlay";
|
||||
import {
|
||||
LayoutGrid,
|
||||
Calendar,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
@ -1737,10 +1738,10 @@ export default function WeeklyView() {
|
||||
|
||||
// Mobile detection
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
||||
|
||||
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
|
||||
const [fabTaskTitle, setFabTaskTitle] = useState("");
|
||||
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// Moved state definitions to the top
|
||||
@ -5533,153 +5534,51 @@ export default function WeeklyView() {
|
||||
|
||||
{/* Mobile Header */}
|
||||
{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" }}>
|
||||
{/* Left: Navigation */}
|
||||
<div className="flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5">
|
||||
<button className="p-1.5 rounded text-gray-500 active:bg-white active:shadow-sm" onClick={goToPrevWeek} title="Previous Week">
|
||||
<ChevronsLeft size={16} />
|
||||
</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>
|
||||
<header className="mobile-header" style={{ background: darkMode ? "#111827" : "#ffffff", borderBottom: `1px solid ${darkMode ? "#374151" : "#e5e7eb"}` }}>
|
||||
{/* Left: Menu button */}
|
||||
<button className="mobile-header-btn" onClick={() => setShowSettings(true)} title="Menu" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||
<Menu size={22} />
|
||||
</button>
|
||||
|
||||
{/* Center: Week info */}
|
||||
<div className="flex items-center gap-1 text-sm" style={{
|
||||
{/* Center: Date display (tap to jump to date) */}
|
||||
<button className="mobile-header-date" onClick={() => setShowDatePicker(true)} style={{
|
||||
fontFamily: profile.cwFontFamily || "Inter",
|
||||
fontSize: profile.cwFontSize || "0.9rem",
|
||||
fontWeight: Number(profile.cwFontWeight || "700"),
|
||||
color: adjustColorForDarkMode(profile.cwColor || (darkMode ? "#e5e7eb" : "#333333"), darkMode),
|
||||
}}>
|
||||
{(isLoading || isSyncing || syncStatus === "syncing") ? (
|
||||
<div className="weekly-spinner" title="Syncing..."></div>
|
||||
) : syncError ? (
|
||||
<AlertCircle size={14} className="text-red-500" />
|
||||
) : null}
|
||||
<div className="flex flex-col items-center justify-center leading-tight" style={{ whiteSpace: "nowrap", fontSize: profile.cwFontSize || "0.8rem" }}>
|
||||
{profile.headerDisplay === "none" ? (
|
||||
null
|
||||
) : profile.headerDisplay === "date" ? (
|
||||
<span>{(() => {
|
||||
const today = new Date();
|
||||
const isTodayInWeek = getVisibleDays().some(d =>
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
d.getFullYear() === today.getFullYear()
|
||||
);
|
||||
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
|
||||
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
})()}</span>
|
||||
) : profile.headerDisplay === "month_year" ? (
|
||||
<span>{getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' })}</span>
|
||||
) : profile.headerDisplay === "custom" ? (
|
||||
<span>{formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)}</span>
|
||||
) : (
|
||||
<>
|
||||
<span>KW{getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}</span>
|
||||
<span style={{
|
||||
fontFamily: profile.yearFontFamily || "Inter",
|
||||
fontWeight: Number(profile.yearFontWeight || "700"),
|
||||
color: adjustColorForDarkMode(profile.yearColor || "#333333", darkMode),
|
||||
}}>{getCWReferenceDate(getVisibleDays()).getFullYear()}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{(isLoading || isSyncing || syncStatus === "syncing") && (
|
||||
<div className="weekly-spinner" title="Syncing..." style={{ width: 14, height: 14 }}></div>
|
||||
)}
|
||||
{syncError && <AlertCircle size={14} className="text-red-500" />}
|
||||
<span>
|
||||
{profile.headerDisplay === "none" ? "" :
|
||||
profile.headerDisplay === "date" ? (() => {
|
||||
const today = new Date();
|
||||
const isTodayInWeek = getVisibleDays().some(d =>
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
d.getFullYear() === today.getFullYear()
|
||||
);
|
||||
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
|
||||
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
})() :
|
||||
profile.headerDisplay === "month_year" ?
|
||||
getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' }) :
|
||||
profile.headerDisplay === "custom" ?
|
||||
formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t) :
|
||||
profile.headerDisplay === "month" ?
|
||||
getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long" }) :
|
||||
`KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")} | ${getCWReferenceDate(getVisibleDays()).getFullYear()}`
|
||||
}
|
||||
</span>
|
||||
<ChevronDown size={12} style={{ opacity: 0.5 }} />
|
||||
</button>
|
||||
|
||||
{/* Right: Settings + Overflow */}
|
||||
<div className="flex items-center gap-1" ref={mobileMenuRef}>
|
||||
<button className="p-2 rounded-md text-gray-500 active:bg-gray-100" onClick={() => setShowSettings(true)} title="Settings">
|
||||
<Settings size={18} />
|
||||
</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>
|
||||
{/* Right: Search */}
|
||||
<button className="mobile-header-btn" onClick={() => setIsSearchOpen(true)} title="Search" style={{ color: darkMode ? "#e5e7eb" : "#6b7280" }}>
|
||||
<Search size={20} />
|
||||
</button>
|
||||
</header>
|
||||
)}
|
||||
|
||||
@ -8204,6 +8103,35 @@ export default function WeeklyView() {
|
||||
saveKanbanStages={saveKanbanStages}
|
||||
profile={profile}
|
||||
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;
|
||||
kanbanStages: KanbanStage[];
|
||||
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
|
||||
interface NotesSidebarProps {
|
||||
@ -9702,6 +9657,8 @@ function SettingsSidebar({
|
||||
saveKanbanStages,
|
||||
profile,
|
||||
setProfile,
|
||||
isMobile: isMobileSidebar,
|
||||
mobileActions,
|
||||
}: SettingsSidebarProps) {
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
|
||||
@ -10162,6 +10119,120 @@ function SettingsSidebar({
|
||||
</button>
|
||||
</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
|
||||
className="weekly-settings-tabs"
|
||||
style={{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user