feat(todo): improve task UI with priority checkboxes, drag styling, and route fix

- Integrate priority indicator into checkbox (color fill + border weight + dashed for low + ! for urgent) for better a11y
- Reduce notepad border-radius and enable rounding on mobile
- Fix reorder route being shadowed by :id param route
- Improve drag & drop styling: remove container highlight, add lifted effect on dragged item

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-26 08:51:06 +01:00
parent 23ea63a1d3
commit 5a3ee5c7cb
4 changed files with 77 additions and 39 deletions

View file

@ -58,6 +58,12 @@ export class TaskController {
return { tasks };
}
@Put('reorder')
async reorder(@CurrentUser() user: CurrentUserData, @Body() dto: ReorderTasksDto) {
const tasks = await this.taskService.reorder(user.userId, dto.taskIds, dto.projectId);
return { tasks };
}
@Get(':id')
async findOne(@CurrentUser() user: CurrentUserData, @Param('id') id: string) {
const task = await this.taskService.findByIdOrThrow(id, user.userId);
@ -119,10 +125,4 @@ export class TaskController {
const task = await this.taskService.findByIdOrThrow(id, user.userId);
return { task };
}
@Put('reorder')
async reorder(@CurrentUser() user: CurrentUserData, @Body() dto: ReorderTasksDto) {
const tasks = await this.taskService.reorder(user.userId, dto.taskIds, dto.projectId);
return { tasks };
}
}

View file

@ -372,11 +372,12 @@
</svg>
</div>
<!-- Checkbox -->
<!-- Checkbox with priority fill -->
<button
class="task-checkbox"
class="task-checkbox priority-{task.priority || 'medium'}"
class:checked={task.isCompleted}
class:animating={isAnimatingComplete}
style="--priority-color: {priorityColors[task.priority] || priorityColors.medium}"
onclick={handleToggleClick}
>
{#if task.isCompleted || isAnimatingComplete}
@ -394,15 +395,11 @@
d="M5 13l4 4L19 7"
/>
</svg>
{:else if task.priority === 'urgent'}
<span class="priority-bang">!</span>
{/if}
</button>
<!-- Priority indicator -->
<div
class="priority-dot"
style="background-color: {priorityColors[task.priority] || priorityColors.medium}"
></div>
<!-- Content -->
<div class="task-content">
{#if isEditingTitle}
@ -806,21 +803,13 @@
pointer-events: none;
}
/* Priority dot */
.priority-dot {
width: 0.625rem;
height: 0.625rem;
border-radius: 9999px;
flex-shrink: 0;
}
/* Checkbox */
/* Checkbox with priority color fill */
.task-checkbox {
width: 1.25rem;
height: 1.25rem;
border-radius: 9999px;
border: 2px solid rgba(0, 0, 0, 0.2);
background: transparent;
border: 2px solid var(--priority-color, rgba(0, 0, 0, 0.2));
background: color-mix(in srgb, var(--priority-color) 15%, transparent);
display: flex;
align-items: center;
justify-content: center;
@ -831,22 +820,57 @@
}
:global(.dark) .task-checkbox {
border-color: rgba(255, 255, 255, 0.3);
border-color: var(--priority-color, rgba(255, 255, 255, 0.3));
background: color-mix(in srgb, var(--priority-color) 20%, transparent);
}
.task-checkbox:hover {
border-color: #8b5cf6;
background: rgba(139, 92, 246, 0.1);
background: color-mix(in srgb, var(--priority-color) 35%, transparent);
}
/* Priority: low — dashed thin border */
.task-checkbox.priority-low {
border-style: dashed;
border-width: 1.5px;
}
/* Priority: medium — normal solid border */
.task-checkbox.priority-medium {
border-width: 2px;
}
/* Priority: high — thick border */
.task-checkbox.priority-high {
border-width: 3px;
}
/* Priority: urgent — thick border + exclamation mark */
.task-checkbox.priority-urgent {
border-width: 3px;
}
.priority-bang {
font-size: 0.6875rem;
font-weight: 800;
line-height: 1;
color: var(--priority-color);
pointer-events: none;
}
.task-checkbox.checked {
background: #8b5cf6;
border-color: #8b5cf6;
border-style: solid;
}
.task-checkbox.checked .priority-bang {
display: none;
}
.task-checkbox.animating {
background: #22c55e;
border-color: #22c55e;
border-style: solid;
transform: scale(1.2);
}

View file

@ -243,7 +243,7 @@
items,
flipDurationMs,
dropTargetStyle: {},
dropTargetClasses: ['task-drop-target'],
dropTargetClasses: [],
type: 'homepage-tasks',
}}
onconsider={handleDndConsider}
@ -334,17 +334,31 @@
opacity: 0.5;
}
:global(.task-drop-target) {
outline: 2px dashed #8b5cf6 !important;
outline-offset: -2px;
background: rgba(139, 92, 246, 0.08) !important;
/* Dragged item styling */
:global([aria-grabbed='true']) {
opacity: 0.9;
transform: scale(1.02);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
border-radius: 0.5rem;
background: rgba(255, 255, 255, 0.95);
z-index: 100;
}
:global(.task-drop-target) .empty-placeholder {
opacity: 0;
:global(.dark [aria-grabbed='true']) {
background: rgba(40, 40, 40, 0.95);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
:global(.dark .task-drop-target) {
background: rgba(139, 92, 246, 0.15) !important;
/* Shadow placeholder (where dragged item will land) */
:global(.task-list [data-is-dnd-shadow-item-hint]) {
background: rgba(139, 92, 246, 0.06);
border-radius: 0.375rem;
border: 1px dashed rgba(139, 92, 246, 0.3);
visibility: visible !important;
}
:global(.dark .task-list [data-is-dnd-shadow-item-hint]) {
background: rgba(139, 92, 246, 0.1);
border-color: rgba(139, 92, 246, 0.4);
}
</style>

View file

@ -507,7 +507,7 @@
max-width: 560px;
margin: 0 auto;
background: #fffef5;
border-radius: 0.75rem;
border-radius: 0.5rem;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.08),
0 1px 2px rgba(0, 0, 0, 0.04);
@ -529,7 +529,7 @@
@media (max-width: 640px) {
.notepad {
max-width: 100%;
border-radius: 0;
border-radius: 0.5rem;
}
}
</style>