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",
|
"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",
|
"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": {
|
||||||
|
|||||||
@ -3693,6 +3693,90 @@ h3 {
|
|||||||
opacity: 0;
|
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 (formerly Grid/App View - no vertical lines) */
|
||||||
.list-view.time-grid-off .weekly-day-column {
|
.list-view.time-grid-off .weekly-day-column {
|
||||||
border-right: none !important;
|
border-right: none !important;
|
||||||
|
|||||||
@ -1795,6 +1795,7 @@ export default function WeeklyView() {
|
|||||||
|
|
||||||
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
|
const [showMobileFabSheet, setShowMobileFabSheet] = useState(false);
|
||||||
const [showMobileFabMenu, setShowMobileFabMenu] = useState(false);
|
const [showMobileFabMenu, setShowMobileFabMenu] = useState(false);
|
||||||
|
const [showHeaderMore, setShowHeaderMore] = useState(false);
|
||||||
const [fabTaskTitle, setFabTaskTitle] = useState("");
|
const [fabTaskTitle, setFabTaskTitle] = useState("");
|
||||||
|
|
||||||
const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
|
const fabTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@ -6021,163 +6022,72 @@ export default function WeeklyView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT SECTION: Navigation & Tools */}
|
{/* 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 }}>
|
<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 }}>
|
||||||
{/* Undo/Redo */}
|
{/* Secondary actions — hidden on tablet, visible on desktop */}
|
||||||
<button
|
<div className="header-desktop-only flex items-center gap-1.5">
|
||||||
onClick={handleUndo}
|
<button onClick={handleUndo} disabled={undoCount === 0} className="weekly-btn-icon disabled:opacity-30 disabled:cursor-default" title="Undo (Ctrl+Z)">
|
||||||
disabled={undoCount === 0}
|
<Undo2 size={17} className="text-gray-600 hover:text-black transition-colors" />
|
||||||
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" />
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={handleRedo} disabled={redoCount === 0} className="weekly-btn-icon disabled:opacity-30 disabled:cursor-default" title="Redo (Ctrl+Y)">
|
||||||
onClick={handleRedo}
|
<Redo2 size={17} className="text-gray-600 hover:text-black transition-colors" />
|
||||||
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>
|
</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">
|
||||||
<button
|
<Plus size={17} className="text-gray-600 hover:text-black transition-colors" />
|
||||||
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>
|
</button>
|
||||||
<button
|
<button onClick={() => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }} className={`weekly-btn-icon ${showNextTask ? "active" : ""}`} title={showNextTask ? "Showing Next Task" : "Showing Goal"}>
|
||||||
onClick={() => {
|
{showNextTask ? <Play size={17} className="text-teal-600" /> : <Target size={17} className="text-gray-400" />}
|
||||||
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>
|
</button>
|
||||||
|
<button onClick={() => setShowFocusMode(true)} className="weekly-btn-icon" title="Enter Focus Mode">
|
||||||
{/* Focus Mode Toggle */}
|
<Zap size={17} className="text-gray-600 hover:text-yellow-500 transition-colors" />
|
||||||
<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>
|
</button>
|
||||||
|
<button onClick={() => setDarkMode(!darkMode)} className="weekly-btn-icon" title={darkMode ? "Light Mode" : "Dark Mode"}>
|
||||||
{/* Day/Night Mode Switch */}
|
{darkMode ? <Sun size={17} className="text-yellow-500" /> : <Moon size={17} className="text-gray-500" />}
|
||||||
<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>
|
</button>
|
||||||
</div>
|
</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 */}
|
{/* Always visible: Search, Settings, User */}
|
||||||
<button
|
<button className="weekly-btn-icon" onClick={() => setIsSearchOpen(true)} title="Search"><Search size={17} /></button>
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
<button className="weekly-btn-icon" onClick={() => setShowSettings(true)} title="Settings"><Settings size={17} /></button>
|
||||||
onClick={() => setIsSearchOpen(true)}
|
|
||||||
title="Search"
|
|
||||||
>
|
|
||||||
<Search size={18} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Recurring Tasks */}
|
{/* Overflow menu — visible on tablet, hidden on desktop */}
|
||||||
<button
|
<div className="header-tablet-only" style={{ position: "relative" }}>
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
<button className="weekly-btn-icon" onClick={() => setShowHeaderMore(!showHeaderMore)} title="More">
|
||||||
onClick={() => setIsRecurringTasksOpen(true)}
|
<MoreVertical size={17} />
|
||||||
title="Recurring Tasks"
|
|
||||||
>
|
|
||||||
<Repeat size={18} />
|
|
||||||
</button>
|
</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
|
{/* Recurring Tasks — desktop only */}
|
||||||
className="p-1.5 hover:bg-gray-100 rounded-md text-gray-500 hover:text-black transition-colors"
|
<button className="weekly-btn-icon header-desktop-only" onClick={() => setIsRecurringTasksOpen(true)} title="Recurring Tasks"><Repeat size={17} /></button>
|
||||||
onClick={() => setShowSettings(true)}
|
|
||||||
title="Settings"
|
|
||||||
>
|
|
||||||
<Settings size={18} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* User Menu */}
|
{/* User Menu */}
|
||||||
<UserMenu
|
<UserMenu
|
||||||
userEmail={session?.user?.email}
|
userEmail={session?.user?.email}
|
||||||
onOpenSettings={() => setShowSettings(true)}
|
onOpenSettings={() => setShowSettings(true)}
|
||||||
language={profile.language}
|
language={profile.language}
|
||||||
trigger={
|
trigger={<button className="weekly-btn-icon" title="User Menu"><User size={17} /></button>}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user