first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user