feat(design): AnyDay area polish — tabs, cards, flyout menu

CSS:
- .weekly-someday padding: 0 (no left/right border bleed)
- .someday-tabs-bar: removed border-bottom + bg, padding 0 12px
- Tabs: underline-style (border-bottom: 3px accent on active), transparent
  bg — active tab visually merges with someday area
- Active count badge: accent-tinted instead of white-on-blue
- .weekly-someday-list: 300px wide (restored from 240px)
- .task-list-slot: min-height instead of fixed height + top/bottom padding
  so two-line tasks expand properly instead of being clipped

JSX:
- Combined provider icon + tab-assign + edit-style + delete into a single
  flyout menu per card header (trigger = provider icon if synced, else pencil)
- Flyout: "Open in Google/Apple/…", "Assign to tab", "Edit style", "Delete"
- Added menuOpenListId state to track open flyout

v1.107.1
This commit is contained in:
mARTin-B78 2026-05-11 10:16:21 +02:00
parent f0df18c853
commit 50aa310a2d
3 changed files with 119 additions and 162 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.107.0",
"version": "1.107.1",
"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": {

View File

@ -1278,7 +1278,7 @@ h3 {
.weekly-someday {
background-color: #f9f9f9;
width: 100%;
padding: 0 1rem;
padding: 0;
position: relative;
flex-shrink: 0;
}
@ -1417,8 +1417,8 @@ h3 {
.weekly-someday-list {
padding: 0;
min-height: 200px;
flex: 0 0 240px;
width: 240px;
flex: 0 0 300px;
width: 300px;
transition: transform 0.2s ease, opacity 0.2s ease, box-shadow 0.2s ease;
max-width: 100%;
display: flex;
@ -1443,13 +1443,15 @@ h3 {
}
.task-list-slot {
height: 38px;
min-height: 38px;
width: 100%;
position: relative;
display: flex;
align-items: center;
border-bottom: 1px solid var(--weekly-border);
box-sizing: border-box;
padding-top: 4px;
padding-bottom: 4px;
}
.task-list-slot.drop-target {
@ -1609,12 +1611,10 @@ h3 {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
padding: 8px 12px;
border-bottom: 2px solid var(--weekly-border);
gap: 2px;
padding: 0px 12px;
overflow-x: auto;
scrollbar-width: none;
background: var(--weekly-bg);
}
.someday-tabs-bar::-webkit-scrollbar {
@ -1642,45 +1642,47 @@ h3 {
.someday-tabs-bar-divider {
width: 1px;
height: 20px;
height: 16px;
background: var(--weekly-border, #e5e7eb);
flex-shrink: 0;
margin: 0 4px;
}
/* Someday Tab Buttons (horizontal) */
/* Someday Tab Buttons (horizontal) — underline style */
.someday-tab-btn-h {
background: var(--weekly-hover-bg, rgba(0, 0, 0, 0.05));
border: 1px solid transparent;
background: transparent;
border: none;
border-bottom: 3px solid transparent;
cursor: pointer;
color: var(--weekly-text, #444);
color: var(--weekly-text-light, #888);
font-size: 0.8rem;
font-weight: 600;
padding: 5px 13px;
border-radius: 20px;
font-weight: 500;
padding: 8px 10px 7px;
border-radius: 0;
white-space: nowrap;
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
transition: color 0.15s, border-color 0.15s;
display: inline-flex;
align-items: center;
gap: 5px;
}
.someday-tab-btn-h:hover {
background: var(--weekly-border, rgba(0, 0, 0, 0.1));
color: var(--weekly-text, #222);
color: var(--weekly-text, #333);
background: transparent;
}
.someday-tab-btn-h.active {
background: var(--weekly-accent, #6366f1);
color: #fff;
border-color: transparent;
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.35);
color: var(--weekly-text, #222);
font-weight: 700;
border-bottom-color: var(--weekly-accent, #6366f1);
background: transparent;
box-shadow: none;
}
.someday-tab-btn-h.drag-over {
outline: 2px dashed var(--weekly-accent, #6366f1);
outline-offset: -1px;
background: rgba(99, 102, 241, 0.12);
}
.someday-tab-count {
@ -1691,15 +1693,16 @@ h3 {
height: 18px;
padding: 0 5px;
border-radius: 9px;
background: rgba(0, 0, 0, 0.1);
background: rgba(0, 0, 0, 0.07);
font-size: 0.65rem;
font-weight: 700;
line-height: 1;
color: var(--weekly-text-light, #888);
}
.someday-tab-btn-h.active .someday-tab-count {
background: rgba(255, 255, 255, 0.3);
color: #fff;
background: rgba(99, 102, 241, 0.12);
color: var(--weekly-accent, #6366f1);
}
/* Someday Tab Wrapper with dissolve button */

View File

@ -720,6 +720,7 @@ export default function WeeklyView() {
const [editingTaskId, setEditingTaskId] = useState<string | null>(null);
const [draggingListId, setDraggingListId] = useState<string | null>(null);
const [listToDelete, setListToDelete] = useState<string | null>(null);
const [menuOpenListId, setMenuOpenListId] = useState<string | null>(null);
// Punkt 7+8: per-list color/icon edit popover, and per-tab settings popover
const [editingListVisualsId, setEditingListVisualsId] = useState<string | null>(null);
const [editingTabVisualsName, setEditingTabVisualsName] = useState<string | null>(null);
@ -8419,143 +8420,96 @@ export default function WeeklyView() {
if (e.key === "Enter") e.currentTarget.blur();
}}
/>
{list.externalProvider && (() => {
const providerUrl = list.externalProvider === "google" ? "https://tasks.google.com/tasks/" :
list.externalProvider === "outlook" ? "https://to-do.live.com/tasks/" :
list.externalProvider === "apple" ? "https://www.icloud.com/reminders/" : null;
const iconContent = (
<span
title={`Synced with ${list.externalProvider === "outlook" ? "Microsoft" : list.externalProvider === "google" ? "Google" : list.externalProvider === "apple" ? "Apple" : list.externalProvider === "notion" ? "Notion" : list.externalProvider}`}
style={{ display: "inline-flex", alignItems: "center", marginLeft: "8px", opacity: 0.8, flexShrink: 0, cursor: providerUrl ? "pointer" : "default" }}
>
{list.externalProvider === "outlook" ? (
<FontAwesomeIcon icon={faMicrosoft} className="w-4 h-4 text-zinc-600 dark:text-zinc-400 hover:text-[#0078D4] dark:hover:text-[#00A4EF] transition-colors" />
) : list.externalProvider === "google" ? (
<FontAwesomeIcon icon={faGoogle} className="w-4 h-4 text-zinc-600 dark:text-zinc-400 hover:text-[#4285F4] dark:hover:text-[#8AB4F8] transition-colors" />
) : list.externalProvider === "apple" ? (
<FontAwesomeIcon icon={faApple} className="w-4 h-4 text-zinc-600 dark:text-zinc-400 hover:text-[#555] dark:hover:text-[#ccc] transition-colors" />
) : list.externalProvider === "synology" ? (
<FontAwesomeIcon icon={faServer} className="w-4 h-4 text-zinc-600 dark:text-zinc-400 hover:text-[#007AFF] dark:hover:text-[#3A9CFF] transition-colors" />
) : list.externalProvider === "notion" ? (
<FontAwesomeIcon icon={faNotion} className="w-4 h-4 text-zinc-600 dark:text-zinc-400 hover:text-[#000] dark:hover:text-[#fff] transition-colors" />
) : (
<RefreshCcw size={14} className="text-zinc-400" />
)}
</span>
);
return providerUrl ? (
<a href={providerUrl} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()}>
{iconContent}
</a>
) : iconContent;
})()}
<div className="someday-tab-assign" style={{ marginLeft: "auto", position: "relative", display: "flex", alignItems: "center" }}>
<FolderPlus size={13} style={{ color: list.tab ? "var(--weekly-accent, #6366f1)" : "#bbb", flexShrink: 0 }} />
<select
className="someday-tab-select"
value={list.tab || ""}
onClick={(e) => e.stopPropagation()}
onChange={(e) => {
const val = e.target.value;
if (val === "__new__") {
e.target.value = list.tab || "";
setNewTabForListId(list.id);
setNewTabNameValue("");
} else {
assignListToTab(list.id, val || null);
}
}}
style={{
position: "absolute",
inset: 0,
opacity: 0,
cursor: "pointer",
width: "100%",
}}
title={t.assignTab || "Assign to tab"}
{/* Flyout menu: provider icon (or pencil) triggers all list actions */}
<div style={{ marginLeft: "auto", position: "relative", flexShrink: 0 }}>
<button
onClick={(e) => { e.stopPropagation(); setMenuOpenListId(menuOpenListId === list.id ? null : list.id); }}
style={{ border: "none", background: "none", cursor: "pointer", padding: "4px", display: "flex", alignItems: "center", color: darkMode ? "#6b7280" : "#aaa", borderRadius: "4px" }}
title={profile.language === "de" ? "Listenoptionen" : "List options"}
>
<option value="">{t.noTab}</option>
{somedayTabs.map(tab => (
<option key={tab} value={tab}>{tab}</option>
))}
<option value="__new__">+ {t.newTab || "New tab"}</option>
</select>
</div>
{newTabForListId === list.id && (
<input
className="someday-tab-new-input"
value={newTabNameValue}
onChange={(e) => setNewTabNameValue(e.target.value)}
onBlur={() => {
if (newTabNameValue.trim()) {
assignListToTab(list.id, newTabNameValue.trim());
}
setNewTabForListId(null);
setNewTabNameValue("");
}}
onKeyDown={(e) => {
if (e.key === "Enter") e.currentTarget.blur();
if (e.key === "Escape") {
{list.externalProvider === "google" ? <FontAwesomeIcon icon={faGoogle} style={{ fontSize: 13 }} /> :
list.externalProvider === "outlook" ? <FontAwesomeIcon icon={faMicrosoft} style={{ fontSize: 13 }} /> :
list.externalProvider === "apple" ? <FontAwesomeIcon icon={faApple} style={{ fontSize: 13 }} /> :
list.externalProvider === "synology" ? <FontAwesomeIcon icon={faServer} style={{ fontSize: 13 }} /> :
list.externalProvider === "notion" ? <FontAwesomeIcon icon={faNotion} style={{ fontSize: 13 }} /> :
<Pencil size={13} />}
</button>
{menuOpenListId === list.id && (
<>
<div style={{ position: "fixed", inset: 0, zIndex: 199 }} onClick={(e) => { e.stopPropagation(); setMenuOpenListId(null); }} />
<div style={{ position: "absolute", top: "calc(100% + 4px)", right: 0, zIndex: 200, background: darkMode ? "#1f2937" : "#fff", border: "1px solid var(--weekly-border)", borderRadius: "8px", boxShadow: "0 4px 16px rgba(0,0,0,0.12)", minWidth: "170px", overflow: "hidden" }}>
{/* Provider link */}
{list.externalProvider && (() => {
const providerUrl = list.externalProvider === "google" ? "https://tasks.google.com/tasks/" : list.externalProvider === "outlook" ? "https://to-do.live.com/tasks/" : list.externalProvider === "apple" ? "https://www.icloud.com/reminders/" : null;
const providerName = list.externalProvider === "outlook" ? "Microsoft" : list.externalProvider === "google" ? "Google" : list.externalProvider === "apple" ? "Apple" : list.externalProvider === "notion" ? "Notion" : list.externalProvider;
return providerUrl ? (
<a href={providerUrl} target="_blank" rel="noopener noreferrer" onClick={(e) => { e.stopPropagation(); setMenuOpenListId(null); }} style={{ display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", fontSize: "0.8rem", color: darkMode ? "#e5e7eb" : "#374151", textDecoration: "none" }}>
{list.externalProvider === "google" ? <FontAwesomeIcon icon={faGoogle} style={{ fontSize: 13, opacity: 0.6 }} /> : list.externalProvider === "outlook" ? <FontAwesomeIcon icon={faMicrosoft} style={{ fontSize: 13, opacity: 0.6 }} /> : list.externalProvider === "apple" ? <FontAwesomeIcon icon={faApple} style={{ fontSize: 13, opacity: 0.6 }} /> : <RefreshCcw size={13} style={{ opacity: 0.6 }} />}
{profile.language === "de" ? `In ${providerName} öffnen` : `Open in ${providerName}`}
</a>
) : null;
})()}
{/* Assign to tab */}
<div style={{ position: "relative", display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", fontSize: "0.8rem", color: darkMode ? "#e5e7eb" : "#374151", cursor: "pointer" }}>
<FolderPlus size={13} style={{ opacity: 0.6, flexShrink: 0 }} />
<span>{profile.language === "de" ? "Tab zuweisen" : "Assign to tab"}{list.tab ? ` (${list.tab})` : ""}</span>
<select
value={list.tab || ""}
onClick={(e) => e.stopPropagation()}
onChange={(e) => {
const val = e.target.value;
if (val === "__new__") {
e.target.value = list.tab || "";
setNewTabForListId(list.id);
setNewTabNameValue("");
setMenuOpenListId(null);
} else {
assignListToTab(list.id, val || null);
setMenuOpenListId(null);
}
}}
style={{ position: "absolute", inset: 0, opacity: 0, cursor: "pointer", width: "100%" }}
>
<option value="">{t.noTab}</option>
{somedayTabs.map(tab => <option key={tab} value={tab}>{tab}</option>)}
<option value="__new__">+ {t.newTab || "New tab"}</option>
</select>
</div>
{/* Edit style */}
<button onClick={(e) => { e.stopPropagation(); setEditingListVisualsId(list.id); setMenuOpenListId(null); }} style={{ width: "100%", display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", fontSize: "0.8rem", color: darkMode ? "#e5e7eb" : "#374151", background: "none", border: "none", cursor: "pointer", textAlign: "left" }}>
<Pencil size={13} style={{ opacity: 0.6, flexShrink: 0 }} />
{profile.language === "de" ? "Stil bearbeiten" : "Edit style"}
</button>
<div style={{ height: "1px", background: "var(--weekly-border)", margin: "2px 0" }} />
{/* Delete */}
<button onClick={(e) => { e.stopPropagation(); setListToDelete(list.id); setMenuOpenListId(null); }} style={{ width: "100%", display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", fontSize: "0.8rem", color: "#ef4444", background: "none", border: "none", cursor: "pointer", textAlign: "left" }}>
<Trash2 size={13} style={{ flexShrink: 0 }} />
{profile.language === "de" ? "Löschen" : "Delete"}
</button>
</div>
</>
)}
{newTabForListId === list.id && (
<input
className="someday-tab-new-input"
value={newTabNameValue}
onChange={(e) => setNewTabNameValue(e.target.value)}
onBlur={() => {
if (newTabNameValue.trim()) assignListToTab(list.id, newTabNameValue.trim());
setNewTabForListId(null);
setNewTabNameValue("");
}
}}
placeholder={t.newTabName || "Tab name..."}
autoFocus
onClick={(e) => e.stopPropagation()}
style={{
fontSize: "0.7rem",
border: "1px solid var(--weekly-border)",
borderRadius: "4px",
padding: "2px 6px",
background: "var(--weekly-bg)",
color: "var(--weekly-text)",
width: "80px",
outline: "none",
}}
/>
)}
<button
onClick={(e) => {
e.stopPropagation();
setEditingListVisualsId(editingListVisualsId === list.id ? null : list.id);
}}
style={{
border: "none",
background: "none",
cursor: "pointer",
color: "#ccc",
marginLeft: "4px",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "4px"
}}
title={profile.language === "de" ? "Listen-Stil bearbeiten" : "Edit list style"}
>
<Pencil size={14} />
</button>
<button
className="someday-list-delete-btn"
onClick={(e) => {
e.stopPropagation();
setListToDelete(list.id);
}}
style={{
border: "none",
background: "none",
cursor: "pointer",
color: "#ccc",
marginLeft: "4px",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "4px"
}}
title="Delete List"
>
<Trash2 size={16} />
</button>
}}
onKeyDown={(e) => {
if (e.key === "Enter") e.currentTarget.blur();
if (e.key === "Escape") { setNewTabForListId(null); setNewTabNameValue(""); }
}}
placeholder={t.newTabName || "Tab name..."}
autoFocus
onClick={(e) => e.stopPropagation()}
style={{ fontSize: "0.7rem", border: "1px solid var(--weekly-border)", borderRadius: "4px", padding: "2px 6px", background: "var(--weekly-bg)", color: "var(--weekly-text)", width: "80px", outline: "none" }}
/>
)}
</div>
</>
)}
</div>