feat: add responsive header with overflow menu for tablet
Desktop shows all action buttons inline. On tablet (≤1024px), secondary actions collapse into a dropdown overflow menu with proper touch targets, dark mode support, and fade-in animation. v1.42.0
This commit is contained in:
parent
18da568b9f
commit
0248cfbb6c
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.41.0",
|
||||
"version": "1.42.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": {
|
||||
|
||||
@ -3693,6 +3693,90 @@ h3 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Responsive header sections */
|
||||
.header-desktop-only {
|
||||
display: flex;
|
||||
}
|
||||
.header-tablet-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.header-desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
.header-tablet-only {
|
||||
display: block !important;
|
||||
}
|
||||
/* Make controls visible on tablet (no hover on touch devices) */
|
||||
.weekly-header-controls {
|
||||
opacity: 1 !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Header overflow menu (tablet) */
|
||||
.header-overflow-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
min-width: 200px;
|
||||
padding: 6px;
|
||||
margin-top: 4px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
animation: menuFadeIn 0.15s ease;
|
||||
}
|
||||
|
||||
.dark-mode .header-overflow-menu {
|
||||
background: #1f2937;
|
||||
border-color: #374151;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.header-overflow-menu button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.header-overflow-menu button:hover {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.header-overflow-menu button:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dark-mode .header-overflow-menu button {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.dark-mode .header-overflow-menu button:hover {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
@keyframes menuFadeIn {
|
||||
from { opacity: 0; transform: translateY(-4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* List View (formerly Grid/App View - no vertical lines) */
|
||||
.list-view.time-grid-off .weekly-day-column {
|
||||
border-right: none !important;
|
||||
|
||||
@ -1795,6 +1795,7 @@ export default function WeeklyView() {
|
||||
|
||||
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
|
||||
const [showMobileFabMenu, setShowMobileFabMenu] = useState(false);
|
||||
const [showHeaderMore, setShowHeaderMore] = useState(false);
|
||||
const [fabTaskTitle, setFabTaskTitle] = useState("");
|
||||
|
||||
const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
@ -6021,163 +6022,72 @@ export default function WeeklyView() {
|
||||
</div>
|
||||
|
||||
{/* RIGHT SECTION: Navigation & Tools */}
|
||||
<div className="weekly-header-controls flex-shrink-0 flex items-center gap-2 sm:gap-3 transition-opacity duration-300 opacity-0 group-hover:opacity-100" style={{ zIndex: 10 }}>
|
||||
{/* Undo/Redo */}
|
||||
<button
|
||||
onClick={handleUndo}
|
||||
disabled={undoCount === 0}
|
||||
className="weekly-btn-icon disabled:opacity-30 disabled:cursor-default"
|
||||
title="Undo (Ctrl+Z)"
|
||||
>
|
||||
<Undo2 size={18} className="text-gray-600 hover:text-black transition-colors" />
|
||||
<div className="weekly-header-controls flex-shrink-0 flex items-center gap-1.5 sm:gap-2 transition-opacity duration-300 opacity-0 group-hover:opacity-100" style={{ zIndex: 10 }}>
|
||||
{/* Secondary actions — hidden on tablet, visible on desktop */}
|
||||
<div className="header-desktop-only flex items-center gap-1.5">
|
||||
<button onClick={handleUndo} disabled={undoCount === 0} className="weekly-btn-icon disabled:opacity-30 disabled:cursor-default" title="Undo (Ctrl+Z)">
|
||||
<Undo2 size={17} className="text-gray-600 hover:text-black transition-colors" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRedo}
|
||||
disabled={redoCount === 0}
|
||||
className="weekly-btn-icon disabled:opacity-30 disabled:cursor-default"
|
||||
title="Redo (Ctrl+Y)"
|
||||
>
|
||||
<Redo2 size={18} className="text-gray-600 hover:text-black transition-colors" />
|
||||
<button onClick={handleRedo} disabled={redoCount === 0} className="weekly-btn-icon disabled:opacity-30 disabled:cursor-default" title="Redo (Ctrl+Y)">
|
||||
<Redo2 size={17} className="text-gray-600 hover:text-black transition-colors" />
|
||||
</button>
|
||||
{/* Add Event Button */}
|
||||
<button
|
||||
onClick={() => {
|
||||
const now = new Date();
|
||||
setCalendarEventModal({
|
||||
isOpen: true,
|
||||
event: undefined,
|
||||
initialDate: now,
|
||||
initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00`,
|
||||
});
|
||||
}}
|
||||
className="weekly-btn-icon"
|
||||
title="Add Calendar Event"
|
||||
>
|
||||
<Plus
|
||||
size={18}
|
||||
className="text-gray-600 hover:text-black transition-colors"
|
||||
/>
|
||||
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); }} className="weekly-btn-icon" title="Add Calendar Event">
|
||||
<Plus size={17} className="text-gray-600 hover:text-black transition-colors" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newVal = !showNextTask;
|
||||
setShowNextTask(newVal);
|
||||
saveSetting("showNextTask", newVal);
|
||||
}}
|
||||
className={`weekly-btn-icon ${showNextTask ? "active" : ""}`}
|
||||
title={showNextTask ? "Showing Next Task" : "Showing Goal"}
|
||||
>
|
||||
{showNextTask ? (
|
||||
<Play size={18} className="text-teal-600" />
|
||||
) : (
|
||||
<Target size={18} className="text-gray-400" />
|
||||
)}
|
||||
<button onClick={() => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }} className={`weekly-btn-icon ${showNextTask ? "active" : ""}`} title={showNextTask ? "Showing Next Task" : "Showing Goal"}>
|
||||
{showNextTask ? <Play size={17} className="text-teal-600" /> : <Target size={17} className="text-gray-400" />}
|
||||
</button>
|
||||
|
||||
{/* Focus Mode Toggle */}
|
||||
<button
|
||||
onClick={() => setShowFocusMode(true)}
|
||||
className="weekly-btn-icon"
|
||||
title="Enter Focus Mode"
|
||||
>
|
||||
<Zap
|
||||
size={18}
|
||||
className="text-gray-600 hover:text-yellow-500 transition-colors"
|
||||
/>
|
||||
<button onClick={() => setShowFocusMode(true)} className="weekly-btn-icon" title="Enter Focus Mode">
|
||||
<Zap size={17} className="text-gray-600 hover:text-yellow-500 transition-colors" />
|
||||
</button>
|
||||
|
||||
{/* Day/Night Mode Switch */}
|
||||
<button
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
className="p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
||||
title={darkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
|
||||
>
|
||||
{darkMode ? (
|
||||
<Sun size={18} className="text-yellow-500" />
|
||||
) : (
|
||||
<Moon size={18} className="text-gray-500" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Navigation Controls */}
|
||||
<div className="flex items-center bg-gray-100 rounded-lg p-0.5">
|
||||
<button
|
||||
className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all"
|
||||
onClick={goToPrevWeek}
|
||||
title="Previous Week"
|
||||
>
|
||||
<ChevronsLeft size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all"
|
||||
onClick={goToPrevDay}
|
||||
title="Previous Day"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
<button
|
||||
className="px-3 py-1 text-xs font-bold text-gray-600 hover:text-black hover:bg-white hover:shadow-sm rounded transition-all"
|
||||
onClick={goToToday}
|
||||
title="Go to Today"
|
||||
>
|
||||
Today
|
||||
</button>
|
||||
<button
|
||||
className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all"
|
||||
onClick={goToNextDay}
|
||||
title="Next Day"
|
||||
>
|
||||
<ChevronLeft size={16} className="rotate-180" />
|
||||
</button>
|
||||
<button
|
||||
className="p-1 hover:bg-white hover:shadow-sm rounded text-gray-500 hover:text-black transition-all"
|
||||
onClick={goToNextWeek}
|
||||
title="Next Week"
|
||||
>
|
||||
<ChevronsLeft size={16} className="rotate-180" />
|
||||
<button onClick={() => setDarkMode(!darkMode)} className="weekly-btn-icon" title={darkMode ? "Light Mode" : "Dark Mode"}>
|
||||
{darkMode ? <Sun size={17} className="text-yellow-500" /> : <Moon size={17} className="text-gray-500" />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation Controls — always visible */}
|
||||
<div className="flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5">
|
||||
<button className="p-1 hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm rounded text-gray-500 hover:text-black dark:hover:text-white transition-all" onClick={goToPrevWeek} title="Previous Week"><ChevronsLeft size={15} /></button>
|
||||
<button className="p-1 hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm rounded text-gray-500 hover:text-black dark:hover:text-white transition-all" onClick={goToPrevDay} title="Previous Day"><ChevronLeft size={15} /></button>
|
||||
<button className="px-2 py-1 text-xs font-bold text-gray-600 dark:text-gray-400 hover:text-black dark:hover:text-white hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm rounded transition-all" onClick={goToToday} title="Go to Today">Today</button>
|
||||
<button className="p-1 hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm rounded text-gray-500 hover:text-black dark:hover:text-white transition-all" onClick={goToNextDay} title="Next Day"><ChevronLeft size={15} className="rotate-180" /></button>
|
||||
<button className="p-1 hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm rounded text-gray-500 hover:text-black dark:hover:text-white transition-all" onClick={goToNextWeek} title="Next Week"><ChevronsLeft size={15} className="rotate-180" /></button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<button
|
||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
||||
onClick={() => setIsSearchOpen(true)}
|
||||
title="Search"
|
||||
>
|
||||
<Search size={18} />
|
||||
</button>
|
||||
{/* Always visible: Search, Settings, User */}
|
||||
<button className="weekly-btn-icon" onClick={() => setIsSearchOpen(true)} title="Search"><Search size={17} /></button>
|
||||
<button className="weekly-btn-icon" onClick={() => setShowSettings(true)} title="Settings"><Settings size={17} /></button>
|
||||
|
||||
{/* Recurring Tasks */}
|
||||
<button
|
||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
||||
onClick={() => setIsRecurringTasksOpen(true)}
|
||||
title="Recurring Tasks"
|
||||
>
|
||||
<Repeat size={18} />
|
||||
{/* Overflow menu — visible on tablet, hidden on desktop */}
|
||||
<div className="header-tablet-only" style={{ position: "relative" }}>
|
||||
<button className="weekly-btn-icon" onClick={() => setShowHeaderMore(!showHeaderMore)} title="More">
|
||||
<MoreVertical size={17} />
|
||||
</button>
|
||||
{showHeaderMore && (
|
||||
<>
|
||||
<div style={{ position: "fixed", inset: 0, zIndex: 99 }} onClick={() => setShowHeaderMore(false)} />
|
||||
<div className="header-overflow-menu">
|
||||
<button onClick={() => { handleUndo(); setShowHeaderMore(false); }} disabled={undoCount === 0}><Undo2 size={16} /> <span>Undo</span></button>
|
||||
<button onClick={() => { handleRedo(); setShowHeaderMore(false); }} disabled={redoCount === 0}><Redo2 size={16} /> <span>Redo</span></button>
|
||||
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); setShowHeaderMore(false); }}><Plus size={16} /> <span>{language === "de" ? "Termin erstellen" : "Add Event"}</span></button>
|
||||
<button onClick={() => { setIsRecurringTasksOpen(true); setShowHeaderMore(false); }}><Repeat size={16} /> <span>{language === "de" ? "Wiederkehrend" : "Recurring"}</span></button>
|
||||
<button onClick={() => { const nv = !showNextTask; setShowNextTask(nv); saveSetting("showNextTask", nv); setShowHeaderMore(false); }}>{showNextTask ? <Play size={16} /> : <Target size={16} />} <span>{showNextTask ? "Next Task" : "Goal"}</span></button>
|
||||
<button onClick={() => { setShowFocusMode(true); setShowHeaderMore(false); }}><Zap size={16} /> <span>{language === "de" ? "Fokus" : "Focus"}</span></button>
|
||||
<button onClick={() => { setDarkMode(!darkMode); setShowHeaderMore(false); }}>{darkMode ? <Sun size={16} /> : <Moon size={16} />} <span>{darkMode ? "Light" : "Dark"}</span></button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
||||
onClick={() => setShowSettings(true)}
|
||||
title="Settings"
|
||||
>
|
||||
<Settings size={18} />
|
||||
</button>
|
||||
{/* Recurring Tasks — desktop only */}
|
||||
<button className="weekly-btn-icon header-desktop-only" onClick={() => setIsRecurringTasksOpen(true)} title="Recurring Tasks"><Repeat size={17} /></button>
|
||||
|
||||
{/* User Menu */}
|
||||
<UserMenu
|
||||
userEmail={session?.user?.email}
|
||||
onOpenSettings={() => setShowSettings(true)}
|
||||
language={profile.language}
|
||||
trigger={
|
||||
<button
|
||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
||||
title="User Menu"
|
||||
>
|
||||
<User size={18} />
|
||||
</button>
|
||||
}
|
||||
trigger={<button className="weekly-btn-icon" title="User Menu"><User size={17} /></button>}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user