Notification list
helixui ships a toast() API for one-off toasts. This block is the
persistent variant — a tray of notifications visible until the user
dismisses or they expire.
'use client';
import { useEffect, useState } from 'react';import { Card, IconButton, Stack, Text } from '@helixui/core';import { Close } from '@helixui/icons';
export interface Notification { id: string; title: string; description?: string; tone?: 'info' | 'success' | 'warning' | 'danger'; expiresAt?: number; // epoch ms; auto-dismiss}
export function NotificationList({ items, onDismiss,}: { items: Notification[]; onDismiss: (id: string) => void;}) { // Auto-dismiss timer (no setInterval — one timer per item). useEffect(() => { const timers = items .filter((n) => n.expiresAt) .map((n) => setTimeout(() => onDismiss(n.id), Math.max(0, n.expiresAt! - Date.now()))); return () => { timers.forEach(clearTimeout); }; }, [items, onDismiss]);
return ( <div role="region" aria-label="Notifications" aria-live="polite"> <Stack gap={2}> {items.map((n) => ( <Card key={n.id} variant="outlined" style={{ padding: 'var(--helixui-space-3) var(--helixui-space-4)' }}> <Stack direction="row" align="center" justify="between" gap={3}> <Stack gap={0} style={{ flex: 1 }}> <Text weight="semibold">{n.title}</Text> {n.description ? <Text size="sm" tone="muted">{n.description}</Text> : null} </Stack> <IconButton aria-label="Dismiss" variant="ghost" size="sm" onClick={() => onDismiss(n.id)}> <Close /> </IconButton> </Stack> </Card> ))} </Stack> </div> );}Usage
const [items, setItems] = useState<Notification[]>([]);
<NotificationList items={items} onDismiss={(id) => setItems((xs) => xs.filter((x) => x.id !== id))} />The aria-live="polite" announcement makes the list screen-reader
friendly. For instant feedback (form-submit success), use the global
toast() helper instead; this block is for persistent notifications.