refactor(dashboard): ReaderDraftsModule mint left-border, card-head, row structure (TDD, #483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,24 +12,47 @@ interface Props {
|
|||||||
const { drafts }: Props = $props();
|
const { drafts }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
<div
|
||||||
<h2 class="mb-5 text-xs font-bold tracking-widest text-ink-3 uppercase">
|
class="flex flex-col overflow-hidden rounded-sm border border-l-[3px] border-line border-l-brand-mint bg-surface"
|
||||||
{m.dashboard_reader_drafts_heading()}
|
>
|
||||||
</h2>
|
<!-- Card-head -->
|
||||||
|
<div class="flex items-center border-b border-line px-3 py-1.5">
|
||||||
|
<h3 class="text-[11px] font-bold tracking-[.12em] text-ink-3 uppercase">
|
||||||
|
{m.dashboard_reader_drafts_heading()}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if drafts.length === 0}
|
{#if drafts.length === 0}
|
||||||
<p class="font-sans text-sm text-ink-3">{m.dashboard_reader_drafts_empty()}</p>
|
<p class="px-3 py-3 font-sans text-sm text-ink-3">{m.dashboard_reader_drafts_empty()}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ul class="flex flex-col gap-2">
|
<ul class="flex flex-col">
|
||||||
{#each drafts as draft (draft.id)}
|
{#each drafts as draft (draft.id)}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="/geschichten/{draft.id}/edit"
|
href="/geschichten/{draft.id}/edit"
|
||||||
class="flex min-h-[44px] items-center justify-between gap-4 rounded-sm py-2 transition-colors hover:text-brand-mint focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
class="flex min-h-[44px] items-center justify-between border-b border-line/50 px-3 py-1.5 last:border-b-0 hover:bg-muted focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<span class="text-ink-1 truncate font-serif text-sm">{draft.title}</span>
|
<span class="flex min-w-0 flex-col">
|
||||||
<span class="shrink-0 font-sans text-xs text-ink-3">
|
<span class="truncate font-serif text-sm text-brand-navy">{draft.title}</span>
|
||||||
{relativeTimeDe(new Date(draft.updatedAt))}
|
<span class="text-[11px] text-ink-3">
|
||||||
|
{m.dashboard_reader_draft_meta({ relative: relativeTimeDe(new Date(draft.updatedAt)) })}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<svg
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
viewBox="0 0 7 7"
|
||||||
|
fill="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="shrink-0 text-ink-3"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1.5 1 L5.5 3.5 L1.5 6"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ describe('ReaderDraftsModule', () => {
|
|||||||
await expect.element(link2).toHaveAttribute('href', '/geschichten/g2/edit');
|
await expect.element(link2).toHaveAttribute('href', '/geschichten/g2/edit');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows heading "Meine Entwürfe"', async () => {
|
it('shows heading as h3 (not h2)', async () => {
|
||||||
render(ReaderDraftsModule, { drafts: [draft1] });
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
||||||
const heading = page.getByRole('heading', { name: /Meine Entwürfe/i });
|
const h3 = page.getByRole('heading', { level: 3 });
|
||||||
await expect.element(heading).toBeInTheDocument();
|
await expect.element(h3).toBeInTheDocument();
|
||||||
|
const h2 = page.getByRole('heading', { level: 2 });
|
||||||
|
await expect.element(h2).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows empty state when drafts is empty', async () => {
|
it('shows empty state when drafts is empty', async () => {
|
||||||
@@ -53,4 +55,45 @@ describe('ReaderDraftsModule', () => {
|
|||||||
const emptyText = page.getByText(/Keine Entwürfe/i);
|
const emptyText = page.getByText(/Keine Entwürfe/i);
|
||||||
await expect.element(emptyText).not.toBeInTheDocument();
|
await expect.element(emptyText).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('card wrapper has mint left-border classes', async () => {
|
||||||
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
||||||
|
const h3 = page.getByRole('heading', { level: 3 });
|
||||||
|
const card = ((await h3.element()) as HTMLElement).closest('div[class]');
|
||||||
|
const rootCard = card?.parentElement;
|
||||||
|
const cls = rootCard?.className ?? '';
|
||||||
|
expect(cls).toMatch(/border-l-\[3px\]/);
|
||||||
|
expect(cls).toMatch(/border-l-brand-mint/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draft-row link has min-h-[44px] touch target', async () => {
|
||||||
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
||||||
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
||||||
|
const cls = ((await link.element()) as HTMLElement).className;
|
||||||
|
expect(cls).toMatch(/min-h-\[44px\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draft title has text-brand-navy class', async () => {
|
||||||
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
||||||
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
||||||
|
const el = (await link.element()) as HTMLElement;
|
||||||
|
const titleEl = el.querySelector('[class*="text-brand-navy"]');
|
||||||
|
expect(titleEl).not.toBeNull();
|
||||||
|
expect(titleEl?.textContent?.trim()).toBe('Mein erster Entwurf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draft meta contains "Entwurf" text', async () => {
|
||||||
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
||||||
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
||||||
|
const el = (await link.element()) as HTMLElement;
|
||||||
|
expect(el.textContent).toMatch(/Entwurf/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('chevron SVG is present in each draft row', async () => {
|
||||||
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
||||||
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
||||||
|
const el = (await link.element()) as HTMLElement;
|
||||||
|
const svg = el.querySelector('svg');
|
||||||
|
expect(svg).not.toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user