clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

main
lofi / package / preact / Notices.tsx
Notices.tsx 2.3K
1import { Fragment, type VNode } from "preact";2// Package-owned optional notices surface.3import { type NoticeEntry, useNotices } from "./use-notices.ts";45/** Props for the built-in {@link Notices} surface. */6export type NoticesProps = {7 /** Accessible label for the region; defaults to "Notifications". */8 label?: string;9 /** Renders one notice; defaults to the built-in row with a dismiss button. */10 children?: (notice: NoticeEntry, dismiss: () => void) => VNode;11};1213/**14 * The built-in durable-notice surface: renders the live `s.notice` queue as an15 * ARIA live region so a message enqueued when a write settled — success or the16 * "a rejected write still flashed success" case — is announced and dismissable.17 * Optional and entirely public: it is `useNotices` plus default markup, so an18 * app can drop it and render its own (a toast stack, a banner) over the same19 * hook.20 *21 * @example22 * ```tsx23 * import { Notices } from "@nzip/lofi/preact";24 *25 * export function AppChrome() {26 * return <Notices />;27 * }28 * ```29 *30 * @param props Optional region label and a custom per-notice renderer.31 * @returns The always-mounted live notices region.32 */33export function Notices({ label = "Notifications", children }: NoticesProps): VNode {34 const { notices, dismiss } = useNotices();35 // The live region stays mounted even when empty: a screen reader only36 // announces mutations to a region already in the DOM, so inserting the37 // region together with its first notice would drop that first announcement —38 // exactly the message this surface exists to deliver.39 return (40 <section41 class="lofi-notices"42 aria-label={label}43 aria-live="polite"44 >45 {notices.map((notice) =>46 children47 ? <Fragment key={notice.id}>{children(notice, () => dismiss(notice.id))}</Fragment>48 : (49 <div key={notice.id} class="lofi-notice" data-tone={notice.tone} role="status">50 <p class="lofi-notice-message">{notice.message}</p>51 <button52 type="button"53 class="lofi-notice-dismiss"54 aria-label="Dismiss notification"55 onClick={() => dismiss(notice.id)}56 >57 Dismiss58 </button>59 </div>60 )61 )}62 </section>63 );64}