Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
4.1 kB · 141 lines
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142<script lang="ts"> import { untrack } from "svelte"; import { resolve } from "$app/paths"; import ArrowRight from "$icon/arrow-right"; import Bell from "$icon/bell"; import Button from "$lib/components/ui/Button.svelte"; import Dropdown from "$lib/components/ui/Dropdown.svelte"; import { createLoad } from "$lib/action.svelte"; import { createFixtureNotifications, getNotifications, type Notifications } from "$lib/notifications.svelte"; import { notificationCategory } from "$lib/components/notifications/notification"; import NotificationList from "$lib/components/notifications/NotificationList.svelte"; import NotificationToolbar from "$lib/components/notifications/NotificationToolbar.svelte"; import type { CategoryFilter, NotificationSummary, ReadFilter } from "$lib/components/notifications/types";
interface Props { /** optional fixture override for storybook; omitted → loads from deliberi */ notifications?: NotificationSummary[]; }
let { notifications: fixtures }: Props = $props();
const shared: Notifications = untrack(() => fixtures ? createFixtureNotifications(fixtures) : (getNotifications() ?? createFixtureNotifications([])) );
const PREVIEW_LIMIT = 5;
let readFilter = $state<ReadFilter>("unread"); let categoryFilter = $state<CategoryFilter>("all");
const previewLoad = createLoad(() => shared.list({ read: readFilter, limit: PREVIEW_LIMIT }));
const loaded = $derived(previewLoad.data ?? []); const all = $derived(shared.overlay(loaded)); const badge = $derived(shared.unread > 99 ? "99+" : String(shared.unread));
const unreadOnLoad = $derived(new Set(loaded.filter((n) => !n.read).map((n) => n.uri)));
const preview = $derived( all .filter((n) => readFilter !== "unread" || unreadOnLoad.has(n.uri)) .filter( (n) => categoryFilter === "all" || notificationCategory(n.type) === categoryFilter ) .slice(0, PREVIEW_LIMIT) );
const dismiss = (event: MouseEvent & { currentTarget: HTMLAnchorElement }) => { const popover = event.currentTarget.closest("[popover]"); if (popover instanceof HTMLElement) popover.hidePopover(); };
const viewAll = $derived( resolve( (categoryFilter === "all" ? `/notifications?read=${readFilter}` : `/notifications?read=${readFilter}&category=${categoryFilter}`) as "/" ) );</script>
{#snippet count()} {#if shared.unread > 0} <span class="pointer-events-none absolute top-0.5 right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-background-info-emphasis px-1 typography-paragraph-mini text-foreground-on-emphasis" aria-hidden="true" > {badge} </span> {/if}{/snippet}
<div class="relative flex md:hidden"> <Button variant="ghost" size="small" href={resolve("/notifications")} icon={Bell} aria-label="Notifications, {shared.unread} unread" /> {@render count()}</div>
<div class="relative hidden md:flex"> <Dropdown id="notifications-menu" group="topbar" variant="button" buttonVariant="ghost" size="small" align="right" label="Notifications, {shared.unread} unread" menuClass="w-[36rem]" > {#snippet trigger()} <Bell class="size-4" aria-hidden="true" /> {/snippet}
<div class="p-2"> <NotificationToolbar {readFilter} {categoryFilter} hasNotifications={preview.length > 0} preferences={false} onReadFilter={(filter) => (readFilter = filter)} onCategoryFilter={(filter) => (categoryFilter = filter)} onMarkAllRead={shared.markAllRead} /> </div>
<NotificationList loading={previewLoad.loading && !previewLoad.data} notifications={preview} {readFilter} border={false} grouped={false} onToggleRead={shared.setRead} />
<a href={viewAll} onclick={dismiss} class="flex items-center justify-end gap-1 p-2 typography-paragraph-small text-foreground-muted no-underline transition-colors hover:bg-background-subtle hover:text-foreground-default hover:no-underline md:px-4" > View all <ArrowRight class="size-4" aria-hidden="true" /> </a> </Dropdown>
{@render count()}</div>