From 019f3eb9fd71f59c19ac68d39d9809ff80d13810 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 3 Apr 2026 11:37:41 +0200 Subject: [PATCH] feat(manacore/web): show tags in detail views with click-to-remove Display assigned tags as colored pills in todo, calendar, contacts DetailViews. Clicking a tag pill removes it from the item. Hover turns the X icon red for clear affordance. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../modules/calendar/views/DetailView.svelte | 71 ++++++++++++++++- .../modules/contacts/views/DetailView.svelte | 70 +++++++++++++++++ .../lib/modules/todo/views/DetailView.svelte | 76 ++++++++++++++++++- 3 files changed, 215 insertions(+), 2 deletions(-) diff --git a/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte b/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte index 5b434d539..27a208db2 100644 --- a/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte +++ b/apps/manacore/apps/web/src/lib/modules/calendar/views/DetailView.svelte @@ -6,9 +6,10 @@ import { liveQuery } from 'dexie'; import { db } from '$lib/data/database'; import { eventsStore } from '../stores/events.svelte'; - import { Trash, MapPin, Clock } from '@manacore/shared-icons'; + import { Trash, MapPin, Clock, X } from '@manacore/shared-icons'; import type { ViewProps } from '$lib/components/workbench/nav-stack'; import type { LocalEvent } from '../types'; + import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte'; let { navigate, goBack, params }: ViewProps = $props(); let eventId = $derived(params.eventId as string); @@ -26,6 +27,18 @@ let focused = $state(false); + const tagsQuery = useAllTags(); + let allTags = $derived(tagsQuery.value ?? []); + let eventTags = $derived(getTagsByIds(allTags, event?.tagIds ?? [])); + + async function removeTag(tagId: string) { + const current = event?.tagIds ?? []; + await eventsStore.updateTagIds( + eventId, + current.filter((id) => id !== tagId) + ); + } + $effect(() => { eventId; // track confirmDelete = false; @@ -145,6 +158,26 @@ {/if} + + {#if eventTags.length > 0} +
+ +
+ {#each eventTags as tag (tag.id)} + + {/each} +
+
+ {/if} +
@@ -298,6 +331,42 @@ flex-direction: column; gap: 0.375rem; } + .tags-list { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + } + .tag-pill { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.125rem 0.5rem; + border-radius: 9999px; + border: none; + background: color-mix(in srgb, var(--tag-color) 12%, transparent); + font-size: 0.6875rem; + color: #6b7280; + cursor: pointer; + transition: all 0.15s; + } + .tag-pill:hover { + background: color-mix(in srgb, var(--tag-color) 20%, transparent); + color: #ef4444; + } + :global(.dark) .tag-pill { + background: color-mix(in srgb, var(--tag-color) 18%, transparent); + color: #9ca3af; + } + :global(.dark) .tag-pill:hover { + background: color-mix(in srgb, var(--tag-color) 28%, transparent); + color: #ef4444; + } + .tag-dot { + width: 6px; + height: 6px; + border-radius: 9999px; + flex-shrink: 0; + } .section-label { font-size: 0.6875rem; font-weight: 600; diff --git a/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte b/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte index 24a135f6f..fcb09da32 100644 --- a/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte +++ b/apps/manacore/apps/web/src/lib/modules/contacts/views/DetailView.svelte @@ -14,9 +14,11 @@ MapPin, Briefcase, Globe, + X, } from '@manacore/shared-icons'; import type { ViewProps } from '$lib/components/workbench/nav-stack'; import type { LocalContact } from '../types'; + import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte'; let { navigate, goBack, params }: ViewProps = $props(); let contactId = $derived(params.contactId as string); @@ -40,6 +42,18 @@ let editWebsite = $state(''); let editNotes = $state(''); + const tagsQuery = useAllTags(); + let allTags = $derived(tagsQuery.value ?? []); + let contactTags = $derived(getTagsByIds(allTags, contact?.tagIds ?? [])); + + async function removeTag(tagId: string) { + const current = contact?.tagIds ?? []; + await contactsStore.updateTagIds( + contactId, + current.filter((id) => id !== tagId) + ); + } + $effect(() => { contactId; // track confirmDelete = false; @@ -255,6 +269,26 @@
+ + {#if contactTags.length > 0} +
+ +
+ {#each contactTags as tag (tag.id)} + + {/each} +
+
+ {/if} +
@@ -447,6 +481,42 @@ flex-direction: column; gap: 0.375rem; } + .tags-list { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + } + .tag-pill { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.125rem 0.5rem; + border-radius: 9999px; + border: none; + background: color-mix(in srgb, var(--tag-color) 12%, transparent); + font-size: 0.6875rem; + color: #6b7280; + cursor: pointer; + transition: all 0.15s; + } + .tag-pill:hover { + background: color-mix(in srgb, var(--tag-color) 20%, transparent); + color: #ef4444; + } + :global(.dark) .tag-pill { + background: color-mix(in srgb, var(--tag-color) 18%, transparent); + color: #9ca3af; + } + :global(.dark) .tag-pill:hover { + background: color-mix(in srgb, var(--tag-color) 28%, transparent); + color: #ef4444; + } + .tag-dot { + width: 6px; + height: 6px; + border-radius: 9999px; + flex-shrink: 0; + } .section-label { font-size: 0.6875rem; font-weight: 600; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte b/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte index 949d95b1d..56c2685fe 100644 --- a/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte +++ b/apps/manacore/apps/web/src/lib/modules/todo/views/DetailView.svelte @@ -6,9 +6,10 @@ import { liveQuery } from 'dexie'; import { db } from '$lib/data/database'; import { tasksStore } from '../stores/tasks.svelte'; - import { Check, Trash } from '@manacore/shared-icons'; + import { Check, Trash, X } from '@manacore/shared-icons'; import type { ViewProps } from '$lib/components/workbench/nav-stack'; import type { LocalTask, TaskPriority } from '../types'; + import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte'; let { navigate, goBack, params }: ViewProps = $props(); let taskId = $derived(params.taskId as string); @@ -25,6 +26,23 @@ // Track whether user is actively editing to prevent overwrite from liveQuery let focused = $state(false); + const tagsQuery = useAllTags(); + let allTags = $derived(tagsQuery.value ?? []); + + function getTaskTagIds(): string[] { + return ((task?.metadata as Record)?.labelIds as string[]) ?? []; + } + + let taskTags = $derived(getTagsByIds(allTags, getTaskTagIds())); + + async function removeTag(tagId: string) { + const current = getTaskTagIds(); + await tasksStore.updateLabels( + taskId, + current.filter((id) => id !== tagId) + ); + } + $effect(() => { taskId; // track confirmDelete = false; @@ -152,6 +170,26 @@ {/if}
+ + {#if taskTags.length > 0} +
+ +
+ {#each taskTags as tag (tag.id)} + + {/each} +
+
+ {/if} +
@@ -349,6 +387,42 @@ flex-direction: column; gap: 0.375rem; } + .tags-list { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + } + .tag-pill { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.125rem 0.5rem; + border-radius: 9999px; + border: none; + background: color-mix(in srgb, var(--tag-color) 12%, transparent); + font-size: 0.6875rem; + color: #6b7280; + cursor: pointer; + transition: all 0.15s; + } + .tag-pill:hover { + background: color-mix(in srgb, var(--tag-color) 20%, transparent); + color: #ef4444; + } + :global(.dark) .tag-pill { + background: color-mix(in srgb, var(--tag-color) 18%, transparent); + color: #9ca3af; + } + :global(.dark) .tag-pill:hover { + background: color-mix(in srgb, var(--tag-color) 28%, transparent); + color: #ef4444; + } + .tag-dot { + width: 6px; + height: 6px; + border-radius: 9999px; + flex-shrink: 0; + } .section-label { font-size: 0.6875rem; font-weight: 600;