diff --git a/apps/tauri/src/lib/components/DateTimePicker.svelte b/apps/tauri/src/lib/components/DateTimePicker.svelte new file mode 100644 index 0000000..40cafce --- /dev/null +++ b/apps/tauri/src/lib/components/DateTimePicker.svelte @@ -0,0 +1,186 @@ + + + + +
{ if (e.key === "Escape") dismiss(); }} +> + +
e.stopPropagation()} + > + +
+ Date & Time + +
+ + +
+ {monthLabel} +
+ + +
+
+ + +
+ {#each DAY_NAMES as name} +
{name}
+ {/each} +
+ + +
+ {#each calendarCells as day} + {#if day === null} +
+ {:else} + + {/if} + {/each} +
+ + +
+ {#if includeTime} + Time +
+ + : + +
+ + {:else} + + {/if} +
+ + + {#if value} +
+ +
+ {/if} +
+
diff --git a/apps/tauri/src/lib/components/TaskDetailView.svelte b/apps/tauri/src/lib/components/TaskDetailView.svelte new file mode 100644 index 0000000..20085e7 --- /dev/null +++ b/apps/tauri/src/lib/components/TaskDetailView.svelte @@ -0,0 +1,195 @@ + + + + +
+ + + +
+ + {#if showMenu} +
+ +
+ {/if} +
+
+ + +
+ + + + +
+ + + + +
+ + +
+ + + + {#if task.due_date} +
+ + +
+ {:else} + + {/if} +
+
+ + +
+ +
+ + +{#if showDatePicker} + (showDatePicker = false)} + /> +{/if} diff --git a/apps/tauri/src/lib/components/TaskItem.svelte b/apps/tauri/src/lib/components/TaskItem.svelte index 9e40f90..c2e23e7 100644 --- a/apps/tauri/src/lib/components/TaskItem.svelte +++ b/apps/tauri/src/lib/components/TaskItem.svelte @@ -1,5 +1,4 @@ @@ -7,26 +6,18 @@ import type { Task } from "../types"; import { app } from "../stores/app.svelte"; - let { task }: { task: Task } = $props(); + let { task, onopen }: { task: Task; onopen?: (task: Task) => void } = $props(); - let editTitle = $state(task.title); - let editDesc = $state(task.description); - let editing = $derived(editingTaskId === task.id); let touchStartX = $state(0); let swipeX = $state(0); let swiping = $state(false); - let containerEl = $state(null); - let titleInputEl = $state(null); - let showMenu = $state(false); - let menuEl = $state(null); let transitioning = $state(false); let animatingIn = $state(false); let isCompleted = $derived(task.status === "completed"); $effect(() => { - // Check on status change whether this task should animate in - const _ = task.status; // track reactively + const _ = task.status; if (animateInIds.has(task.id)) { animateInIds.delete(task.id); animatingIn = true; @@ -38,50 +29,14 @@ } }); - async function handleToggle() { + async function handleToggle(e: MouseEvent) { + e.stopPropagation(); transitioning = true; animateInIds.add(task.id); await new Promise((r) => setTimeout(r, 200)); await app.toggleTask(task.id); } - function handleMenuClickOutside(e: MouseEvent) { - if (showMenu && menuEl && !menuEl.contains(e.target as Node)) { - showMenu = false; - } - } - - $effect(() => { - if (showMenu) { - window.addEventListener("mousedown", handleMenuClickOutside); - return () => window.removeEventListener("mousedown", handleMenuClickOutside); - } - }); - - function startEditing() { - if (editing) return; - editingTaskId = task.id; - editTitle = task.title; - editDesc = task.description; - setTimeout(() => titleInputEl?.focus(), 220); - } - - async function save() { - if (editingTaskId !== task.id) return; - editingTaskId = null; - const trimmed = editTitle.trim(); - if (!trimmed) { editTitle = task.title; return; } - if (trimmed === task.title && editDesc === task.description) return; - await app.updateTask({ ...task, title: trimmed, description: editDesc }); - } - - function handleFocusOut(e: FocusEvent) { - if (containerEl?.contains(e.relatedTarget as Node)) return; - requestAnimationFrame(() => { - if (editingTaskId === task.id) save(); - }); - } - function handleTouchStart(e: TouchEvent) { touchStartX = e.touches[0].clientX; swiping = true; @@ -98,7 +53,9 @@ if (Math.abs(swipeX) > 100) { swipeX = 0; swiping = false; - handleToggle(); + transitioning = true; + animateInIds.add(task.id); + setTimeout(() => app.toggleTask(task.id), 200); return; } swipeX = 0; @@ -120,9 +77,9 @@ class="grid transition-[grid-template-rows,opacity] duration-300 ease-out {animatingIn || transitioning ? 'grid-rows-[0fr] opacity-0' : 'grid-rows-[1fr] opacity-100'}" >
+
- -
onopen?.(task)} > - - - -
- {#if editing} - { if (e.key === "Enter") (e.target as HTMLElement).blur(); if (e.key === "Escape") { editTitle = task.title; editDesc = task.description; editingTaskId = null; } }} - /> - {:else} -

- {task.title} -

- {#if task.description} -

{task.description}

- {/if} - {#if task.due_date} - - {formatDate(task.due_date)} - - {/if} - {/if} - - -
-
- -
-
- -
- - {#if showMenu} -
- -
- {/if} -
-
+ +
+

+ {task.title} +

+ {#if task.description} +

{task.description}

+ {/if} + {#if task.due_date} + + {formatDate(task.due_date)} + + {/if} +
+ + + + + +