@sakaniui/react 0.8.13 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -78,7 +78,7 @@ import { Button, Card } from '@sakaniui/react';
78
78
  (`aria-label`, or `label` on `socialLinks`). Status changes use live regions (`Alert`, `Toast`,
79
79
  `Spinner`, `EmptyState` are `role="status"`/`alert`). Never remove focus rings.
80
80
  7. **Don't fake what the system doesn't do.** `Toast` is presentational — you own queueing and
81
- positioning. `Menu` is a surface — you own anchoring (pair it with `Popover`). Dragging is a
81
+ positioning (for a ready-made stack, use `Toaster` and `toast()`). `Menu` is a surface — you own anchoring (pair it with `Popover`). Dragging is a
82
82
  look, not a behaviour. Check "Behaviour you must supply" in the table below before assuming.
83
83
  8. **Compose in slots, don't fork.** Components expose slots (`leading`, `trailing`, `meta`,
84
84
  `assignees`, `icon`, `actions`). Fill a slot with other system components rather than copying a
@@ -153,7 +153,7 @@ CSS of a component into your own file.
153
153
  Format: **Use for** · **Why it is built this way** · **Not for / instead**.
154
154
 
155
155
  **Looking for a component by another name?** SakaniUI uses its own names. A *dialog* or *alert dialog* is
156
- `Modal`; a *snackbar* is `Toast`; a *dropdown menu* is `Menu`; a *navbar* or *app bar* is `TopBar`; a
156
+ `Modal`; a *snackbar* is `Toast` (or `Toaster` + `toast()` for a managed stack); a *dropdown menu* is `Menu`; a *navbar* or *app bar* is `TopBar`; a
157
157
  *date picker* is `Calendar`; a *loader* is `Spinner`; a *toggle* is `Switch`; a *banner* is `Announcement`;
158
158
  *autocomplete* is `Combobox`. There is no `Dialog`, `AlertDialog`, `Snackbar` or `Navbar`. Each component
159
159
  lists its other names under `aliases` in `manifest.json`, and `search_components` understands them.
@@ -174,6 +174,11 @@ lists its other names under `aliases` in `manifest.json`, and `search_components
174
174
  - **Toast** — Use for short-lived confirmation or error after an action. *Why:* presentational only;
175
175
  queueing and positioning are the app's job, so it fits any notification system. *Not for* anything the user
176
176
  must act on — a toast disappears.
177
+ - **Toaster + toast()** — Use for notifications you trigger from code: mount one `<Toaster />`, call
178
+ `toast.success('Saved')` from anywhere. *Why:* it owns queueing, position (6), the 4s timer, the pile that
179
+ spreads on hover and swipe-to-dismiss, so the app does not. `toast.promise` turns one toast from loading
180
+ into success or error. *Not for* anything the user must act on — a toast disappears; a plain static card
181
+ → `Toast`. Needs `Toaster` mounted once, and `toast()` is a function, not a component.
177
182
  - **Badge** — Use for a short status, category or count. *Why:* 6 colours × subtle/solid, no sizes, no
178
183
  borders — it is a label, not a control. *Not for* clickable filters → `FilterChip`.
179
184
  - **Tooltip** — Use for brief supplementary text on hover/focus. *Why:* pure CSS, so it works with
@@ -261,7 +266,8 @@ and **tell the user when you are composing** rather than implying a dedicated co
261
266
 
262
267
  | Component | You own |
263
268
  |---|---|
264
- | Toast | queueing, positioning, auto-dismiss timing |
269
+ | Toast | queueing, positioning, auto-dismiss timing (or use `Toaster`) |
270
+ | Toaster | mounting it once; it handles the rest |
265
271
  | Menu | anchoring/positioning (pair with Popover) |
266
272
  | BoardCard `dragging` | drag-and-drop logic and drop targets |
267
273
  | BoardColumn `loading` | the skeleton cards inside it |
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to `@sakaniui/react` are documented here.
4
4
 
5
+ ## 0.9.0
6
+
7
+ - **New: `Toaster` and `toast()`**, a managed toast stack (Sonner-style) built from the new *↳ Sonner* page in Figma.
8
+ Mount one `<Toaster />`, then call `toast()`, `toast.success()`, `.info()`, `.warning()`, `.error()`,
9
+ `.loading()`, `.promise()` and `.dismiss()` from anywhere.
10
+ - New toasts rise in from the edge; older ones pile up behind the newest (lifted 14px, shrunk 5% per level,
11
+ content hidden, three visible). Hover or focus spreads the pile into a list and pauses the 4s timers;
12
+ leaving folds it back. Swipe a toast towards its edge to dismiss it (past 45px, or a quick flick, it flies
13
+ out in 200ms; otherwise it springs back in 400ms). Motion is 400ms `ease`.
14
+ - Options per toast: `description`, `icon`, `action`, `cancel`, `closeButton`, `duration`, `dismissible`,
15
+ `richColors`, `id` (update in place), `onDismiss`, `onAutoClose`. On `Toaster`: `position` (six),
16
+ `richColors`, `closeButton`, `expand`, `visibleToasts`, `duration`, `gap`, `offset`, `mobileOffset`,
17
+ `hotkey` (alt+T focuses the stack; Escape folds it).
18
+ - Light, dark and glass surfaces through the existing tokens; reduced motion keeps the fades only; errors
19
+ are announced assertively.
20
+ - The existing `Toast` is unchanged: it stays the static, presentational card.
21
+ - Agent contract: `Toaster` is in the manifest with recipes (show a toast, a promise toast) and the aliases
22
+ *sonner*, *toast stack*, *toast provider*. (`toaster` now means this component, not `Toast`.)
23
+
5
24
  ## 0.8.13
6
25
 
7
26
  A user reported the MCP server "didn't work". Reproduced: on a Node older than 16.9 the server connected
package/README.md CHANGED
@@ -262,7 +262,7 @@ Change a semantic token and every component follows — in both the design file
262
262
 
263
263
  **Forms** — Input · Textarea · Select · Checkbox · Radio · Switch · Slider · Combobox (single/multi, async loading) · File Upload
264
264
 
265
- **Composite** — Card · Alert · Toast · Accordion · Tabs · Breadcrumb · Table (generic, selectable) · Stat Card (sparklines) · Stepper · Calendar (single + range, dropdown navigation) · Pagination · Popover · Segmented Control · List Item
265
+ **Composite** — Card · Alert · Toast · Toaster (Sonner-style `toast()` stack) · Accordion · Tabs · Breadcrumb · Table (generic, selectable) · Stat Card (sparklines) · Stepper · Calendar (single + range, dropdown navigation) · Pagination · Popover · Segmented Control · List Item
266
266
 
267
267
  **Sidebar kit** — Sidebar · Header · Search · Item · Sub Item · Group Label · Divider · Promo · Footer — nine standalone parts that compose into full navigation
268
268
 
@@ -468,8 +468,10 @@ function checkCode(source) {
468
468
  if (imp.def) errors.push({ line: imp.line, message: `${entry} has no default export; use a named import: import { ${imp.def} } from '${entry}';` });
469
469
  for (const n of imp.names) {
470
470
  if (n.typeOnly) continue;
471
- const c = byName.get(n.imported.toLowerCase());
472
- const isUtility = manifest.utilities?.some((u) => u.name === n.imported);
471
+ const util = manifest.utilities?.find((u) => u.name === n.imported);
472
+ const isUtility = !!util;
473
+ // an exact helper name (toast) is not a mis-cased component (Toast)
474
+ const c = isUtility ? undefined : byName.get(n.imported.toLowerCase());
473
475
  if (entry === '@sakaniui/react/icons') {
474
476
  if (!iconNames.has(n.imported) && !iconOtherExports.has(n.imported)) {
475
477
  const hint = searchIcons(n.imported, 3).map((i) => i.name);
@@ -489,6 +491,7 @@ function checkCode(source) {
489
491
  errors.push({ line: imp.line, message: `${n.imported} is not exported by @sakaniui/react.${aliasHint(n.imported)}${near.length ? ` Did you mean: ${near.join(', ')}?` : ''} Use search_components.` });
490
492
  }
491
493
  if (c && inInstalled(c) === false) errors.push({ line: imp.line, message: `${c.name} was added in ${c.since}; the project has ${projectVersion}.` });
494
+ if (util && inInstalled(util) === false) errors.push({ line: imp.line, message: `${util.name} was added in ${util.since}; the project has ${projectVersion}.` });
492
495
  }
493
496
  }
494
497
 
@@ -0,0 +1,30 @@
1
+ import { default as React } from 'react';
2
+ export type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
3
+ export interface ToasterProps {
4
+ /** Corner or edge the stack sits on. Defaults to "bottom-right". */
5
+ position?: ToastPosition;
6
+ /** Tints success, info, warning and error toasts by type. */
7
+ richColors?: boolean;
8
+ /** Shows the round close button on every toast. */
9
+ closeButton?: boolean;
10
+ /** Keeps the stack spread out instead of piled up. */
11
+ expand?: boolean;
12
+ /** How many toasts show at once; older ones fade out. Defaults to 3. */
13
+ visibleToasts?: number;
14
+ /** Milliseconds before a toast closes itself. Defaults to 4000. */
15
+ duration?: number;
16
+ /** Space between toasts when spread out, in px. Defaults to 14. */
17
+ gap?: number;
18
+ /** Distance from the viewport edge, in px or any CSS length. Defaults to 24. */
19
+ offset?: number | string;
20
+ /** Distance from the viewport edge on screens 600px wide or less. Defaults to 16. */
21
+ mobileOffset?: number | string;
22
+ /** Keys that focus the stack. Defaults to alt+T (`['altKey', 'KeyT']`). */
23
+ hotkey?: string[];
24
+ /** Accessible name of the notifications region. The hotkey is appended. Defaults to "Notifications". */
25
+ containerAriaLabel?: string;
26
+ className?: string;
27
+ style?: React.CSSProperties;
28
+ }
29
+ export declare const Toaster: React.FC<ToasterProps>;
30
+ export default Toaster;
@@ -0,0 +1,4 @@
1
+ export { Toaster, default } from './Sonner';
2
+ export type { ToasterProps, ToastPosition } from './Sonner';
3
+ export { toast } from './state';
4
+ export type { ToastType, ToastId, ToastOptions, ToastData, ToastAction, ToastCancel, ToastPromiseMessages } from './state';
@@ -0,0 +1,106 @@
1
+ import { default as React } from 'react';
2
+ export type ToastType = 'default' | 'success' | 'info' | 'warning' | 'error' | 'loading';
3
+ export type ToastId = string | number;
4
+ export interface ToastAction {
5
+ /** Button text. */
6
+ label: React.ReactNode;
7
+ /** Called on click. The toast closes afterwards unless you call `event.preventDefault()`. */
8
+ onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
9
+ }
10
+ export interface ToastCancel {
11
+ /** Button text. */
12
+ label: React.ReactNode;
13
+ /** Called on click, before the toast closes. */
14
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
15
+ }
16
+ export interface ToastOptions {
17
+ /** Reuse an id to update a toast in place instead of adding a new one. */
18
+ id?: ToastId;
19
+ /** Secondary line under the title. */
20
+ description?: React.ReactNode;
21
+ /** Replaces the type icon. Pass `null` to show no icon. */
22
+ icon?: React.ReactNode;
23
+ /** Primary button, right-most. */
24
+ action?: ToastAction;
25
+ /** Secondary button, left of the action. */
26
+ cancel?: ToastCancel;
27
+ /** Milliseconds before the toast closes itself. Overrides the Toaster's `duration`. `Infinity` keeps it open. */
28
+ duration?: number;
29
+ /** Shows the round close button on this toast. Overrides the Toaster's `closeButton`. */
30
+ closeButton?: boolean;
31
+ /** When false the toast cannot be swiped away or closed by the user. Defaults to true. */
32
+ dismissible?: boolean;
33
+ /** Announces the toast assertively (interrupting the screen reader). Errors are announced this way by default. */
34
+ important?: boolean;
35
+ /** Tints this toast by its type. Overrides the Toaster's `richColors`. */
36
+ richColors?: boolean;
37
+ /** Called when the toast is closed by the user (close button, cancel, swipe) or by `toast.dismiss()`. */
38
+ onDismiss?: (toast: ToastData) => void;
39
+ /** Called when the toast closes itself after its duration. */
40
+ onAutoClose?: (toast: ToastData) => void;
41
+ /** Extra class on the toast element. */
42
+ className?: string;
43
+ }
44
+ export interface ToastData extends ToastOptions {
45
+ id: ToastId;
46
+ type: ToastType;
47
+ title: React.ReactNode;
48
+ /** @internal set by toast.dismiss(); the Toaster plays the exit motion, then removes it */
49
+ delete?: boolean;
50
+ /** @internal bumped on every update so the timer restarts */
51
+ version: number;
52
+ }
53
+ export interface ToastPromiseMessages<T> extends Omit<ToastOptions, 'id'> {
54
+ id?: ToastId;
55
+ /** Shown while the promise is pending. Leave it out to show nothing until it settles. */
56
+ loading?: React.ReactNode;
57
+ /** Shown when it resolves. A function receives the resolved value. */
58
+ success?: React.ReactNode | ((data: T) => React.ReactNode);
59
+ /** Shown when it rejects. A function receives the error. */
60
+ error?: React.ReactNode | ((error: unknown) => React.ReactNode);
61
+ /** Called after it settles either way. */
62
+ finally?: () => void;
63
+ }
64
+ type Event = {
65
+ kind: 'upsert';
66
+ toast: ToastData;
67
+ } | {
68
+ kind: 'dismiss';
69
+ id?: ToastId;
70
+ };
71
+ type Listener = (event: Event) => void;
72
+ /** @internal */
73
+ export declare const store: {
74
+ subscribe(listener: Listener): () => void;
75
+ /** Toasts that are open right now, newest first. */
76
+ active: () => ToastData[];
77
+ /** @internal the Toaster calls this once a toast has finished its exit motion */
78
+ remove(id: ToastId): void;
79
+ };
80
+ declare function dismiss(id?: ToastId): ToastId | undefined;
81
+ declare function promise<T>(input: Promise<T> | (() => Promise<T>), messages: ToastPromiseMessages<T>): ToastId;
82
+ type ToastFn = (title: React.ReactNode, options?: ToastOptions) => ToastId;
83
+ /**
84
+ * Shows a toast. Needs one `<Toaster />` mounted somewhere in the app.
85
+ *
86
+ * toast('Event has been created', { description: 'Monday, January 3rd at 6:00pm' })
87
+ * toast.success('Saved')
88
+ * toast.error('Upload failed', { action: { label: 'Retry', onClick: retry } })
89
+ * toast.promise(save(), { loading: 'Saving…', success: 'Saved', error: 'Could not save' })
90
+ * toast.dismiss(id)
91
+ *
92
+ * Every call returns the toast's id; pass it back as `id` to update that toast in place.
93
+ */
94
+ export declare const toast: ToastFn & {
95
+ message: ToastFn;
96
+ success: ToastFn;
97
+ info: ToastFn;
98
+ warning: ToastFn;
99
+ error: ToastFn;
100
+ /** Stays open (no timer) until you update it with the same `id` or dismiss it. */
101
+ loading: ToastFn;
102
+ promise: typeof promise;
103
+ /** Closes one toast, or every toast when called without an id. */
104
+ dismiss: typeof dismiss;
105
+ };
106
+ export {};