/chronik → /aktivitaeten; heading updated in all three locales. Component folder (lib/components/chronik/) stays unchanged — internal implementation detail, not user-facing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
139 lines
4.0 KiB
Svelte
139 lines
4.0 KiB
Svelte
<script lang="ts">
|
|
import { m } from '$lib/paraglide/messages.js';
|
|
import { relativeTime } from '$lib/utils/time';
|
|
import type { NotificationItem } from '$lib/stores/notifications.svelte';
|
|
|
|
type Props = {
|
|
notifications: NotificationItem[];
|
|
onMarkRead: (notification: NotificationItem) => void;
|
|
onMarkAllRead: () => void;
|
|
onClose: () => void;
|
|
};
|
|
|
|
let { notifications, onMarkRead, onMarkAllRead, onClose }: Props = $props();
|
|
</script>
|
|
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={m.notification_bell_label()}
|
|
class="absolute right-0 z-50 mt-2 w-80 overflow-hidden rounded-sm border border-line bg-surface shadow-lg"
|
|
>
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between border-b border-line px-4 py-3">
|
|
<span class="text-xs font-bold tracking-widest text-ink-2 uppercase">
|
|
{m.notification_bell_label()}
|
|
</span>
|
|
{#if notifications.length > 0}
|
|
<button
|
|
type="button"
|
|
onclick={onMarkAllRead}
|
|
class="text-xs font-medium text-ink-3 transition-colors hover:text-ink"
|
|
>
|
|
{m.notification_mark_all_read()}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Notification list -->
|
|
{#if notifications.length === 0}
|
|
<!-- Empty state -->
|
|
<div class="flex flex-col items-center gap-2 px-4 py-8 text-center text-sm text-ink-3">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-8 w-8 text-ink-3 opacity-40"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="1.5"
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
|
|
/>
|
|
</svg>
|
|
<span>{m.notification_empty()}</span>
|
|
</div>
|
|
{:else}
|
|
<ul role="list" class="max-h-[24rem] overflow-y-auto">
|
|
{#each notifications as notification (notification.id)}
|
|
<li>
|
|
<button
|
|
type="button"
|
|
onclick={() => onMarkRead(notification)}
|
|
class="flex w-full cursor-pointer items-start gap-3 border-b border-line px-4 py-3 text-left last:border-b-0 hover:bg-canvas
|
|
{!notification.read ? 'bg-accent-bg/20' : ''}"
|
|
>
|
|
<!-- Type icon -->
|
|
<span class="mt-0.5 shrink-0 text-ink-3" aria-hidden="true">
|
|
{#if notification.type === 'REPLY'}
|
|
<!-- Reply icon -->
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
|
/>
|
|
</svg>
|
|
{:else}
|
|
<!-- Mention icon -->
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
</span>
|
|
|
|
<!-- Text + time -->
|
|
<div class="min-w-0 flex-1">
|
|
<p class="text-sm leading-snug text-ink">
|
|
{notification.type === 'REPLY'
|
|
? m.notification_type_reply({ actor: notification.actorName })
|
|
: m.notification_type_mention({ actor: notification.actorName })}
|
|
</p>
|
|
<p class="mt-1 text-xs text-ink-3">{relativeTime(notification.createdAt)}</p>
|
|
</div>
|
|
|
|
<!-- Unread dot -->
|
|
{#if !notification.read}
|
|
<span
|
|
class="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-primary"
|
|
aria-label={m.notification_unread()}
|
|
></span>
|
|
{/if}
|
|
</button>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
{/if}
|
|
|
|
<div class="border-t border-line px-4 py-2">
|
|
<a
|
|
href="/aktivitaeten"
|
|
onclick={onClose}
|
|
class="text-xs font-medium text-ink-2 transition-colors hover:text-ink"
|
|
>
|
|
{m.chronik_view_all()}
|
|
</a>
|
|
</div>
|
|
</div>
|