refactor(todo,photos): use shared TagChip and TagSelector components

Replace todo's custom 87-line TagSelector with thin wrapper around
shared-ui TagSelector. Replace inline tag chip HTML in TaskItem,
KanbanTaskCard, and PhotoDetailModal with shared TagChip component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-02 14:24:34 +02:00
parent 04fcbd15c9
commit de8335277a
4 changed files with 23 additions and 87 deletions

View file

@ -3,6 +3,7 @@
import type { Photo } from '$lib/modules/photos/types';
import { photoStore } from '$lib/modules/photos/stores/photos.svelte';
import { CaretRight, DownloadSimple, Heart, X } from '@manacore/shared-icons';
import { TagChip } from '@manacore/shared-ui';
interface Props {
photo: Photo;
@ -145,12 +146,7 @@
<h4 class="info-label">Tags</h4>
<div class="flex flex-wrap gap-2">
{#each photo.tags as tag}
<span
class="rounded-full px-2 py-0.5 text-xs"
style="background-color: {tag.color}20; color: {tag.color}"
>
{tag.name}
</span>
<TagChip name={tag.name} color={tag.color} />
{/each}
</div>
</div>

View file

@ -2,6 +2,7 @@
import type { Task, TaskPriority } from '../types';
import { getPriorityLabel, getPriorityColor } from '../queries';
import { Check, Circle, CalendarBlank, CheckSquare } from '@manacore/shared-icons';
import { TagChip } from '@manacore/shared-ui';
import { isToday, isPast, format } from 'date-fns';
import { de } from 'date-fns/locale';
@ -115,12 +116,7 @@
</span>
{/if}
{#each taskTags as tag (tag.id)}
<span
class="rounded-full px-1.5 py-0.5 text-[0.625rem] font-medium"
style="background: color-mix(in srgb, {tag.color} 15%, transparent); color: {tag.color}"
>
{tag.name}
</span>
<TagChip name={tag.name} color={tag.color} />
{/each}
</div>
</div>

View file

@ -2,7 +2,8 @@
import { getContext } from 'svelte';
import type { Observable } from 'dexie';
import type { LocalLabel } from '../../types';
import { X, Plus, Tag } from '@manacore/shared-icons';
import { TagSelector as SharedTagSelector } from '@manacore/shared-ui';
import type { Tag } from '@manacore/shared-ui';
interface Props {
selectedIds: string[];
@ -18,75 +19,22 @@
return () => sub.unsubscribe();
});
let showPicker = $state(false);
function toggle(id: string) {
if (selectedIds.includes(id)) {
onChange(selectedIds.filter((i) => i !== id));
} else {
onChange([...selectedIds, id]);
}
}
let selectedLabels = $derived(
selectedIds
.map((id) => allLabels.find((l) => l.id === id))
.filter((l): l is LocalLabel => l != null)
// Adapt LocalLabel[] to Tag[] for the shared component
const tags: Tag[] = $derived(allLabels.map((l) => ({ id: l.id, name: l.name, color: l.color })));
const selectedTags: Tag[] = $derived(
selectedIds.map((id) => tags.find((t) => t.id === id)).filter((t): t is Tag => t != null)
);
let availableLabels = $derived(allLabels.filter((l) => !selectedIds.includes(l.id)));
function handleTagsChange(newTags: Tag[]) {
onChange(newTags.map((t) => t.id));
}
</script>
<div class="flex flex-wrap items-center gap-1">
{#each selectedLabels as label (label.id)}
<span
class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[0.6875rem] font-medium"
style="background: color-mix(in srgb, {label.color} 15%, transparent); color: {label.color}"
>
{label.name}
<button type="button" onclick={() => toggle(label.id)} class="hover:opacity-70">
<X size={10} />
</button>
</span>
{/each}
<div class="relative">
<button
type="button"
onclick={() => (showPicker = !showPicker)}
class="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-muted"
>
<Tag size={12} />
<Plus size={10} />
</button>
{#if showPicker && availableLabels.length > 0}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="absolute left-0 top-full z-50 mt-1 min-w-[140px] rounded-lg border border-border bg-card p-1 shadow-lg"
onclick={(e) => e.stopPropagation()}
>
{#each availableLabels as label (label.id)}
<button
type="button"
onclick={() => {
toggle(label.id);
if (availableLabels.length <= 1) showPicker = false;
}}
class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-muted"
>
<span class="h-2.5 w-2.5 rounded-full" style="background-color: {label.color}"></span>
{label.name}
</button>
{/each}
</div>
{/if}
</div>
</div>
{#if showPicker}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 z-40" onclick={() => (showPicker = false)}></div>
{/if}
<SharedTagSelector
{tags}
{selectedTags}
onTagsChange={handleTagsChange}
addTagLabel="Label"
placeholder="Label hinzufügen..."
searchPlaceholder="Label suchen..."
/>

View file

@ -3,6 +3,7 @@
import { isToday, isPast, format } from 'date-fns';
import { de } from 'date-fns/locale';
import { Check, Circle, CalendarBlank, CheckSquare, Flag, Trash } from '@manacore/shared-icons';
import { TagChip } from '@manacore/shared-ui';
interface Props {
task: Task;
@ -144,12 +145,7 @@
{/if}
{#each taskLabels as label (label.id)}
<span
class="rounded px-1.5 py-0.5 text-[0.625rem] font-medium"
style="background: color-mix(in srgb, {label.color} 15%, transparent); color: {label.color}"
>
{label.name}
</span>
<TagChip name={label.name} color={label.color} />
{/each}
</div>
{/if}