- 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>
127 lines
4.0 KiB
Svelte
127 lines
4.0 KiB
Svelte
<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';
|
|
|
|
interface Props {
|
|
alreadyAddedIds?: Set<string>;
|
|
onAddDocument: (doc: DocumentOption) => void;
|
|
onAddInterlude: (text: string) => void;
|
|
}
|
|
|
|
let { alreadyAddedIds = new Set(), onAddDocument, onAddInterlude }: Props = $props();
|
|
|
|
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);
|
|
}
|
|
|
|
function handleInterludeConfirm() {
|
|
if (!canConfirmInterlude) return;
|
|
const text = interludeDraft.trim();
|
|
interludeDraft = '';
|
|
showInterludeForm = false;
|
|
onAddInterlude(text);
|
|
}
|
|
|
|
function handleInterludeCancel() {
|
|
interludeDraft = '';
|
|
showInterludeForm = false;
|
|
}
|
|
</script>
|
|
|
|
<div bind:this={rootEl} class="flex flex-col gap-3">
|
|
<div class="flex flex-wrap gap-2">
|
|
<button
|
|
type="button"
|
|
data-add-document
|
|
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={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()}
|
|
</button>
|
|
</div>
|
|
|
|
{#if showPicker}
|
|
<div id="journey-add-picker">
|
|
<DocumentPickerDropdown
|
|
alreadyAddedIds={alreadyAddedIds}
|
|
onSelect={handleDocumentSelect}
|
|
placeholder={m.journey_add_document()}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if showInterludeForm}
|
|
<div id="journey-add-interlude" class="flex flex-col gap-2">
|
|
<textarea
|
|
bind:value={interludeDraft}
|
|
placeholder={m.journey_interlude_placeholder()}
|
|
rows={3}
|
|
maxlength={2000}
|
|
class="block w-full resize-y rounded border border-line bg-surface px-3 py-2 font-sans text-sm text-ink placeholder:text-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
|
></textarea>
|
|
<div class="flex gap-2">
|
|
<button
|
|
type="button"
|
|
onclick={handleInterludeConfirm}
|
|
disabled={!canConfirmInterlude}
|
|
class={[
|
|
'inline-flex h-11 items-center rounded px-4 font-sans text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring',
|
|
canConfirmInterlude
|
|
? 'bg-primary text-primary-fg hover:opacity-90'
|
|
: 'cursor-not-allowed bg-primary/40 text-primary-fg/60'
|
|
].join(' ')}
|
|
>
|
|
{m.journey_add_interlude_confirm()}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onclick={handleInterludeCancel}
|
|
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_remove_confirm_cancel()}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|