Files
familienarchiv/frontend/src/lib/components/DocumentTopBar.svelte
Marcel 358131ca34 feat(ui): replace DocumentTopBar with responsive orchestrator (issue #173)
- Accent bar, h-12/h-14 responsive height, 44×44px back link touch target
- PersonChipRow with sender→receivers chips, overflow pill button at ≥768px
- DocumentStatusChip dot-only at ≥768px
- Edit/annotate/download actions with annotateMode wiring
- AnnotateHintStrip below main row when annotateMode active
- status field added to Doc type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 23:11:11 +02:00

172 lines
5.4 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import { formatDate } from '$lib/utils/personFormat';
import PersonChipRow from './PersonChipRow.svelte';
import DocumentStatusChip from './DocumentStatusChip.svelte';
import AnnotateHintStrip from './AnnotateHintStrip.svelte';
import OverflowPillButton from './OverflowPillButton.svelte';
type DocumentStatus = 'PLACEHOLDER' | 'UPLOADED' | 'TRANSCRIBED' | 'REVIEWED' | 'ARCHIVED';
type Person = { id: string; firstName: string; lastName: string };
type Doc = {
id: string;
title?: string | null;
originalFilename?: string | null;
documentDate?: string | null;
status: DocumentStatus;
sender?: Person | null;
receivers?: Person[] | null;
filePath?: string | null;
contentType?: string | null;
};
type Props = {
doc: Doc;
canWrite: boolean;
canAnnotate: boolean;
fileUrl: string;
annotateMode: boolean;
};
let { doc, canWrite, canAnnotate, fileUrl, annotateMode = $bindable() }: Props = $props();
const isPdf = $derived(!!doc.filePath && doc.contentType?.startsWith('application/pdf'));
const receivers = $derived(doc.receivers ?? []);
const extraCount = $derived(Math.max(0, receivers.length - 2));
const overflowPersons = $derived(receivers.slice(2));
const shortDate = $derived(doc.documentDate ? formatDate(doc.documentDate, 'short') : null);
const longDate = $derived(doc.documentDate ? formatDate(doc.documentDate, 'long') : null);
</script>
<div data-topbar class="border-b border-line bg-surface shadow-sm">
<!-- Main row -->
<div class="flex h-12 shrink-0 items-center xs:h-14">
<!-- Accent bar -->
<div class="h-full w-[3px] shrink-0 bg-primary"></div>
<!-- Back link — 44×44px touch target -->
<a
href="/"
aria-label="Zurück zur Dokumentenliste"
class="group -ml-0.5 flex h-11 w-11 shrink-0 items-center justify-center rounded-full transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Left-MD.svg"
alt=""
aria-hidden="true"
class="h-5 w-5"
/>
</a>
<!-- Divider -->
<div class="mx-2 h-6 w-px shrink-0 bg-line"></div>
<!-- Title + meta -->
<div class="min-w-0 flex-1 overflow-hidden">
<h1
class="truncate font-serif text-[11px] leading-tight text-ink lg:text-[13px]"
title={doc.title ?? doc.originalFilename ?? ''}
>
{doc.title || doc.originalFilename}
</h1>
{#if shortDate}
<p class="font-sans text-[10px] text-ink-2">
<span class="lg:hidden">{shortDate}</span>
<span class="hidden lg:inline">{longDate}</span>
</p>
{/if}
</div>
<!-- Chip row (≥375px) -->
<div class="mx-3 min-w-0 shrink-0">
<PersonChipRow
sender={doc.sender}
receivers={receivers}
abbreviated={true}
extraCount={extraCount}
/>
</div>
<!-- Overflow pill button (desktop) + status dot -->
{#if extraCount > 0}
<OverflowPillButton extraCount={extraCount} persons={overflowPersons} />
{/if}
<DocumentStatusChip status={doc.status} />
<!-- Action buttons -->
<div class="ml-2 flex shrink-0 items-center gap-1.5 pr-3 font-sans">
{#if canAnnotate && isPdf && !annotateMode}
<button
onclick={() => (annotateMode = true)}
aria-label={m.doc_panel_annotate()}
aria-pressed={false}
class="hidden items-center gap-1.5 rounded border border-primary px-3 py-1.5 font-sans text-xs font-medium text-ink transition hover:bg-primary hover:text-primary-fg focus-visible:ring-2 focus-visible:ring-primary md:flex"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Note/Note-Add-MD.svg"
alt=""
aria-hidden="true"
class="h-4 w-4"
/>
<span>{m.doc_panel_annotate()}</span>
</button>
{/if}
{#if canAnnotate && isPdf && annotateMode}
<button
onclick={() => (annotateMode = false)}
aria-label={m.doc_panel_annotate_stop()}
aria-pressed={true}
class="hidden items-center gap-1.5 rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg transition focus-visible:ring-2 focus-visible:ring-primary md:flex"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Note/Note-Add-MD.svg"
alt=""
aria-hidden="true"
class="h-4 w-4 invert"
/>
<span>{m.doc_panel_annotate_stop()}</span>
</button>
{/if}
{#if canWrite && !annotateMode}
<a
href="/documents/{doc.id}/edit"
aria-label={m.btn_edit()}
class="flex items-center gap-1.5 rounded border border-primary bg-transparent px-3 py-1.5 text-xs font-medium text-ink transition hover:bg-primary hover:text-primary-fg focus-visible:ring-2 focus-visible:ring-primary"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Edit-Content-MD.svg"
alt=""
aria-hidden="true"
class="h-4 w-4"
/>
<span class="hidden sm:inline">{m.btn_edit()}</span>
</a>
{/if}
{#if doc.filePath && !annotateMode}
<a
href={fileUrl}
download={doc.originalFilename}
class="hidden rounded border border-transparent bg-muted p-1.5 text-ink transition hover:bg-accent focus-visible:ring-2 focus-visible:ring-primary md:block"
title={m.doc_download_title()}
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Download-MD.svg"
alt=""
aria-hidden="true"
class="h-5 w-5"
/>
</a>
{/if}
</div>
</div>
<!-- Hint strip — only when annotateMode, only at ≥768px -->
<AnnotateHintStrip annotateMode={annotateMode} />
</div>