feat(bulk-edit): add canWrite-gated row checkboxes on /documents and /enrich

Each row in the document search list and the enrichment queue gets a
WCAG-compliant (44px touch target) checkbox bound to bulkSelectionStore.
Checkbox click does not trigger the row's stretched-link navigation —
it sits inside the z-10 content sibling, the link is in the z-0 sibling,
so click events do not bubble between them.

Refs #225

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-25 15:03:59 +02:00
parent 25446c9a5c
commit 27e3d290e7
5 changed files with 81 additions and 6 deletions

View File

@@ -119,7 +119,7 @@ function groupByReceiver(docItems: DocumentSearchItem[]) {
</div>
<ul class="divide-y divide-line">
{#each group.items as item (group.label + '-' + item.document.id)}
<DocumentRow item={item} />
<DocumentRow item={item} canWrite={canWrite} />
{/each}
</ul>
</div>

View File

@@ -19,5 +19,5 @@ export async function load({
const documents = result.response.ok ? (result.data ?? []) : [];
return { documents };
return { documents, canWrite };
}

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import BackButton from '$lib/components/BackButton.svelte';
import { bulkSelectionStore } from '$lib/stores/bulkSelection.svelte';
let { data } = $props();
const documents = $derived(data.documents);
const count = $derived(documents.length);
const canWrite = $derived(data.canWrite);
</script>
<div class="mx-auto max-w-4xl px-4 py-10">
@@ -61,8 +63,24 @@ const count = $derived(documents.length);
<div class="border border-line bg-surface shadow-sm">
<ul class="divide-y divide-line-2">
{#each documents as doc (doc.id)}
<li class="group transition-colors duration-200 hover:bg-muted">
<a href="/enrich/{doc.id}" class="flex items-center justify-between p-6">
<li class="group relative transition-colors duration-200 hover:bg-muted">
<a href="/enrich/{doc.id}" class="absolute inset-0 z-0 block" aria-label={doc.title}
></a>
<div class="pointer-events-none relative z-10 flex items-center justify-between p-6">
{#if canWrite}
<label
class="pointer-events-auto mr-4 flex min-h-[44px] min-w-[44px] flex-shrink-0 cursor-pointer items-center"
data-testid="bulk-select-checkbox"
>
<input
type="checkbox"
class="h-5 w-5 cursor-pointer accent-brand-navy"
checked={bulkSelectionStore.has(doc.id)}
onchange={() => bulkSelectionStore.toggle(doc.id)}
aria-label={m.bulk_edit_select_document({ title: doc.title })}
/>
</label>
{/if}
<div class="min-w-0 flex-1">
<p class="font-serif text-lg font-medium text-ink group-hover:underline">
{doc.title}
@@ -74,7 +92,7 @@ const count = $derived(documents.length);
aria-hidden="true"
class="ml-4 h-5 w-5 shrink-0 opacity-30 transition-opacity group-hover:opacity-70"
/>
</a>
</div>
</li>
{/each}
</ul>