feat: fix tab edit popover + align someday task items
- Fix tab double-click popover not showing: tab bar overflow-x:auto was clipping the position:absolute popover. Now uses position:fixed anchored to the button's bounding rect, captured on dblclick. Also stabilizes the outside-click listener (onCloseRef pattern, empty deps). - Restructure someday task item order to: [project icon column] [checkbox] [task name]. Fixed-width 18px icon column (placeholder when no project) keeps all checkboxes and task names vertically aligned. v1.108.0
This commit is contained in:
parent
6ccf61f09a
commit
55ef3614d1
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.107.3",
|
"version": "1.108.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": {
|
||||||
|
|||||||
@ -724,6 +724,7 @@ export default function WeeklyView() {
|
|||||||
// Punkt 7+8: per-list color/icon edit popover, and per-tab settings popover
|
// Punkt 7+8: per-list color/icon edit popover, and per-tab settings popover
|
||||||
const [editingListVisualsId, setEditingListVisualsId] = useState<string | null>(null);
|
const [editingListVisualsId, setEditingListVisualsId] = useState<string | null>(null);
|
||||||
const [editingTabVisualsName, setEditingTabVisualsName] = useState<string | null>(null);
|
const [editingTabVisualsName, setEditingTabVisualsName] = useState<string | null>(null);
|
||||||
|
const [editingTabVisualsRect, setEditingTabVisualsRect] = useState<DOMRect | null>(null);
|
||||||
const [activeSomedayTab, setActiveSomedayTab] = useState<string | null>(null);
|
const [activeSomedayTab, setActiveSomedayTab] = useState<string | null>(null);
|
||||||
const [editingTabName, setEditingTabName] = useState<string | null>(null);
|
const [editingTabName, setEditingTabName] = useState<string | null>(null);
|
||||||
const [renamingTabValue, setRenamingTabValue] = useState("");
|
const [renamingTabValue, setRenamingTabValue] = useState("");
|
||||||
@ -8069,7 +8070,7 @@ export default function WeeklyView() {
|
|||||||
<button
|
<button
|
||||||
className={`someday-tab-btn-h ${activeSomedayTab === tab ? "active" : ""} ${dragOverTab === tab ? "drag-over" : ""}`}
|
className={`someday-tab-btn-h ${activeSomedayTab === tab ? "active" : ""} ${dragOverTab === tab ? "drag-over" : ""}`}
|
||||||
onClick={() => setSomedayTab(tab)}
|
onClick={() => setSomedayTab(tab)}
|
||||||
onDoubleClick={(e) => { e.stopPropagation(); setEditingTabVisualsName(editingTabVisualsName === tab ? null : tab); }}
|
onDoubleClick={(e) => { e.stopPropagation(); const newTab = editingTabVisualsName === tab ? null : tab; if (newTab) setEditingTabVisualsRect((e.currentTarget as HTMLElement).getBoundingClientRect()); setEditingTabVisualsName(newTab); }}
|
||||||
style={tv.color ? {
|
style={tv.color ? {
|
||||||
background: activeSomedayTab === tab ? tv.color : `${tv.color}22`,
|
background: activeSomedayTab === tab ? tv.color : `${tv.color}22`,
|
||||||
color: activeSomedayTab === tab ? "#fff" : tv.color,
|
color: activeSomedayTab === tab ? "#fff" : tv.color,
|
||||||
@ -8097,6 +8098,7 @@ export default function WeeklyView() {
|
|||||||
onRename={(newName) => { renameTab(tab, newName); setEditingTabVisualsName(null); }}
|
onRename={(newName) => { renameTab(tab, newName); setEditingTabVisualsName(null); }}
|
||||||
onDissolve={() => dissolveTab(tab)}
|
onDissolve={() => dissolveTab(tab)}
|
||||||
onClose={() => setEditingTabVisualsName(null)}
|
onClose={() => setEditingTabVisualsName(null)}
|
||||||
|
anchorRect={editingTabVisualsRect}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -10188,8 +10190,8 @@ function TaskItem({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "flex-start",
|
alignItems: isSomeday ? "center" : "flex-start",
|
||||||
gap: "0.5rem",
|
gap: isSomeday ? "4px" : "0.5rem",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -10238,6 +10240,23 @@ function TaskItem({
|
|||||||
</form>
|
</form>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{/* Someday: fixed-width project icon column for alignment */}
|
||||||
|
{isSomeday && showProjectIcons && (
|
||||||
|
<span style={{ width: "18px", minWidth: "18px", flexShrink: 0, display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
|
||||||
|
{task.project && <ProjectIcon icon={task.project.icon} size={13} color={task.project.color || "#888"} />}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{/* Someday: checkbox outside text span so it aligns in its own column */}
|
||||||
|
{isSomeday && showTaskCheckboxes && (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={task.completed}
|
||||||
|
onChange={(e) => { e.stopPropagation(); onToggle(); }}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="task-checkbox flex-shrink-0"
|
||||||
|
style={{ width: "16px", height: "16px", margin: 0, cursor: "pointer", flexShrink: 0, accentColor: "var(--weekly-teal, #009a9a)", WebkitAppearance: "checkbox" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className={`weekly-task-text flex-1 ${task.completed ? "completed" : ""}`}
|
className={`weekly-task-text flex-1 ${task.completed ? "completed" : ""}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -10251,7 +10270,8 @@ function TaskItem({
|
|||||||
: { display: "flex", alignItems: "center", gap: "6px", ...(task.completed && showTaskCheckboxes ? { opacity: 0.5 } : {}) }
|
: { display: "flex", alignItems: "center", gap: "6px", ...(task.completed && showTaskCheckboxes ? { opacity: 0.5 } : {}) }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{showTaskCheckboxes && (
|
{/* Non-someday: checkbox stays inside text span */}
|
||||||
|
{!isSomeday && showTaskCheckboxes && (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={task.completed}
|
checked={task.completed}
|
||||||
@ -10297,7 +10317,8 @@ function TaskItem({
|
|||||||
: <User size={11} color="#8b5cf6" />}
|
: <User size={11} color="#8b5cf6" />}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{showProjectIcons && task.project && (
|
{/* Non-someday: inline project icon; someday uses the column above */}
|
||||||
|
{showProjectIcons && task.project && !isSomeday && (
|
||||||
<span style={{ marginRight: "4px", verticalAlign: "middle" }}>
|
<span style={{ marginRight: "4px", verticalAlign: "middle" }}>
|
||||||
<ProjectIcon icon={task.project.icon} size={13} color={task.project.color || "#888"} />
|
<ProjectIcon icon={task.project.icon} size={13} color={task.project.color || "#888"} />
|
||||||
</span>
|
</span>
|
||||||
@ -10992,11 +11013,13 @@ function TabVisualsPopover({
|
|||||||
onRename,
|
onRename,
|
||||||
onDissolve,
|
onDissolve,
|
||||||
onClose,
|
onClose,
|
||||||
|
anchorRect,
|
||||||
}: {
|
}: {
|
||||||
tabName: string;
|
tabName: string;
|
||||||
visuals: { color?: string; icon?: string };
|
visuals: { color?: string; icon?: string };
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
language: string;
|
language: string;
|
||||||
|
anchorRect?: DOMRect | null;
|
||||||
onChange: (updates: { color?: string | null; icon?: string | null }) => void;
|
onChange: (updates: { color?: string | null; icon?: string | null }) => void;
|
||||||
onRename: (newName: string) => void;
|
onRename: (newName: string) => void;
|
||||||
onDissolve: () => void;
|
onDissolve: () => void;
|
||||||
@ -11005,23 +11028,25 @@ function TabVisualsPopover({
|
|||||||
const [iconPickerOpen, setIconPickerOpen] = useState(false);
|
const [iconPickerOpen, setIconPickerOpen] = useState(false);
|
||||||
const [renameValue, setRenameValue] = useState(tabName);
|
const [renameValue, setRenameValue] = useState(tabName);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const onCloseRef = useRef(onClose);
|
||||||
|
useEffect(() => { onCloseRef.current = onClose; }, [onClose]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onDoc = (e: MouseEvent) => {
|
const onDoc = (e: MouseEvent) => {
|
||||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) onClose();
|
if (containerRef.current && !containerRef.current.contains(e.target as Node)) onCloseRef.current();
|
||||||
};
|
};
|
||||||
document.addEventListener("mousedown", onDoc);
|
document.addEventListener("mousedown", onDoc);
|
||||||
return () => document.removeEventListener("mousedown", onDoc);
|
return () => document.removeEventListener("mousedown", onDoc);
|
||||||
}, [onClose]);
|
}, []);
|
||||||
const de = language === "de";
|
const de = language === "de";
|
||||||
const labelStyle = { fontSize: "0.75rem", color: darkMode ? "#9ca3af" : "#666", flex: 1 } as const;
|
const labelStyle = { fontSize: "0.75rem", color: darkMode ? "#9ca3af" : "#666", flex: 1 } as const;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "fixed",
|
||||||
top: "calc(100% + 6px)",
|
top: anchorRect ? anchorRect.bottom + 6 : -9999,
|
||||||
left: 0,
|
left: anchorRect ? anchorRect.left : -9999,
|
||||||
zIndex: 100,
|
zIndex: 10000,
|
||||||
background: darkMode ? "#1e1e2e" : "#fff",
|
background: darkMode ? "#1e1e2e" : "#fff",
|
||||||
border: `1px solid ${darkMode ? "#444" : "#e5e7eb"}`,
|
border: `1px solid ${darkMode ? "#444" : "#e5e7eb"}`,
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user