fix(geschichte-ui): a11y and visual round-3 batch

- JourneyItemCard: 'Brief öffnen' back to a >=44px touch target with the
  height regression spec restored
- GeschichteListRow: REISE badges text-[10px] -> text-xs; drop the
  hardcoded aria-label and the mobile badge's aria-hidden so phone screen
  readers learn a row is a Lesereise; mobile avatar initials -> color dot
- detail page: badge text-xs, metabar Edit/Delete h-9 -> h-11, avatar
  color keyed by name to match the list
- JourneyReader: dead border-subtle class -> border-line-2
- DocumentPickerDropdown: aria-controls only while the listbox exists
- JourneyAddBar: aria-expanded/aria-controls on both toggles + focus
  hand-off into the revealed picker input / interlude textarea
- GeschichteSidebar: section h2s hidden below sm (summary already shows
  the label there)
- JourneyCreate: bg-brand-navy -> semantic bg-primary/text-primary-fg;
  title maxlength=255
- JourneyItemRow interlude: neutral frame border + left accent only,
  token utilities instead of arbitrary var() syntax and inline style

Review round 3: Leonie (1-8 + round-1 leftovers), Elicit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-11 08:30:59 +02:00
parent 836c9594d4
commit 66e9309d8a
10 changed files with 73 additions and 39 deletions

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { tick } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
import DocumentPickerDropdown from '$lib/document/DocumentPickerDropdown.svelte';
import type { DocumentOption } from '$lib/document/documentTypeahead';
@@ -14,9 +15,29 @@ let { alreadyAddedIds = new Set(), onAddDocument, onAddInterlude }: Props = $pro
let showPicker = $state(false);
let showInterludeForm = $state(false);
let interludeDraft = $state('');
let rootEl: HTMLElement | null = $state(null);
const canConfirmInterlude = $derived(interludeDraft.trim().length > 0);
async function togglePicker() {
showPicker = !showPicker;
showInterludeForm = false;
if (showPicker) {
// Keyboard users need a perceivable result of activating the toggle.
await tick();
rootEl?.querySelector<HTMLInputElement>('#journey-add-picker input')?.focus();
}
}
async function toggleInterludeForm() {
showInterludeForm = !showInterludeForm;
showPicker = false;
if (showInterludeForm) {
await tick();
rootEl?.querySelector<HTMLTextAreaElement>('#journey-add-interlude textarea')?.focus();
}
}
function handleDocumentSelect(doc: DocumentOption) {
showPicker = false;
onAddDocument(doc);
@@ -36,25 +57,23 @@ function handleInterludeCancel() {
}
</script>
<div class="flex flex-col gap-3">
<div bind:this={rootEl} class="flex flex-col gap-3">
<div class="flex flex-wrap gap-2">
<button
type="button"
data-add-document
onclick={() => {
showPicker = !showPicker;
showInterludeForm = false;
}}
onclick={togglePicker}
aria-expanded={showPicker}
aria-controls={showPicker ? 'journey-add-picker' : undefined}
class="inline-flex h-11 items-center rounded border border-line bg-surface px-4 font-sans text-sm font-medium text-ink hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
+ {m.journey_add_document()}
</button>
<button
type="button"
onclick={() => {
showInterludeForm = !showInterludeForm;
showPicker = false;
}}
onclick={toggleInterludeForm}
aria-expanded={showInterludeForm}
aria-controls={showInterludeForm ? 'journey-add-interlude' : undefined}
class="inline-flex h-11 items-center rounded border border-line bg-surface px-4 font-sans text-sm font-medium text-ink hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
+ {m.journey_add_interlude()}
@@ -62,15 +81,17 @@ function handleInterludeCancel() {
</div>
{#if showPicker}
<DocumentPickerDropdown
alreadyAddedIds={alreadyAddedIds}
onSelect={handleDocumentSelect}
placeholder={m.journey_add_document()}
/>
<div id="journey-add-picker">
<DocumentPickerDropdown
alreadyAddedIds={alreadyAddedIds}
onSelect={handleDocumentSelect}
placeholder={m.journey_add_document()}
/>
</div>
{/if}
{#if showInterludeForm}
<div class="flex flex-col gap-2">
<div id="journey-add-interlude" class="flex flex-col gap-2">
<textarea
bind:value={interludeDraft}
placeholder={m.journey_interlude_placeholder()}