first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
# `useToastSonnerBridge`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Bridge hook that exposes the legacy chassis `useToast` API on top of
`sonner.toast`. The original chassis `useToast` (Wave 2) ran a small
`useSyncExternalStore`-backed queue that a separate root-level
renderer painted; Wave 3g adopts `sonner` as the canonical toast
surface (per shadcn skill rule) so the chassis-side queue is no
longer the canonical path. This bridge lets consumers migrate one
import at a time — call sites that already imported the legacy
`useToast` hook keep working, and new call sites that want the
sonner-native surface adopt this bridge instead.
The two routes funnel through the same
`react_ui.toast.shown_total{toast.kind=...}` counter so observability
dashboards never double-count.
## Features
- `bridge.toast({ titleKey, descriptionKey?, kind? })` — branded
translation keys, resolved against the active
`<TranslationProvider>`, dispatched to sonner.
- `bridge.dismiss(id?)` — dismiss a specific toast id, or every
active toast when called without arguments.
- Kind-aware dispatch: `success`, `error`, `info`, `warning` route to
the matching `toast.<kind>` method; everything else is dispatched
via the default `toast()` callable.
- Zero internal state — the bridge is a stateless adapter; the
authoritative queue lives inside sonner.
## Usage: Basic Example
```tsx
import {
Sonner,
useToastSonnerBridge,
} from "@suaveplan/react-ui";
function SaveButton(): JSX.Element {
const { toast } = useToastSonnerBridge();
return (
<>
<button
type="button"
onClick={() =>
toast({
titleKey: "react-ui.loading",
kind: "success",
})
}
>
Save
</button>
<Sonner />
</>
);
}
```
## Usage: Advanced Example
```tsx
import {
Sonner,
useToastSonnerBridge,
} from "@suaveplan/react-ui";
import { useEffect } from "react";
function NetworkBoundary(): JSX.Element {
const { toast, dismiss } = useToastSonnerBridge();
useEffect(() => {
const id = toast({
titleKey: "react-ui.loading",
descriptionKey: "react-ui.empty.noResults",
kind: "warning",
});
const timer = setTimeout(() => dismiss(id), 4000);
return () => clearTimeout(timer);
}, [toast, dismiss]);
return <Sonner position="top-right" />;
}
```
## API Reference
### `useToastSonnerBridge(): UseToastSonnerBridgeReturn`
Returns:
- `toast(input: BridgeToastInput): string | number` — emits a toast
via sonner. The returned id is the same id sonner assigns; pass it
to `dismiss` to retire the specific toast.
- `dismiss(id?: string | number): void` — dismisses one toast by id,
or all toasts when `id` is `undefined`.
### `BridgeToastInput`
- `titleKey: TranslationKey` — REQUIRED branded key resolved against
the active i18n catalog.
- `descriptionKey?: TranslationKey` — optional secondary line, also
resolved against i18n.
- `kind?: ToastKind` — `'default' | 'success' | 'error' | 'info' |
'warning'`. Defaults to `'default'`.
## Implementation Notes
- The bridge MUST be called inside a `<TranslationProvider>` because
it reads `useTranslation()` to resolve the branded keys; outside a
provider it throws `REACT_I18N.NO_PROVIDER`.
- The bridge does NOT instrument OTel directly — the underlying
`toast` re-export (which records the counter) is the single point
of attribution so we never double-count when the bridge fans out
to a kind-specific method.
- The bridge does NOT read sonner's `useSonner()` hook because the
upstream API does not expose a way to subscribe to a SPECIFIC toast
id, and the chassis bridge has no use for the global toast list at
call time — `toast.dismiss(id)` is sufficient for the UX surface.
- Memoised `useCallback` wrappers keep the returned object referentially
stable across renders so consumers using the bridge inside a `useEffect`
dependency array do not re-fire on every parent render.