refactor(briefwechsel): extract TagChipList from ThumbnailRow
Lifts the three-chip-plus-"+N" tag row out of ThumbnailRow into a standalone TagChipList component so the chip cap + overflow policy lives in one place and can be reused on other surfaces (document detail header is a candidate). ThumbnailRow drops from 110 to ~90 lines and no longer owns tag-slicing logic — it just asks for the list with max=3. Behavior is byte-identical: same data-testid, same max cap, same "+N" overflow indicator. All ThumbnailRow row-level tag tests continue to pass against the new composition. Refs #305 Fixes @felixbrandt suggestion 1 from PR review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
23
frontend/src/lib/components/TagChipList.svelte
Normal file
23
frontend/src/lib/components/TagChipList.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
type Tag = { id: string; name: string };
|
||||
|
||||
let { tags, max }: { tags: Tag[]; max: number } = $props();
|
||||
|
||||
const displayedTags = $derived(tags.slice(0, max));
|
||||
const hiddenTagCount = $derived(Math.max(0, tags.length - max));
|
||||
</script>
|
||||
|
||||
{#if tags.length > 0}
|
||||
<div class="flex flex-wrap items-center gap-1 pt-0.5">
|
||||
{#each displayedTags as tag (tag.id)}
|
||||
<span
|
||||
data-testid="thumb-row-tag"
|
||||
class="max-w-[140px] truncate rounded-full border border-line bg-surface px-2 py-0.5 text-xs text-ink-2"
|
||||
>{tag.name}</span
|
||||
>
|
||||
{/each}
|
||||
{#if hiddenTagCount > 0}
|
||||
<span class="text-xs font-bold text-ink-3">+{hiddenTagCount}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
34
frontend/src/lib/components/TagChipList.svelte.spec.ts
Normal file
34
frontend/src/lib/components/TagChipList.svelte.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
|
||||
import TagChipList from './TagChipList.svelte';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const makeTags = (n: number) =>
|
||||
Array.from({ length: n }, (_, i) => ({ id: `t${i}`, name: `Tag${i}` }));
|
||||
|
||||
describe('TagChipList', () => {
|
||||
it('renders all tags as chips when under the cap', () => {
|
||||
render(TagChipList, { tags: makeTags(2), max: 3 });
|
||||
const chips = document.querySelectorAll('[data-testid="thumb-row-tag"]');
|
||||
expect(chips).toHaveLength(2);
|
||||
expect(document.body.textContent).not.toMatch(/\+/);
|
||||
});
|
||||
|
||||
it('caps visible chips at max and renders +N for the remainder', () => {
|
||||
render(TagChipList, { tags: makeTags(5), max: 3 });
|
||||
const chips = document.querySelectorAll('[data-testid="thumb-row-tag"]');
|
||||
expect(chips).toHaveLength(3);
|
||||
expect(document.body.textContent).toMatch(/\+2/);
|
||||
});
|
||||
|
||||
it('renders nothing when tags is empty', () => {
|
||||
render(TagChipList, { tags: [], max: 3 });
|
||||
const chips = document.querySelectorAll('[data-testid="thumb-row-tag"]');
|
||||
expect(chips).toHaveLength(0);
|
||||
expect(document.body.textContent).not.toMatch(/\+/);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ConversationThumbnail from '$lib/components/ConversationThumbnail.svelte';
|
||||
import TagChipList from '$lib/components/TagChipList.svelte';
|
||||
import { formatDate } from '$lib/utils/date';
|
||||
import { relativeYearsDe } from '$lib/relativeTime';
|
||||
|
||||
@@ -36,8 +37,6 @@ let {
|
||||
} = $props();
|
||||
|
||||
const title = $derived(doc.title || doc.originalFilename);
|
||||
const displayedTags = $derived((doc.tags ?? []).slice(0, 3));
|
||||
const hiddenTagCount = $derived(Math.max(0, (doc.tags ?? []).length - 3));
|
||||
const otherPartyName = $derived(
|
||||
showOtherParty
|
||||
? isOut
|
||||
@@ -91,19 +90,6 @@ const ariaLabel = $derived(
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if displayedTags.length > 0}
|
||||
<div class="flex flex-wrap items-center gap-1 pt-0.5">
|
||||
{#each displayedTags as tag (tag.id)}
|
||||
<span
|
||||
data-testid="thumb-row-tag"
|
||||
class="max-w-[140px] truncate rounded-full border border-line bg-surface px-2 py-0.5 text-xs text-ink-2"
|
||||
>{tag.name}</span
|
||||
>
|
||||
{/each}
|
||||
{#if hiddenTagCount > 0}
|
||||
<span class="text-xs font-bold text-ink-3">+{hiddenTagCount}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<TagChipList tags={doc.tags ?? []} max={3} />
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user