{"version":3,"file":"NotificationCenter.cjs","names":[],"sources":["../../../src/components/NotificationCenter/NotificationCenter.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — an inbox has four actions the app\n * must implement (onSelect, onMarkRead, onMarkAllRead, onDismiss) over one list,\n * plus the presentation it needs to render a readable feed: renderIcon, locale, now\n * for relative timestamps, emptyState and title.\n */\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\nimport { relativeTime, type RelativeTimeLocale } from \"@/utils/relative-time\";\n\nimport { Badge } from \"../Badge\";\nimport { EmptyState } from \"../EmptyState\";\nimport type { NotificationItem } from \"./use-notification-inbox\";\nimport styles from \"./NotificationCenter.module.css\";\n\n/**\n * DOM attributes this component redefines.\n *\n * `title` becomes a rendered heading node instead of a tooltip string, and\n * `onSelect` becomes \"an entry was activated\" instead of the DOM text-selection\n * event. Both would be type errors if merged, and silently confusing if allowed\n * through.\n */\ntype OverriddenDomProps = \"children\" | \"title\" | \"onSelect\";\n\nexport interface NotificationCenterProps extends Omit<\n    HTMLAttributes<HTMLDivElement>,\n    OverriddenDomProps\n> {\n    /** Entries to show. Rendered in the order given — `useNotificationInbox` sorts newest first. */\n    items: readonly NotificationItem[];\n    /**\n     * Panel heading. Pass `null` to render the list with no header.\n     *\n     * Widened from the DOM `title` attribute (a `string`) on purpose: this is a\n     * rendered heading, not a tooltip, so it takes a node.\n     */\n    title?: ReactNode;\n    /** Activate an entry: click, `Enter` or `Space`. Also marks it read when `onMarkRead` is set. */\n    onSelect?: (item: NotificationItem) => void;\n    /** Mark one entry as read. Enables the per-item read control. */\n    onMarkRead?: (id: string) => void;\n    /** Mark every entry as read. Enables the header action. */\n    onMarkAllRead?: () => void;\n    /** Remove one entry. Enables the per-item dismiss control. */\n    onDismiss?: (id: string) => void;\n    /** Icon rendered at the leading edge of each row — an `<Icon name={…} />`, say. */\n    renderIcon?: (item: NotificationItem) => ReactNode;\n    /** Locale for the relative timestamps. Default `\"pt-BR\"`. */\n    locale?: RelativeTimeLocale;\n    /** Shown when `items` is empty. */\n    emptyState?: ReactNode;\n    /** Reference point for the relative timestamps. Default: now, at render time. */\n    now?: number;\n}\n\n/**\n * Inbox panel for notifications the app has received.\n *\n * Deliberately presentational and controlled: it takes a list and emits intent.\n * Where the list comes from — an API, a service-worker push, `localStorage` — and\n * where read state is written are app decisions, and a component that assumed any\n * of them would be wrong for most apps. `useNotificationInbox` is the companion\n * that holds the state, including the service-worker bridge.\n *\n * It is also just the panel, not a popover: apps mount it inside their own\n * `Popover`, `Drawer` or route. That keeps one component from owning both the\n * inbox and a positioning strategy.\n *\n * @example\n * const inbox = useNotificationInbox();\n *\n * <Popover trigger={<Button>Notificações ({inbox.unreadCount})</Button>}>\n *     <NotificationCenter\n *         items={inbox.items}\n *         onMarkRead={inbox.markRead}\n *         onMarkAllRead={inbox.markAllRead}\n *         onDismiss={inbox.remove}\n *         onSelect={(item) => item.url && navigate(item.url)}\n *     />\n * </Popover>\n */\nexport function NotificationCenter({\n    items,\n    title = \"Notificações\",\n    onSelect,\n    onMarkRead,\n    onMarkAllRead,\n    onDismiss,\n    renderIcon,\n    locale = \"pt-BR\",\n    emptyState,\n    now,\n    className,\n    ...rest\n}: NotificationCenterProps) {\n    const unreadCount = items.filter((item) => !item.read).length;\n    const reference = now ?? new Date().getTime();\n\n    /**\n     * Activate an entry.\n     *\n     * Reading a notification is implied by opening it, so activation marks it read\n     * as well — otherwise every app would have to remember to call both, and the\n     * unread badge would keep counting something the user already saw.\n     */\n    const activate = (item: NotificationItem): void => {\n        if (!item.read) onMarkRead?.(item.id);\n        onSelect?.(item);\n    };\n\n    const interactive = Boolean(onSelect || onMarkRead);\n\n    return (\n        <div className={cn(styles.panel, className)} {...rest}>\n            {(title || (onMarkAllRead && unreadCount > 0)) && (\n                <header className={styles.header}>\n                    {title ? (\n                        <h2 className={styles.title}>\n                            {title}\n                            {unreadCount > 0 && (\n                                <Badge variant=\"primary\" aria-label={`${unreadCount} não lidas`}>\n                                    {unreadCount}\n                                </Badge>\n                            )}\n                        </h2>\n                    ) : (\n                        <span />\n                    )}\n                    {onMarkAllRead && unreadCount > 0 && (\n                        <button\n                            type=\"button\"\n                            className={styles.headerAction}\n                            onClick={onMarkAllRead}\n                        >\n                            Marcar todas como lidas\n                        </button>\n                    )}\n                </header>\n            )}\n\n            {items.length === 0 ? (\n                (emptyState ?? <EmptyState title=\"Nenhuma notificação\" />)\n            ) : (\n                <ul className={styles.list}>\n                    {items.map((item) => (\n                        <li\n                            key={item.id}\n                            className={cn(styles.item, !item.read && styles.unread)}\n                            aria-current={!item.read ? \"true\" : undefined}\n                        >\n                            {renderIcon && <span className={styles.icon}>{renderIcon(item)}</span>}\n\n                            <div className={styles.body}>\n                                {interactive ? (\n                                    <button\n                                        type=\"button\"\n                                        className={styles.trigger}\n                                        onClick={() => activate(item)}\n                                    >\n                                        <span className={styles.itemTitle}>{item.title}</span>\n                                        {item.body && (\n                                            <span className={styles.itemBody}>{item.body}</span>\n                                        )}\n                                    </button>\n                                ) : (\n                                    <>\n                                        <span className={styles.itemTitle}>{item.title}</span>\n                                        {item.body && (\n                                            <span className={styles.itemBody}>{item.body}</span>\n                                        )}\n                                    </>\n                                )}\n                                <time\n                                    className={styles.time}\n                                    dateTime={new Date(item.receivedAt).toISOString()}\n                                >\n                                    {relativeTime(item.receivedAt, { locale, now: reference })}\n                                </time>\n                            </div>\n\n                            {onDismiss && (\n                                <button\n                                    type=\"button\"\n                                    className={styles.dismiss}\n                                    onClick={() => onDismiss(item.id)}\n                                    aria-label={`Descartar: ${item.title}`}\n                                >\n                                    ×\n                                </button>\n                            )}\n                        </li>\n                    ))}\n                </ul>\n            )}\n        </div>\n    );\n}\n"],"mappings":"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"}