Compare commits
10 Commits
4d5b8b4ead
...
b5ec4ebc0c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5ec4ebc0c | ||
|
|
10fdaf7d00 | ||
|
|
e01ef56c48 | ||
|
|
b01a9ef406 | ||
|
|
e31b73303e | ||
|
|
9d9d19ceb5 | ||
|
|
0a5c82cd0e | ||
|
|
1b063d4e4b | ||
|
|
b312878b3f | ||
|
|
90120ca8e8 |
@@ -182,11 +182,11 @@ const containerStyle = $derived(
|
||||
<style>
|
||||
@keyframes annotation-flash-anim {
|
||||
0% {
|
||||
outline: 3px solid rgba(0, 199, 177, 0.8);
|
||||
outline: 3px solid color-mix(in srgb, var(--color-turquoise) 80%, transparent);
|
||||
outline-offset: 0px;
|
||||
}
|
||||
100% {
|
||||
outline: 3px solid rgba(0, 199, 177, 0);
|
||||
outline: 3px solid color-mix(in srgb, var(--color-turquoise) 0%, transparent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ const containerStyle = $derived(
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.annotation-flash {
|
||||
animation: none;
|
||||
outline: 3px solid rgba(0, 199, 177, 0.8);
|
||||
outline: 3px solid color-mix(in srgb, var(--color-turquoise) 80%, transparent);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -64,4 +64,32 @@ describe('AnnotationLayer', () => {
|
||||
expect(clickedId).toBe('ann-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('flashAnnotationId prop', () => {
|
||||
it('should apply annotation-flash class when flashAnnotationId matches', async () => {
|
||||
render(AnnotationLayer, {
|
||||
annotations: [annotation],
|
||||
canDraw: false,
|
||||
color: '#00c7b1',
|
||||
flashAnnotationId: 'ann-1',
|
||||
onDraw: () => {}
|
||||
});
|
||||
|
||||
const el = document.querySelector('[data-testid="annotation-ann-1"]')!;
|
||||
expect(el.classList.contains('annotation-flash')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not apply annotation-flash class when flashAnnotationId does not match', async () => {
|
||||
render(AnnotationLayer, {
|
||||
annotations: [annotation],
|
||||
canDraw: false,
|
||||
color: '#00c7b1',
|
||||
flashAnnotationId: 'other-id',
|
||||
onDraw: () => {}
|
||||
});
|
||||
|
||||
const el = document.querySelector('[data-testid="annotation-ann-1"]')!;
|
||||
expect(el.classList.contains('annotation-flash')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import { getLocale } from '$lib/paraglide/runtime.js';
|
||||
|
||||
type Props = {
|
||||
mode: 'read' | 'edit';
|
||||
@@ -14,7 +15,7 @@ let { mode, hasBlocks, blockCount, lastEditedAt, onModeChange, onClose }: Props
|
||||
|
||||
const formattedDate = $derived(
|
||||
lastEditedAt
|
||||
? new Intl.DateTimeFormat('de-DE', {
|
||||
? new Intl.DateTimeFormat(getLocale(), {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
@@ -33,7 +34,7 @@ function handleReadClick() {
|
||||
class="flex h-[44px] items-center justify-between border-b border-line bg-surface px-3 font-sans"
|
||||
>
|
||||
<!-- Segmented toggle -->
|
||||
<div class="flex h-7 items-center rounded-full border border-line bg-muted p-0.5">
|
||||
<div class="flex h-9 items-center rounded-full border border-line bg-muted p-0.5 md:h-7">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="mode-read"
|
||||
@@ -79,7 +80,7 @@ function handleReadClick() {
|
||||
data-testid="panel-close"
|
||||
onclick={onClose}
|
||||
aria-label={m.transcription_panel_close()}
|
||||
class="flex h-8 w-8 items-center justify-center rounded text-ink-2 transition-colors hover:bg-muted hover:text-ink"
|
||||
class="flex h-11 w-11 items-center justify-center rounded text-ink-2 transition-colors hover:bg-muted hover:text-ink"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
|
||||
@@ -105,4 +105,47 @@ describe('TranscriptionPanelHeader', () => {
|
||||
|
||||
await expect.element(page.getByText('5 Abschnitte')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show "0 Abschnitte" when blockCount is 0', async () => {
|
||||
render(TranscriptionPanelHeader, {
|
||||
mode: 'edit',
|
||||
hasBlocks: false,
|
||||
blockCount: 0,
|
||||
lastEditedAt: null,
|
||||
onModeChange: () => {},
|
||||
onClose: () => {}
|
||||
});
|
||||
|
||||
await expect.element(page.getByText('0 Abschnitte')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have close button with 44px touch target classes', async () => {
|
||||
render(TranscriptionPanelHeader, {
|
||||
mode: 'read',
|
||||
hasBlocks: true,
|
||||
blockCount: 3,
|
||||
lastEditedAt: null,
|
||||
onModeChange: () => {},
|
||||
onClose: () => {}
|
||||
});
|
||||
|
||||
const closeBtn = document.querySelector('[data-testid="panel-close"]') as HTMLElement;
|
||||
expect(closeBtn.classList.contains('h-11')).toBe(true);
|
||||
expect(closeBtn.classList.contains('w-11')).toBe(true);
|
||||
});
|
||||
|
||||
it('should show formatted date when lastEditedAt is provided', async () => {
|
||||
render(TranscriptionPanelHeader, {
|
||||
mode: 'read',
|
||||
hasBlocks: true,
|
||||
blockCount: 3,
|
||||
lastEditedAt: '2026-04-07T10:00:00Z',
|
||||
onModeChange: () => {},
|
||||
onClose: () => {}
|
||||
});
|
||||
|
||||
const statusText = document.querySelector('.hidden.md\\:block');
|
||||
expect(statusText).not.toBeNull();
|
||||
expect(statusText!.textContent).toContain('2026');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ let sorted = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
||||
<article class="px-6 py-8">
|
||||
{#each sorted as block (block.id)}
|
||||
<div
|
||||
class="-mx-2 mb-6 cursor-pointer rounded-sm px-2 py-1 font-serif text-[16px] leading-[1.85] text-ink transition-colors hover:bg-[rgba(0,199,177,0.06)]"
|
||||
class="-mx-2 mb-6 cursor-pointer rounded-sm px-2 py-1 font-serif text-[16px] leading-[1.85] text-ink transition-colors hover:bg-turquoise/10"
|
||||
class:flash-highlight={highlightBlockId === block.id}
|
||||
data-block-id={block.id}
|
||||
onclick={() => onParagraphClick(block.annotationId)}
|
||||
@@ -38,7 +38,7 @@ let sorted = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
||||
<style>
|
||||
@keyframes flash {
|
||||
0% {
|
||||
background-color: rgba(0, 199, 177, 0.18);
|
||||
background-color: color-mix(in srgb, var(--color-turquoise) 18%, transparent);
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
@@ -52,7 +52,7 @@ let sorted = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.flash-highlight {
|
||||
animation: none;
|
||||
background-color: rgba(0, 199, 177, 0.18);
|
||||
background-color: color-mix(in srgb, var(--color-turquoise) 18%, transparent);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,6 +108,28 @@ describe('TranscriptionReadView', () => {
|
||||
expect(paragraphs[1].getAttribute('data-block-id')).toBe('b1');
|
||||
});
|
||||
|
||||
it('should apply flash-highlight class when highlightBlockId matches', async () => {
|
||||
render(TranscriptionReadView, {
|
||||
blocks: [blocks[0]],
|
||||
onParagraphClick: () => {},
|
||||
highlightBlockId: 'b1'
|
||||
});
|
||||
|
||||
const el = document.querySelector('[data-block-id="b1"]')!;
|
||||
expect(el.classList.contains('flash-highlight')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not apply flash-highlight class when highlightBlockId does not match', async () => {
|
||||
render(TranscriptionReadView, {
|
||||
blocks: [blocks[0]],
|
||||
onParagraphClick: () => {},
|
||||
highlightBlockId: 'other-id'
|
||||
});
|
||||
|
||||
const el = document.querySelector('[data-block-id="b1"]')!;
|
||||
expect(el.classList.contains('flash-highlight')).toBe(false);
|
||||
});
|
||||
|
||||
it('should render empty state when no blocks', async () => {
|
||||
render(TranscriptionReadView, {
|
||||
blocks: [],
|
||||
|
||||
@@ -299,7 +299,7 @@ onMount(() => {
|
||||
hasBlocks={hasBlocks}
|
||||
blockCount={transcriptionBlocks.length}
|
||||
lastEditedAt={lastEditedAt}
|
||||
onModeChange={(m) => (panelMode = m)}
|
||||
onModeChange={(newMode) => (panelMode = newMode)}
|
||||
onClose={() => (transcribeMode = false)}
|
||||
/>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
|
||||
Reference in New Issue
Block a user