feat(manacore, shared-ui): integrate cross-type DnD into unified app

- Shared TagStrip: add dragSource on tag pills + passiveDropZone for item→tag
  drops. New onTagDrop and dropAccepts props. DnD CSS for hover/success states.
- Unified app layout: add DragPreview, context-based tagDropHandler so child
  pages can register their own drop logic.
- Todo module: add updateLabels() to tasks store (with metadata merge).
- Todo page: add dropTarget on task items, tag badge display via getTaskTags(),
  register tagDropHandler for passive task→tag drops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-01 21:30:40 +02:00
parent 3bf40fc61f
commit bd67e8d20b
4 changed files with 174 additions and 2 deletions

View file

@ -1,6 +1,9 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { Tag, Plus, X } from '@manacore/shared-icons';
import { dragSource } from '../dnd/drag-source';
import { passiveDropZone } from '../dnd/passive-drop';
import type { DragPayload } from '../dnd/types';
interface TagItem {
id: string;
@ -17,6 +20,10 @@
onToggle: (tagId: string) => void;
/** Called when filter is cleared */
onClear: () => void;
/** Called when an item (task, card, etc.) is dropped on a tag pill */
onTagDrop?: (tagId: string, payload: DragPayload) => void;
/** Drag types accepted for drop-on-tag (default: ['task']) */
dropAccepts?: string[];
/** Link for "Tags verwalten" pill */
managementHref?: string;
/** Loading state */
@ -34,6 +41,8 @@
selectedIds,
onToggle,
onClear,
onTagDrop,
dropAccepts = ['task'],
managementHref = '/tags',
loading = false,
showCreateButton = true,
@ -93,6 +102,16 @@
onclick={() => onToggle(tag.id)}
title={tag.name}
style="--tag-color: {tag.color || '#8b5cf6'}"
use:dragSource={{
type: 'tag',
data: () => ({ id: tag.id, name: tag.name, color: tag.color || '#8b5cf6' }),
}}
use:passiveDropZone={{
accepts: dropAccepts,
onDrop: (payload) => onTagDrop?.(tag.id, payload),
highlightClass: 'tag-drop-highlight',
disabled: !onTagDrop,
}}
>
<span class="tag-dot"></span>
<span class="tag-name">{tag.name}</span>
@ -343,6 +362,45 @@
font-weight: 500;
}
/* DnD: Tag is being dragged */
:global(.tag-pill.mana-drag-source-active) {
opacity: 0.5;
transform: scale(0.95) !important;
}
/* DnD: Item hovering over tag pill */
.tag-drop-highlight {
transform: scale(1.15) !important;
background: var(--tag-color) !important;
border-color: var(--tag-color) !important;
box-shadow: 0 0 16px color-mix(in srgb, var(--tag-color) 40%, transparent) !important;
}
.tag-drop-highlight .tag-dot {
background-color: white !important;
}
.tag-drop-highlight .tag-name {
color: white !important;
}
/* DnD: Success flash after drop */
:global(.tag-pill.mana-passive-zone-success) {
animation: tag-drop-success 400ms ease-out;
}
@keyframes tag-drop-success {
0% {
transform: scale(1.2);
}
50% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
/* Responsive */
@media (max-width: 640px) {
.tag-strip-wrapper {