# Toast

> Imperative notification API (`toast.success`, `toast.error`, `toast.promise`). Differentiates `role="alert"` from `role="status"`.

- Available in: `@sisyphos-ui/react`, `@sisyphos-ui/vue`, `@sisyphos-ui/angular`
- Docs: https://www.sisyphosui.com/docs/components/toast
- WAI-ARIA pattern: [Alert](https://www.w3.org/WAI/ARIA/apg/patterns/alert/)

## Installation

Pick the framework binding that matches your stack:

```bash
pnpm add @sisyphos-ui/react   # React 18+
pnpm add @sisyphos-ui/vue     # Vue 3+
pnpm add @sisyphos-ui/angular # Angular 17+
```

## Import

```tsx
import "@sisyphos-ui/react/styles.css";
import { Toast } from "@sisyphos-ui/react";
```

## Framework usage

### React 18+

```tsx
import { Toaster, toast, Button } from "@sisyphos-ui/react";

export const App = () => (
  <>
    <Toaster position="bottom-right" />
    <Button onClick={() => toast.success("Saved")}>Save</Button>
  </>
);
```

### Vue 3+

```vue
<script setup lang="ts">
import { Toaster, toast, Button } from "@sisyphos-ui/vue";
</script>

<template>
  <Toaster position="bottom-right" />
  <Button @click="toast.success('Saved')">Save</Button>
</template>
```

### Angular 17+

```ts
import { Component } from "@angular/core";
import { Toaster, Button, toast } from "@sisyphos-ui/angular";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [Toaster, Button],
  template: `
    <sui-toaster position="bottom-right" />
    <sui-button (buttonClick)="save()">Save</sui-button>
  `,
})
export class AppComponent {
  save() { toast.success("Saved"); }
}
```

## Examples

### Imperative API

`toast.success`, `.error`, `.warning`, `.info`. Mount `<Toaster />` once per app; fire toasts from anywhere.

```tsx
import { Button, Toaster, toast } from "@sisyphos-ui/react";

export function Example() {
  return (
    <>
      {/* Mount the <Toaster /> once — in your root layout or App shell. */}
      <Toaster position="bottom-right" />

      <div className="flex flex-wrap items-center gap-3">
        <Button onClick={() => toast.success("Saved successfully")}>Success</Button>
        <Button color="error"   onClick={() => toast.error("Something went wrong")}>Error</Button>
        <Button color="warning" onClick={() => toast.warning("Heads up — session expiring")}>Warning</Button>
        <Button color="info" variant="outlined" onClick={() => toast.info("New update available")}>Info</Button>
      </div>
    </>
  );
}
```

### Title + description

Second argument accepts `{ description, duration, dismissible, … }`.

```tsx
import { Button, Toaster, toast } from "@sisyphos-ui/react";

export function Example() {
  return (
    <>
      <Toaster position="bottom-right" />
      <Button
        onClick={() =>
          toast.success("Deployment finished", {
            description: "Build #4291 shipped to production in 1m 42s.",
          })
        }
      >
        Rich success toast
      </Button>
    </>
  );
}
```

### With undo action

Pass any ReactNode as `action`. Use `toast.dismiss(id)` to close from inside the action.

```tsx
import { Button, Toaster, toast } from "@sisyphos-ui/react";

export function Example() {
  return (
    <>
      <Toaster position="bottom-right" />
      <Button
        onClick={() => {
          let id = "";
          id = toast.success("Invitation sent", {
            description: "Ada will receive an email shortly.",
            duration: 8000,
            action: (
              <Button size="sm" variant="text" onClick={() => {
                toast.dismiss(id);
                toast.info("Invitation revoked");
              }}>
                Undo
              </Button>
            ),
          });
        }}
      >
        Send invite
      </Button>
    </>
  );
}
```

### Promise + loading

`toast.promise(p, { loading, success, error })` morphs a loading toast into success or error when the promise settles — same id, same slot, animated transition. `success` / `error` accept a function of the resolved value / thrown error.

```tsx
import { Button, toast } from "@sisyphos-ui/react";

export function Example() {
  return (
    <Button
      onClick={() => {
        const p = fetch("/api/invite", { method: "POST" }).then((r) => r.json());
        toast.promise(p, {
          loading: "Inviting teammate…",
          success: (user) => `${user.name} will receive an email shortly`,
          error: (err) => (err instanceof Error ? err.message : "Could not send invite"),
        });
      }}
    >
      Send invite
    </Button>
  );
}
```

### All positions

Pick any of the six anchor points. The `<Toaster />` itself drives layout.

```tsx
import { useState } from "react";
import { Button, Toaster, toast, type ToasterPosition } from "@sisyphos-ui/react";

const ALL: ToasterPosition[] = [
  "top-left", "top-center", "top-right",
  "bottom-left", "bottom-center", "bottom-right",
];

export function Example() {
  const [position, setPosition] = useState<ToasterPosition>("bottom-right");

  return (
    <>
      <Toaster position={position} />
      {ALL.map((p) => (
        <Button key={p} size="sm" onClick={() => setPosition(p)}>{p}</Button>
      ))}
      <Button onClick={() => toast.info(`Hello from ${position}`)}>Fire toast</Button>
    </>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| position | `"top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right"` | `"bottom-right"` | Toaster: corner or edge where the stack is anchored. |
| max | `number` | `5` | Toaster: max toasts visible at once; excess remain queued in state. |
| gap | `number` | `8` | Toaster: gap between stacked toasts in px. |
| description | `ReactNode` | — | `toast()` option: longer explanation rendered under the title. |
| duration | `number` | `4000` | `toast()` option: auto-dismiss delay in ms; pass `Infinity` to persist. |
| icon | `ReactNode \| null` | — | `toast()` option: override the per-type default icon; pass `null` to hide it. |
| action | `ReactNode` | — | `toast()` option: action element (button) rendered on the right. |
| dismissible | `boolean` | `true` | `toast()` option: show the X close button. |
| id | `string` | — | `toast()` option: provide an id to update or dedupe an existing toast. |
| onDismiss | `(id: string) => void` | — | `toast()` option: fired when the toast is removed for any reason. |

## Keyboard interactions

- **Tab** — Reaches a toast's action and dismiss buttons — both are native buttons in the document tab order.
- **Enter + Space** — Activates the focused dismiss or action button.

## Accessibility notes

- Error toasts render `role="alert"` with `aria-live="assertive"`; every other type uses `role="status"` with `aria-live="polite"`, so only errors interrupt the screen reader.
- Auto-dismiss pauses while the pointer hovers a toast, giving users time to read or act.
- The close button carries `aria-label="Dismiss notification"`; type icons are decorative and `aria-hidden`.
- `toast.loading` and `toast.promise` reuse a stable id, so the loading → success/error transition updates the existing toast in place instead of spawning a new announcement.

<!-- exports: { "Toast": "@sisyphos-ui/react" } -->