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:
parent
f0df18c853
commit
50aa310a2d
@ -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": {
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user