feat(todo): inline title editing + detail modal button on task items

- Title already had contenteditable; now shows clear focus ring (primary tint)
  when active so it's obvious the title is being edited
- Added ArrowsOutSimple icon button at the right end of each task row
  (appears on hover) that opens the full TaskEditModal
- Modal wired up with save/delete/close handlers
- Fixed all remaining hardcoded colors in TaskItem to use CSS vars
  (checkbox checked/animating, drag handle, due date, meta, form inputs,
  expanded wrapper border, btn-danger, assignee dot)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-31 19:06:55 +02:00
parent 29515e7c4d
commit 3ea2c03ab2

View file

@ -9,7 +9,8 @@
import { contactsStore } from '$lib/stores/contacts.svelte';
import { ContactAvatar, ContactSelector } from '@manacore/shared-ui';
import SubtaskList from './SubtaskList.svelte';
import { Check, CheckSquare, DotsSixVertical } from '@manacore/shared-icons';
import { Check, CheckSquare, DotsSixVertical, ArrowsOutSimple } from '@manacore/shared-icons';
import TaskEditModal from './TaskEditModal.svelte';
import {
PrioritySelector,
StorypointsSelector,
@ -46,6 +47,27 @@
// Toggle for showing created date on completed tasks
let showCreatedDate = $state(false);
// Detail modal
let showModal = $state(false);
function handleOpenModal(e: MouseEvent) {
e.stopPropagation();
showModal = true;
}
function handleModalClose() {
showModal = false;
}
function handleModalSave(data: Partial<Task>) {
onSave?.(data as UpdateTaskInput);
}
function handleModalDelete(_taskId: string) {
onDelete();
showModal = false;
}
// Shared form state
const form = useTaskForm();
@ -402,6 +424,17 @@
{dueDateText()}
</span>
{/if}
<!-- Detail modal button -->
<button
type="button"
class="detail-btn"
onclick={handleOpenModal}
title="Details öffnen"
tabindex="-1"
>
<ArrowsOutSimple size={14} />
</button>
</div>
<!-- Inline subtasks -->
@ -422,6 +455,15 @@
</div>
{/if}
<!-- Detail modal -->
<TaskEditModal
{task}
open={showModal}
onClose={handleModalClose}
onSave={handleModalSave}
onDelete={handleModalDelete}
/>
<!-- Expanded inline edit form -->
{#if isExpanded}
<div class="expanded-form">
@ -601,20 +643,13 @@
.task-item-wrapper.expanded {
position: relative;
z-index: 9990;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(139, 92, 246, 0.3);
background: var(--color-surface-elevated-2);
border: 1px solid color-mix(in srgb, var(--color-primary) 35%, transparent);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
border-radius: 0.75rem;
margin-bottom: 0.75rem;
}
:global(.dark) .task-item-wrapper.expanded {
background: rgba(30, 30, 30, 0.95);
border-color: rgba(139, 92, 246, 0.4);
}
.task-item {
display: flex;
align-items: center;
@ -637,39 +672,20 @@
box-shadow: none;
backdrop-filter: none;
border-radius: 0.75rem 0.75rem 0 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
:global(.dark) .task-item-wrapper.expanded .task-item {
border-bottom-color: rgba(255, 255, 255, 0.1);
}
:global(.dark) .task-item {
background: transparent;
border: none;
border-bottom: 1px solid var(--color-border);
}
.task-item:hover {
background: transparent;
}
:global(.dark) .task-item:hover {
background: transparent;
}
.task-item.completed {
opacity: 0.6;
}
/* Completing animation */
.task-item.completing {
background: rgba(34, 197, 94, 0.08);
border-bottom-color: rgba(34, 197, 94, 0.2);
}
:global(.dark) .task-item.completing {
background: rgba(34, 197, 94, 0.12);
border-bottom-color: rgba(34, 197, 94, 0.3);
background: color-mix(in srgb, var(--color-success) 8%, transparent);
}
/* Drag handle — sticks out left beyond the content area */
@ -694,11 +710,7 @@
.drag-handle:hover {
opacity: 0.8 !important;
background: rgba(0, 0, 0, 0.05);
}
:global(.dark) .drag-handle:hover {
background: rgba(255, 255, 255, 0.1);
background: var(--color-surface-hover);
}
.drag-handle:active {
@ -715,10 +727,39 @@
/* During drag, disable pointer events on interactive elements */
:global([aria-grabbed='true']) .task-checkbox,
:global([aria-grabbed='true']) .task-content,
:global([aria-grabbed='true']) .expand-btn {
:global([aria-grabbed='true']) .expand-btn,
:global([aria-grabbed='true']) .detail-btn {
pointer-events: none;
}
/* Detail modal button */
.detail-btn {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
border: none;
background: transparent;
color: var(--color-muted-foreground);
cursor: pointer;
border-radius: 0.375rem;
opacity: 0;
transition: all 0.15s;
padding: 0;
}
.task-item:hover .detail-btn {
opacity: 1;
}
.detail-btn:hover {
color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
opacity: 1;
}
/* Checkbox with priority color fill */
.task-checkbox {
margin-top: 0.1875rem;
@ -775,8 +816,8 @@
}
.task-checkbox.checked {
background: #8b5cf6;
border-color: #8b5cf6;
background: var(--color-primary);
border-color: var(--color-primary);
border-style: solid;
}
@ -785,8 +826,8 @@
}
.task-checkbox.animating {
background: #22c55e;
border-color: #22c55e;
background: var(--color-success);
border-color: var(--color-success);
border-style: solid;
transform: scale(1.2);
}
@ -840,7 +881,7 @@
.task-title {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
color: var(--color-foreground);
white-space: normal;
word-break: break-word;
cursor: text;
@ -848,23 +889,17 @@
padding: 0.125rem 0.25rem;
margin: -0.125rem -0.25rem;
outline: none;
transition: background 0.1s;
}
.task-title:hover {
background: transparent;
}
:global(.dark) .task-title:hover {
background: transparent;
}
:global(.dark) .task-title {
color: #f3f4f6;
.task-title:focus {
background: color-mix(in srgb, var(--color-primary) 6%, transparent);
outline: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
}
.task-title.line-through {
text-decoration: line-through;
color: #9ca3af;
color: var(--color-muted-foreground);
}
/* Meta info */
@ -880,11 +915,7 @@
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: #6b7280;
}
:global(.dark) .meta-item {
color: #9ca3af;
color: var(--color-muted-foreground);
}
.meta-icon {
@ -920,13 +951,9 @@
right: -1px;
width: 6px;
height: 6px;
background: #8b5cf6;
background: var(--color-primary);
border-radius: 50%;
border: 1px solid white;
}
:global(.dark) .assignee-avatar::after {
border-color: rgba(30, 30, 30, 1);
border: 1px solid var(--color-surface-elevated-2);
}
.involved-avatars {
@ -944,34 +971,26 @@
.more-contacts {
font-size: 0.625rem;
color: #6b7280;
color: var(--color-muted-foreground);
margin-left: 0.25rem;
font-weight: 500;
}
:global(.dark) .more-contacts {
color: #9ca3af;
}
/* Due date */
.due-date {
font-size: 0.75rem;
color: #6b7280;
color: var(--color-muted-foreground);
flex-shrink: 0;
white-space: nowrap;
margin-top: 0.25rem;
}
:global(.dark) .due-date {
color: #9ca3af;
}
.due-date.overdue {
color: #ef4444;
color: var(--color-error);
}
.due-date.today {
color: #f97316;
color: var(--color-warning);
}
/* Completed date toggle */
@ -1017,20 +1036,16 @@
padding: 0.25rem;
border: none;
background: transparent;
color: #9ca3af;
color: var(--color-muted-foreground);
cursor: pointer;
border-radius: 9999px;
transition: all 0.15s;
flex-shrink: 0;
}
.task-item:hover .expand-btn {
color: #6b7280;
}
.expand-btn:hover {
color: #8b5cf6;
background: rgba(139, 92, 246, 0.1);
color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}
.expand-icon {
@ -1072,18 +1087,14 @@
.form-label {
font-size: 0.75rem;
font-weight: 600;
color: #374151;
color: var(--color-foreground);
text-transform: uppercase;
letter-spacing: 0.025em;
}
:global(.dark) .form-label {
color: #d1d5db;
}
.form-sublabel {
font-size: 0.6875rem;
color: #6b7280;
color: var(--color-muted-foreground);
}
.form-row {
@ -1124,30 +1135,21 @@
.form-input-sm {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid rgba(0, 0, 0, 0.15);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
background: rgba(255, 255, 255, 0.8);
background: var(--color-surface);
font-size: 0.875rem;
color: #374151;
color: var(--color-foreground);
transition: all 0.15s;
}
:global(.dark) .form-input,
:global(.dark) .form-textarea,
:global(.dark) .form-select,
:global(.dark) .form-input-sm {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.15);
color: #f3f4f6;
}
.form-input:focus,
.form-textarea:focus,
.form-select:focus,
.form-input-sm:focus {
outline: none;
border-color: #8b5cf6;
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1);
border-color: var(--color-primary);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 10%, transparent);
}
.form-input-sm {
@ -1166,14 +1168,10 @@
align-items: center;
justify-content: space-between;
padding-top: 0.75rem;
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-top: 1px solid var(--color-border);
margin-top: 0.5rem;
}
:global(.dark) .form-actions {
border-top-color: rgba(255, 255, 255, 0.1);
}
.btn {
display: flex;
align-items: center;
@ -1194,12 +1192,12 @@
}
.btn-danger {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
background: color-mix(in srgb, var(--color-error) 10%, transparent);
color: var(--color-error);
}
.btn-danger:hover:not(:disabled) {
background: #ef4444;
background: var(--color-error);
color: white;
}