Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
4.6 kB · 128 lines
Svelte
at icy/tmrrpn
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129<script module lang="ts"> import { tv, type VariantProps } from "tailwind-variants";
export const notificationItem = tv({ slots: { // the row subscribes to NotificationList's columns, so every row shares one // lead column width and the descriptions line up down the whole list. it // only lays out correctly inside that grid. root: "group/notification col-span-full grid grid-cols-subgrid items-center gap-y-1 px-2 py-4 no-underline transition-colors hover:border-border-disabled hover:no-underline md:px-4", icon: "row-start-1 size-3.5 shrink-0", header: "row-start-1 flex min-w-0 items-center gap-1.5 overflow-hidden typography-paragraph-small", phrase: "truncate text-foreground-muted", target: "text-foreground-default", meta: "relative row-start-1 flex items-center justify-end", time: "typography-paragraph-small whitespace-nowrap text-foreground-subtle", toggle: "absolute inset-0 hidden items-center justify-end text-foreground-placeholder hover:cursor-pointer hover:text-foreground-muted md:group-hover/notification:flex", number: "col-start-1 row-start-2 text-left typography-paragraph-small text-foreground-muted", title: "col-start-2 row-start-2 truncate typography-paragraph-small text-foreground-muted" }, variants: { read: { true: { root: "bg-background-default hover:bg-background-subtle" }, false: { root: "bg-background-info-subtle hover:bg-background-info-subtle-hover dark:hover:bg-background-subtle" } } }, defaultVariants: { read: true } });
export type NotificationItemVariants = VariantProps<typeof notificationItem>;</script>
<script lang="ts"> import Mail from "$icon/mail"; import MailCheck from "$icon/mail-check"; import ColorBall from "$lib/components/ui/ColorBall.svelte"; import TimeAgo from "$lib/components/ui/TimeAgo.svelte"; import User from "$lib/components/ui/User.svelte"; import { notificationHref, notificationIcon, notificationPhrase, notificationTarget, notificationTone } from "./notification"; import type { NotificationSummary } from "./types";
interface Props { notification: NotificationSummary; onToggleRead?: (uri: string, read: boolean) => void; }
let { notification, onToggleRead }: Props = $props();
const style = $derived(notificationItem({ read: notification.read })); const Icon = $derived(notificationIcon(notification.type)); // resolve() would mangle the raw at:// uri here (collapses "//"), so it's used as-is const href = $derived(notificationHref(notification)); const target = $derived(notificationTarget(notification));
// the row is one big link, so the read toggle inside it must not navigate const toggle = (event: MouseEvent) => { event.preventDefault(); event.stopPropagation(); onToggleRead?.(notification.uri, !notification.read); };
// client-side navigation leaves a popover open, so a row inside the topbar // dropdown has to dismiss it on the way out const open = (event: MouseEvent & { currentTarget: HTMLAnchorElement }) => { onToggleRead?.(notification.uri, true); const popover = event.currentTarget.closest("[popover]"); if (popover instanceof HTMLElement) popover.hidePopover(); };</script>
<a {href} class={style.root()} onclick={open}> <Icon class={style.icon({ class: notificationTone(notification.type) })} aria-hidden="true" />
<div class={style.header()}> <ColorBall color="var(--color-background-info-emphasis)" class={notification.read ? "hidden" : undefined} /> <User handle={notification.actor.handle} did={notification.actor.did} size="small" class="shrink-0 text-foreground-default" /> <span class={style.phrase()}> {notificationPhrase(notification)} {#if target} <span class={style.target()}>{target}</span> {/if} </span> </div>
<div class={style.meta()}> <TimeAgo value={notification.createdAt} class={style.time({ class: "md:group-hover/notification:invisible" })} /> {#if onToggleRead} <button type="button" class={style.toggle()} title={notification.read ? "Mark as unread" : "Mark as read"} aria-label={notification.read ? "Mark as unread" : "Mark as read"} onclick={toggle} > {#if notification.read} <Mail class="size-4" aria-hidden="true" /> {:else} <MailCheck class="size-4" aria-hidden="true" /> {/if} </button> {/if} </div>
{#if notification.subject} <span class={style.number()}>#{notification.subject.number}</span> <div class={style.title()}>{notification.subject.title}</div> {/if}</a>