gbs-add-block 1.2.11 → 1.2.13
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/README.md +4 -2
- package/index.cjs +7 -1
- package/package.json +1 -1
- package/source/beta-components/fileuploader/README.md +99 -0
- package/source/beta-components/fileuploader/__tests__/core.test.ts +395 -0
- package/source/beta-components/fileuploader/core/format.ts +79 -0
- package/source/beta-components/fileuploader/core/index.ts +26 -0
- package/source/beta-components/fileuploader/core/store.ts +432 -0
- package/source/beta-components/fileuploader/core/transport.ts +145 -0
- package/source/beta-components/fileuploader/core/types.ts +126 -0
- package/source/beta-components/fileuploader/core/validate.ts +83 -0
- package/source/beta-components/fileuploader/index.ts +7 -0
- package/source/beta-components/fileuploader/react/FileRow.tsx +268 -0
- package/source/beta-components/fileuploader/react/FileUploader.tsx +515 -0
- package/source/beta-components/fileuploader/react/icons.tsx +80 -0
- package/source/beta-components/fileuploader/react/locale.ts +33 -0
- package/source/beta-components/fileuploader/react/props.ts +31 -0
- package/source/beta-components/fileuploader/react/useFileUploader.ts +32 -0
- package/source/beta-components/fileuploader/styles.css +395 -0
- package/source/beta-components/toaster/README.md +128 -0
- package/source/beta-components/toaster/__tests__/core.test.ts +256 -0
- package/source/beta-components/toaster/core/api.ts +87 -0
- package/source/beta-components/toaster/core/index.ts +14 -0
- package/source/beta-components/toaster/core/store.ts +211 -0
- package/source/beta-components/toaster/core/toast.ts +12 -0
- package/source/beta-components/toaster/core/types.ts +78 -0
- package/source/beta-components/toaster/index.ts +6 -0
- package/source/beta-components/toaster/react/ToastItem.tsx +164 -0
- package/source/beta-components/toaster/react/Toaster.tsx +187 -0
- package/source/beta-components/toaster/react/icons.tsx +56 -0
- package/source/beta-components/toaster/react/locale.ts +6 -0
- package/source/beta-components/toaster/react/props.ts +15 -0
- package/source/beta-components/toaster/react/useToasts.ts +11 -0
- package/source/beta-components/toaster/styles.css +283 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* FileUploader styles.
|
|
3
|
+
*
|
|
4
|
+
* Tokens read the DataGrid's --dg-* variables when they are set on an ancestor
|
|
5
|
+
* (set them on :root to share one theme) and fall back to the same palette
|
|
6
|
+
* otherwise. Rules live in the `components` layer, so utility classes passed
|
|
7
|
+
* through `className` / `classNames` override them.
|
|
8
|
+
*/
|
|
9
|
+
@layer theme, base, components, utilities;
|
|
10
|
+
|
|
11
|
+
@layer components {
|
|
12
|
+
.fu-root {
|
|
13
|
+
--fu-bg: var(--dg-bg, light-dark(#ffffff, #0b0b0e));
|
|
14
|
+
--fu-fg: var(--dg-fg, light-dark(#18181b, #f4f4f5));
|
|
15
|
+
--fu-muted: var(--dg-muted, light-dark(#71717a, #a1a1aa));
|
|
16
|
+
--fu-border: var(--dg-border, light-dark(#e4e4e7, #27272a));
|
|
17
|
+
--fu-hover: var(--dg-hover, light-dark(#f4f4f5, #1f1f23));
|
|
18
|
+
--fu-input-bg: var(--dg-input-bg, light-dark(#ffffff, #121216));
|
|
19
|
+
--fu-accent: var(--dg-accent, light-dark(#2563eb, #60a5fa));
|
|
20
|
+
--fu-accent-fg: var(--dg-accent-fg, light-dark(#ffffff, #0b1220));
|
|
21
|
+
--fu-accent-soft: var(--dg-accent-soft, light-dark(#eff6ff, #172554));
|
|
22
|
+
--fu-focus: var(--dg-focus, light-dark(#2563eb, #60a5fa));
|
|
23
|
+
--fu-danger: var(--dg-danger, light-dark(#dc2626, #f87171));
|
|
24
|
+
--fu-success: light-dark(#16a34a, #4ade80);
|
|
25
|
+
--fu-radius: var(--dg-radius, 8px);
|
|
26
|
+
--fu-font-size: var(--dg-font-size, 13px);
|
|
27
|
+
--fu-dropzone-py: 20px;
|
|
28
|
+
--fu-thumb-size: 40px;
|
|
29
|
+
|
|
30
|
+
color-scheme: inherit;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
gap: 8px;
|
|
34
|
+
width: 100%;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
font-size: var(--fu-font-size);
|
|
37
|
+
line-height: 1.4;
|
|
38
|
+
color: var(--fu-fg);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:where(.dark, [data-theme="dark"]) .fu-root {
|
|
42
|
+
color-scheme: dark;
|
|
43
|
+
}
|
|
44
|
+
:where(.light, [data-theme="light"]) .fu-root {
|
|
45
|
+
color-scheme: light;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:where(.fu-root) *,
|
|
49
|
+
:where(.fu-root) *::before,
|
|
50
|
+
:where(.fu-root) *::after {
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.fu-root[data-size="sm"] {
|
|
55
|
+
--fu-font-size: 12px;
|
|
56
|
+
--fu-dropzone-py: 12px;
|
|
57
|
+
--fu-thumb-size: 32px;
|
|
58
|
+
}
|
|
59
|
+
.fu-root[data-size="lg"] {
|
|
60
|
+
--fu-font-size: 14px;
|
|
61
|
+
--fu-dropzone-py: 32px;
|
|
62
|
+
--fu-thumb-size: 48px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.fu-label {
|
|
66
|
+
font-weight: 500;
|
|
67
|
+
}
|
|
68
|
+
.fu-label[data-required]::after {
|
|
69
|
+
content: " *";
|
|
70
|
+
color: var(--fu-danger);
|
|
71
|
+
}
|
|
72
|
+
.fu-description {
|
|
73
|
+
color: var(--fu-muted);
|
|
74
|
+
font-size: 0.92em;
|
|
75
|
+
}
|
|
76
|
+
.fu-error {
|
|
77
|
+
color: var(--fu-danger);
|
|
78
|
+
font-size: 0.92em;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ------------------------------------------------------------ dropzone */
|
|
82
|
+
|
|
83
|
+
.fu-dropzone {
|
|
84
|
+
position: relative;
|
|
85
|
+
border: 1.5px dashed color-mix(in oklab, var(--fu-border), var(--fu-fg) 20%);
|
|
86
|
+
border-radius: var(--fu-radius);
|
|
87
|
+
background: var(--fu-input-bg);
|
|
88
|
+
transition:
|
|
89
|
+
border-color 120ms ease,
|
|
90
|
+
background-color 120ms ease;
|
|
91
|
+
}
|
|
92
|
+
.fu-dropzone:hover:not(:has(.fu-trigger:disabled)) {
|
|
93
|
+
border-color: color-mix(in oklab, var(--fu-border), var(--fu-fg) 40%);
|
|
94
|
+
}
|
|
95
|
+
.fu-dropzone[data-dragging] {
|
|
96
|
+
border-color: var(--fu-accent);
|
|
97
|
+
background: var(--fu-accent-soft);
|
|
98
|
+
}
|
|
99
|
+
.fu-root[data-invalid] .fu-dropzone {
|
|
100
|
+
border-color: var(--fu-danger);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.fu-trigger {
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 4px;
|
|
108
|
+
width: 100%;
|
|
109
|
+
padding: var(--fu-dropzone-py) 16px;
|
|
110
|
+
border: 0;
|
|
111
|
+
border-radius: inherit;
|
|
112
|
+
background: transparent;
|
|
113
|
+
color: inherit;
|
|
114
|
+
font: inherit;
|
|
115
|
+
text-align: center;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
}
|
|
118
|
+
.fu-trigger:focus-visible {
|
|
119
|
+
outline: 2px solid var(--fu-focus);
|
|
120
|
+
outline-offset: 2px;
|
|
121
|
+
}
|
|
122
|
+
.fu-trigger:disabled {
|
|
123
|
+
opacity: 0.55;
|
|
124
|
+
cursor: not-allowed;
|
|
125
|
+
}
|
|
126
|
+
.fu-dropzone-icon {
|
|
127
|
+
color: var(--fu-muted);
|
|
128
|
+
}
|
|
129
|
+
.fu-dropzone[data-dragging] .fu-dropzone-icon {
|
|
130
|
+
color: var(--fu-accent);
|
|
131
|
+
}
|
|
132
|
+
.fu-browse {
|
|
133
|
+
color: var(--fu-accent);
|
|
134
|
+
font-weight: 500;
|
|
135
|
+
text-decoration: underline;
|
|
136
|
+
text-underline-offset: 2px;
|
|
137
|
+
}
|
|
138
|
+
.fu-hint {
|
|
139
|
+
color: var(--fu-muted);
|
|
140
|
+
font-size: 0.92em;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ---------------------------------------------------------- rejections */
|
|
144
|
+
|
|
145
|
+
.fu-rejections {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: 4px;
|
|
149
|
+
margin: 0;
|
|
150
|
+
padding: 0;
|
|
151
|
+
list-style: none;
|
|
152
|
+
}
|
|
153
|
+
.fu-rejection {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 8px;
|
|
157
|
+
padding: 4px 4px 4px 10px;
|
|
158
|
+
border-radius: 6px;
|
|
159
|
+
background: color-mix(in oklab, var(--fu-danger), transparent 90%);
|
|
160
|
+
color: var(--fu-danger);
|
|
161
|
+
}
|
|
162
|
+
.fu-rejection > span {
|
|
163
|
+
flex: 1 1 auto;
|
|
164
|
+
min-width: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* --------------------------------------------------------------- files */
|
|
168
|
+
|
|
169
|
+
.fu-list {
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-direction: column;
|
|
172
|
+
gap: 6px;
|
|
173
|
+
margin: 0;
|
|
174
|
+
padding: 0;
|
|
175
|
+
list-style: none;
|
|
176
|
+
}
|
|
177
|
+
.fu-item {
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 10px;
|
|
181
|
+
padding: 8px 8px 8px 10px;
|
|
182
|
+
border: 1px solid var(--fu-border);
|
|
183
|
+
border-radius: var(--fu-radius);
|
|
184
|
+
background: var(--fu-bg);
|
|
185
|
+
}
|
|
186
|
+
.fu-item[data-status="error"] {
|
|
187
|
+
border-color: color-mix(in oklab, var(--fu-danger), var(--fu-border) 55%);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.fu-thumb {
|
|
191
|
+
--fu-kind: var(--fu-muted);
|
|
192
|
+
|
|
193
|
+
position: relative;
|
|
194
|
+
display: grid;
|
|
195
|
+
flex: none;
|
|
196
|
+
place-items: center;
|
|
197
|
+
width: var(--fu-thumb-size);
|
|
198
|
+
height: var(--fu-thumb-size);
|
|
199
|
+
overflow: hidden;
|
|
200
|
+
border-radius: 6px;
|
|
201
|
+
background: color-mix(in oklab, var(--fu-kind), transparent 88%);
|
|
202
|
+
color: var(--fu-kind);
|
|
203
|
+
}
|
|
204
|
+
.fu-thumb img {
|
|
205
|
+
position: absolute;
|
|
206
|
+
inset: 0;
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100%;
|
|
209
|
+
object-fit: cover;
|
|
210
|
+
}
|
|
211
|
+
.fu-thumb[data-kind="image"] {
|
|
212
|
+
--fu-kind: light-dark(#2563eb, #60a5fa);
|
|
213
|
+
}
|
|
214
|
+
.fu-thumb[data-kind="video"] {
|
|
215
|
+
--fu-kind: light-dark(#9333ea, #c084fc);
|
|
216
|
+
}
|
|
217
|
+
.fu-thumb[data-kind="audio"] {
|
|
218
|
+
--fu-kind: light-dark(#d97706, #fbbf24);
|
|
219
|
+
}
|
|
220
|
+
.fu-thumb[data-kind="pdf"] {
|
|
221
|
+
--fu-kind: light-dark(#dc2626, #f87171);
|
|
222
|
+
}
|
|
223
|
+
.fu-thumb[data-kind="spreadsheet"] {
|
|
224
|
+
--fu-kind: light-dark(#16a34a, #4ade80);
|
|
225
|
+
}
|
|
226
|
+
.fu-thumb[data-kind="document"] {
|
|
227
|
+
--fu-kind: light-dark(#0891b2, #22d3ee);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.fu-body {
|
|
231
|
+
display: flex;
|
|
232
|
+
flex: 1 1 auto;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
gap: 1px;
|
|
235
|
+
min-width: 0;
|
|
236
|
+
}
|
|
237
|
+
.fu-name,
|
|
238
|
+
.fu-meta,
|
|
239
|
+
.fu-item-error {
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
text-overflow: ellipsis;
|
|
242
|
+
white-space: nowrap;
|
|
243
|
+
}
|
|
244
|
+
.fu-name {
|
|
245
|
+
font-weight: 500;
|
|
246
|
+
}
|
|
247
|
+
.fu-meta {
|
|
248
|
+
color: var(--fu-muted);
|
|
249
|
+
font-size: 0.92em;
|
|
250
|
+
font-variant-numeric: tabular-nums;
|
|
251
|
+
}
|
|
252
|
+
.fu-item[data-status="success"] .fu-meta {
|
|
253
|
+
color: var(--fu-success);
|
|
254
|
+
}
|
|
255
|
+
.fu-item[data-status="error"] .fu-meta,
|
|
256
|
+
.fu-item-error {
|
|
257
|
+
color: var(--fu-danger);
|
|
258
|
+
font-size: 0.92em;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.fu-progress {
|
|
262
|
+
height: 4px;
|
|
263
|
+
margin-top: 4px;
|
|
264
|
+
overflow: hidden;
|
|
265
|
+
border-radius: 999px;
|
|
266
|
+
background: var(--fu-hover);
|
|
267
|
+
}
|
|
268
|
+
/* Scaling instead of width: moving the bar never triggers layout. */
|
|
269
|
+
.fu-progress-bar {
|
|
270
|
+
display: block;
|
|
271
|
+
height: 100%;
|
|
272
|
+
background: var(--fu-accent);
|
|
273
|
+
transform-origin: left;
|
|
274
|
+
transition: transform 150ms linear;
|
|
275
|
+
}
|
|
276
|
+
.fu-progress-bar:dir(rtl) {
|
|
277
|
+
transform-origin: right;
|
|
278
|
+
}
|
|
279
|
+
.fu-item[data-status="paused"] .fu-progress-bar {
|
|
280
|
+
background: var(--fu-muted);
|
|
281
|
+
}
|
|
282
|
+
.fu-item[data-status="error"] .fu-progress-bar {
|
|
283
|
+
background: var(--fu-danger);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.fu-actions {
|
|
287
|
+
display: flex;
|
|
288
|
+
flex: none;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: 2px;
|
|
291
|
+
color: var(--fu-muted);
|
|
292
|
+
}
|
|
293
|
+
.fu-icon-button {
|
|
294
|
+
display: inline-grid;
|
|
295
|
+
place-items: center;
|
|
296
|
+
width: 28px;
|
|
297
|
+
height: 28px;
|
|
298
|
+
padding: 0;
|
|
299
|
+
border: 0;
|
|
300
|
+
border-radius: 6px;
|
|
301
|
+
background: transparent;
|
|
302
|
+
color: inherit;
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
}
|
|
305
|
+
.fu-icon-button:hover:not(:disabled) {
|
|
306
|
+
background: var(--fu-hover);
|
|
307
|
+
color: var(--fu-fg);
|
|
308
|
+
}
|
|
309
|
+
.fu-icon-button:focus-visible {
|
|
310
|
+
outline: 2px solid var(--fu-focus);
|
|
311
|
+
outline-offset: 1px;
|
|
312
|
+
}
|
|
313
|
+
.fu-icon-button:disabled {
|
|
314
|
+
opacity: 0.4;
|
|
315
|
+
cursor: default;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* -------------------------------------------------------------- footer */
|
|
319
|
+
|
|
320
|
+
.fu-footer {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
justify-content: flex-end;
|
|
324
|
+
gap: 8px;
|
|
325
|
+
}
|
|
326
|
+
.fu-footer .fu-progress {
|
|
327
|
+
flex: 1 1 auto;
|
|
328
|
+
margin: 0;
|
|
329
|
+
}
|
|
330
|
+
.fu-footer-text {
|
|
331
|
+
color: var(--fu-muted);
|
|
332
|
+
font-size: 0.92em;
|
|
333
|
+
font-variant-numeric: tabular-nums;
|
|
334
|
+
white-space: nowrap;
|
|
335
|
+
}
|
|
336
|
+
.fu-button {
|
|
337
|
+
height: 32px;
|
|
338
|
+
padding: 0 12px;
|
|
339
|
+
border: 1px solid var(--fu-border);
|
|
340
|
+
border-radius: 6px;
|
|
341
|
+
background: var(--fu-bg);
|
|
342
|
+
color: inherit;
|
|
343
|
+
font: inherit;
|
|
344
|
+
font-weight: 500;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
cursor: pointer;
|
|
347
|
+
}
|
|
348
|
+
.fu-button:hover:not(:disabled) {
|
|
349
|
+
background: var(--fu-hover);
|
|
350
|
+
}
|
|
351
|
+
.fu-button[data-variant="primary"] {
|
|
352
|
+
border-color: var(--fu-accent);
|
|
353
|
+
background: var(--fu-accent);
|
|
354
|
+
color: var(--fu-accent-fg);
|
|
355
|
+
}
|
|
356
|
+
.fu-button[data-variant="primary"]:hover:not(:disabled) {
|
|
357
|
+
background: color-mix(in oklab, var(--fu-accent), var(--fu-fg) 12%);
|
|
358
|
+
}
|
|
359
|
+
.fu-button:focus-visible {
|
|
360
|
+
outline: 2px solid var(--fu-focus);
|
|
361
|
+
outline-offset: 1px;
|
|
362
|
+
}
|
|
363
|
+
.fu-button:disabled {
|
|
364
|
+
opacity: 0.55;
|
|
365
|
+
cursor: not-allowed;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.fu-sr-only {
|
|
369
|
+
position: absolute;
|
|
370
|
+
width: 1px;
|
|
371
|
+
height: 1px;
|
|
372
|
+
margin: -1px;
|
|
373
|
+
padding: 0;
|
|
374
|
+
overflow: hidden;
|
|
375
|
+
clip-path: inset(50%);
|
|
376
|
+
white-space: nowrap;
|
|
377
|
+
border: 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
@media (prefers-reduced-motion: reduce) {
|
|
381
|
+
.fu-dropzone,
|
|
382
|
+
.fu-progress-bar {
|
|
383
|
+
transition: none;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@media (forced-colors: active) {
|
|
388
|
+
.fu-progress-bar {
|
|
389
|
+
background: Highlight;
|
|
390
|
+
}
|
|
391
|
+
.fu-dropzone[data-dragging] {
|
|
392
|
+
border-color: Highlight;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Toaster
|
|
2
|
+
|
|
3
|
+
Notifications for any React 19 app — Vite, Next.js, Remix or plain React — styled
|
|
4
|
+
to match the DataGrid, Combobox and DatePicker. No runtime dependencies besides
|
|
5
|
+
React.
|
|
6
|
+
|
|
7
|
+
- **`<Toaster />`** — mount once; renders the toasts.
|
|
8
|
+
- **`toast()`** — call from anywhere: components, event handlers, data layers,
|
|
9
|
+
even code outside React. No hook, context or provider.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { toast, Toaster } from "@/components/toaster";
|
|
15
|
+
import "@/components/toaster/styles.css";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// Once, near the root of the app (e.g. app/layout.tsx or App.tsx)
|
|
20
|
+
<Toaster />
|
|
21
|
+
|
|
22
|
+
// Anywhere
|
|
23
|
+
toast.success("Settings saved");
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Import from one path everywhere. `toast()` and `<Toaster />` meet through a
|
|
27
|
+
shared store, and a bundler that sees two import paths may create two stores.
|
|
28
|
+
|
|
29
|
+
## Showing toasts
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
toast("Event created", { description: "Monday, 10:00" });
|
|
33
|
+
toast.success("Saved");
|
|
34
|
+
toast.error("Upload failed", { action: { label: "Retry", onClick: retry } });
|
|
35
|
+
toast.warning("Storage almost full");
|
|
36
|
+
toast.info("New version available");
|
|
37
|
+
const id = toast.loading("Uploading…"); // stays until updated
|
|
38
|
+
|
|
39
|
+
toast.update(id, { type: "success", title: "Uploaded" });
|
|
40
|
+
toast.dismiss(id); // or toast.dismiss() for all
|
|
41
|
+
|
|
42
|
+
toast.promise(saveUser(data), {
|
|
43
|
+
loading: "Saving…",
|
|
44
|
+
success: (user) => `${user.name} saved`,
|
|
45
|
+
error: (error) => `Could not save: ${(error as Error).message}`,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
toast.custom(({ dismiss }) => <MyCard onClose={dismiss} />);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Every call returns the toast's id. Passing an `id` that is already on screen
|
|
52
|
+
updates that toast instead of adding another.
|
|
53
|
+
|
|
54
|
+
## Toast options
|
|
55
|
+
|
|
56
|
+
| Option | Type | Default | Description |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `id` | `string` | generated | Reuse to update a toast in place. |
|
|
59
|
+
| `type` | `"default"` \| `"success"` \| `"error"` \| `"warning"` \| `"info"` \| `"loading"` | `"default"` | Icon, color bar and announcement. |
|
|
60
|
+
| `description` | `ReactNode` | — | Second line. |
|
|
61
|
+
| `duration` | `number` | `5000` (loading: stays) | Milliseconds. `0` or `Infinity` stays until dismissed. |
|
|
62
|
+
| `dismissible` | `boolean` | `true` | Close button, Escape and swipe. |
|
|
63
|
+
| `action` / `cancel` | `{ label, onClick(event) }` | — | Buttons. The toast closes after a click unless `event.preventDefault()` is called. |
|
|
64
|
+
| `icon` | `ReactNode` | the type's icon | `null` hides it. |
|
|
65
|
+
| `className` | `string` | — | Class for this toast. |
|
|
66
|
+
| `render` | `({ id, dismiss }) => ReactNode` | — | Custom body (`toast.custom` sets it). |
|
|
67
|
+
| `onDismiss` / `onAutoClose` | `(toast) => void` | — | Closed by someone, or because time ran out. |
|
|
68
|
+
|
|
69
|
+
## Toaster props
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Default | Description |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| `position` | `"top-left"` \| `"top-center"` \| `"top-right"` \| `"bottom-left"` \| `"bottom-center"` \| `"bottom-right"` | `"top-right"` | Where the stack sits. Full width on narrow screens. |
|
|
74
|
+
| `limit` | `number` | `3` | Toasts on screen at once. The rest wait, with their timers held. |
|
|
75
|
+
| `duration` | `number` | `5000` | Default auto-close delay. |
|
|
76
|
+
| `closeButton` | `boolean` | `true` | Close button on dismissible toasts. |
|
|
77
|
+
| `hotkey` | `string[]` | `["altKey", "KeyT"]` | Moves focus to the notifications. `[]` turns it off. |
|
|
78
|
+
| `icons` | `Partial<Record<ToastType, ReactNode>>` | — | Replace icons per type; `null` hides one. |
|
|
79
|
+
| `store` | `ToastStore` | the `toast()` store | For a separate notification area. |
|
|
80
|
+
| `dir` | `"ltr"` \| `"rtl"` \| `"auto"` | inherited | Text direction. |
|
|
81
|
+
| `className`, `classNames`, `style` | — | — | Slots: `region`, `list`, `toast`, `icon`, `content`, `title`, `description`, `actions`, `action`, `cancel`, `close`. |
|
|
82
|
+
| `localeText` | `Partial<ToasterLocaleText>` | English | `regionLabel(hotkey)`, `close`. |
|
|
83
|
+
|
|
84
|
+
## Behavior
|
|
85
|
+
|
|
86
|
+
- Timers pause while the pointer is over the toasts, while focus is inside them,
|
|
87
|
+
and while the browser tab is hidden.
|
|
88
|
+
- Toasts beyond `limit` wait with their full time and appear as others close.
|
|
89
|
+
- Toasts render in a portal on `<body>`, so no parent's overflow, transform or
|
|
90
|
+
z-index can hide them.
|
|
91
|
+
|
|
92
|
+
## Keyboard and accessibility
|
|
93
|
+
|
|
94
|
+
| Keys | Action |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| **Alt + T** | Move focus to the notifications (configurable with `hotkey`). |
|
|
97
|
+
| **Tab** | Move between toasts and their buttons. |
|
|
98
|
+
| **Escape** | Dismiss the focused toast. |
|
|
99
|
+
| swipe sideways | Dismiss (touch, pen or mouse). |
|
|
100
|
+
|
|
101
|
+
The list is a polite live region, always in the page, so new toasts are read out
|
|
102
|
+
without interrupting. Error toasts use `role="alert"` and interrupt. The region is
|
|
103
|
+
a labelled landmark ("Notifications (Alt+T)").
|
|
104
|
+
|
|
105
|
+
## Theming
|
|
106
|
+
|
|
107
|
+
Override `--ts-*` variables (they default to the grid's `--dg-*`). Toasts render
|
|
108
|
+
on `<body>`, so set shared `--dg-*` values on `:root`:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
.ts-region { --ts-width: 420px; --ts-success: #059669; --ts-radius: 12px; }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
State attributes: `data-position` on the region; `data-type`, `data-state`
|
|
115
|
+
(`open` / `closing`), `data-custom`, `data-swiping` on toasts.
|
|
116
|
+
|
|
117
|
+
## Headless use
|
|
118
|
+
|
|
119
|
+
`useToasts(store?)` returns the live snapshot (`toasts`, `paused`, `limit`) for a
|
|
120
|
+
custom UI, and `visibleToasts(snapshot)` picks the ones to show.
|
|
121
|
+
`createToastStore()` and `createToastApi(store)` build separate instances. All of
|
|
122
|
+
`core` runs without React.
|
|
123
|
+
|
|
124
|
+
## Known limits
|
|
125
|
+
|
|
126
|
+
- One stack per `<Toaster />`; toasts don't collapse into an expandable pile.
|
|
127
|
+
- `toast()` does nothing during a server render or in server code. Call it in
|
|
128
|
+
the browser, e.g. after a Server Action resolves.
|