# Feedback Components

> Generated reference (regen-owned; see [index.md](index.md)). Binds only under `primitives: nurix`.

Components: Alert, Empty, ErrorBoundary, ErrorState, IssueReporter, Loader, NotificationBadge, Progress, Skeleton, SystemBanner, Toaster + toast

Each imports from its own subpath; a compound is one namespace with its parts as dotted members, e.g. `import { Empty } from "@nurix/components/compound/empty-state"` then `Empty.Header`, `Empty.Title`. Status colour is always `intent` (`neutral` | `info` | `success` | `warning` | `danger`, on the `--intent-*` tokens); a `danger` notice is `role="alert"`, every other intent `role="status"`, and a `role` you pass wins.

## Empty

Placeholder for an empty table body, panel, card or page (`compound/empty-state`).

**Sub-components:** `Empty.Header`, `Empty.Media` (`variant` [default|icon]), `Empty.Title`, `Empty.Description`, `Empty.Content`

- The root is unframed and fills a flex parent (`flex-1`); add `border` for the dashed frame, and a `min-h-*` or `h-full` inside a block-flow parent.
- `variant="icon"` draws the muted `size-10` tile, sizes an unsized `svg` to `size-6`, and is `aria-hidden`; the bare default stays exposed so an illustration `img` keeps its `alt`.
- Every part takes `render`; a root given a `role` is labelled by its Title. A 404 page is this composition — `min-h-[60vh]`, `<Empty.Title render={<h1 />}>`, a way back in `Empty.Content`; there is no `NotFound` export.
- Not for data in flight (`Skeleton`, `Loader`) or a failed load (`ErrorState`); `block/list` renders its own through its `emptyState` prop.

```tsx
<Empty className="border">
  <Empty.Header>
    <Empty.Media variant="icon"><Inbox /></Empty.Media>
    <Empty.Title>No messages</Empty.Title>
    <Empty.Description>Start a conversation to see messages here.</Empty.Description>
  </Empty.Header>
  <Empty.Content><Button>New message</Button></Empty.Content>
</Empty>
```

## Alert

Inline notice that stays on the page until the user acts or navigates away (`compound/alert`).

**Props:** `intent` [neutral|info|success|warning|danger] (default `neutral`, the card surface), `icon` (replaces the intent's glyph; `null` draws none), `onDismiss`, `labels` (`dismiss`), `render`
**Sub-components:** `Alert.Title`, `Alert.Description`

- Each status intent draws its own glyph; `neutral` draws none. Never place an `<svg>` child for the icon — pass `icon`.
- `onDismiss` draws the X in its own trailing column and never hides the alert — visibility stays in the parent (`{isVisible && <Alert … />}`).
- `<Alert.Title render={<h3 />}>` makes the title a real heading. A message that should clear itself is a toast.

```tsx
<Alert intent="danger" onDismiss={() => setIsVisible(false)}>
  <Alert.Title>Sync failed</Alert.Title>
  <Alert.Description>Retry in a few minutes.</Alert.Description>
</Alert>
```

## Toaster + toast

Self-clearing notifications on Base UI Toast (`compound/toast`): mount `<Toaster />` once at the app root, call `toast()` from anywhere, inside or outside React.

**`toast` options:** `title`, `description`, `intent` (the only colour route, draws the glyph), `icon` (`null` draws none), `timeout` (ms; defaults to the dwell token, `Infinity` holds), `priority` [high|low] (default assertive for `danger`, polite otherwise), `action` (`{ label, onClick }`), `onDismiss`, `id` (reusing one updates in place)
**Methods:** `toast.promise(work, { pending, success, error })` (`error` always takes `danger`; a phase may be a function of the result), `toast.update(id, options)` (changes only the named fields), `toast.close(id?)`
**Toaster props:** `side` [top|bottom] (default `bottom`), `align` [start|center|end] (default `end`, logical), `hasCloseButton` (default `true`), `labels` (`region`, `dismiss`); parts `Toaster.Root`, `.Content`, `.Title`, `.Description`, `.Action`, `.Close`

- There are no `toast.success` / `toast.error` shortcuts — pass `intent`. Never mount a second `Toaster`: every one renders the same queue.

```tsx
toast({ title: "Saved", description: "Your changes are live.", intent: "success" });
toast.promise(save(), { pending: "Saving…", success: "Saved", error: "Save failed" });
```

## SystemBanner

Shell-chrome banner above the app for environment, impersonation or outage states (`element/system-banner`); also exports `systemBannerVariants`.

**Props:** `title` (required), `description`, `intent` [neutral|info|success|warning|danger] (default `neutral`), `icon`, `action`, `onDismiss`, `labels` (`dismiss`, `actions`)
**Sub-components:** `SystemBanner.Stack` (orders and divides banners; placing it is yours)

- The banner stores no visibility: `onDismiss` renders the dismiss button and your callback unmounts it; omit it for a banner that must not be dismissed.
- `description` wraps on a narrow viewport. A message in content flow is `Alert`.

```tsx
<SystemBanner.Stack>
  <SystemBanner intent="warning" title="Sandbox environment" description="Data resets nightly" />
  <SystemBanner title="Viewing as Dana" action={<Button size="sm">Exit view-as</Button>} />
</SystemBanner.Stack>
```

## IssueReporter

Report-a-problem dialog with screenshot capture and an environment snapshot (`block/issue-reporter`). Requires the optional peer `html2canvas-pro` — without it the DOM raster fails on every capture and each one falls back to a `getDisplayMedia` permission prompt.

**Props:** `onSubmit(report)` (required; a rejection keeps the dialog open, a resolution closes and resets), `open` / `defaultOpen` / `onOpenChange`, `shortcut` (e.g. `"mod+shift+i"`; default `null`), `shouldCaptureOnOpen` (default `true`), `fields` (default one `textarea` named `text`; types text|textarea|number|select|radio|checkbox), `defaultValue`, `metadata` (value or function, resolved at submit), `maxScreenshots` (default 3), `diagnosticsSections` (default all; `[]` turns collection off), `renderDiagnostics`, `captureOptions` (`ignoreElement`, `maxWidth`, `imageType`…), `onCaptureError`, `labels`
**Also exports:** `useScreenCapture(options)` → `[state, actions]`, `useDiagnostics({ sections, collectOnMount })` → `{ data, isLoading, error, refresh }`, `DIAGNOSTICS_SECTIONS` (`system`, `capabilities`, `webVitals`, `storage`, `serviceWorkers`), `CaptureError`

- `IssueReport` carries `fields`, `screenshots`, `diagnostics`, `metadata`. Submit stays disabled until every `required` field is set **and** a field holds a value or a screenshot is attached.
- A report needing no screenshot or diagnostics is a plain form over `compound/dialog`, not this.

```tsx
<IssueReporter
  shortcut="mod+shift+i"
  fields={[{ name: "text", type: "textarea", label: "What went wrong?", required: true }]}
  metadata={() => ({ route: location.pathname })}
  onSubmit={async (report) => sendToTracker(report)}
/>
```

## Other Components

- `loader` — `Loader` (`element/loader`): em-sized lattice in `currentColor` for open-ended work; `status` [pending|success|error] (default `pending`) settles the same glyph into a check or a cross, `label`, `successLabel`, `errorLabel`, `hasTimer`, `labels`. A "Thinking…" or typing row is `<Loader label="Typing" />` — there is no dots variant.
- `progress` — `Progress` (`element/progress`): `value` (0–100, clamped; `null` is indeterminate), `label` (the accessible name), `variant` [linear|circular|fill] (default `linear`), `size` [sm|default|lg] (not on `fill`), `format` / `getAriaValueText` to reword the announcement.
- `skeleton` — `Skeleton` (`element/skeleton`): pulsing `aria-hidden` `div` with no intrinsic size — size it with `className`, mark the container `aria-busy`, and pass `render={<span />}` inside phrasing content.
- `notification-badge` — `NotificationBadge` (`element/notification-badge`): `value` (pass the real count; renders nothing at zero), `max` (default 99, shows `99+`), `variant` [count|dot], `intent`, `labels` (`dot`); em-sized from the text beside it, positioned by you on a `relative` trigger.
- `error-state` — `ErrorState` (`compound/error-state`): failed-load block on Empty's geometry; `description` (required), `title`, `icon`, `onRetry` (renders Retry), `labels` (`retry`). Carries no live role; `block/list`, `block/table` and `block/tree` take `error={{ message, onRetry }}` instead.
- `error-boundary` — `ErrorBoundary` (`lib/error-boundary`): catches render errors in the subtree it wraps; `onError` (its only way out — it logs nothing), `onRetry` (refetch here, or the subtree throws again), `fallback` (replaces the whole card, retry included).
- No nurix primitive: `text-spinner` — build from the `index.md` fallback spec.
