@pindoba/astro-toast 0.0.0-alpha-20260426220647
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/LICENSE +12 -0
- package/index.ts +6 -0
- package/package.json +55 -0
- package/src/Toaster.astro +435 -0
- package/src/demos/actions.astro +39 -0
- package/src/demos/dedupe.astro +26 -0
- package/src/demos/default.astro +45 -0
- package/src/demos/emphasis.astro +43 -0
- package/src/demos/history.astro +76 -0
- package/src/demos/index.ts +8 -0
- package/src/demos/positions.astro +34 -0
- package/src/demos/promise.astro +47 -0
- package/src/demos/stacked.astro +29 -0
- package/src/runtime.ts +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright 2025 Eduardo Lopes
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
8
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
9
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
10
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
11
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
12
|
+
DEALINGS IN THE SOFTWARE.
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pindoba/astro-toast",
|
|
3
|
+
"version": "0.0.0-alpha-20260426220647",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts",
|
|
9
|
+
"./runtime": "./src/runtime.ts",
|
|
10
|
+
"./demos": "./src/demos/index.ts",
|
|
11
|
+
"./demos/default": "./src/demos/default.astro",
|
|
12
|
+
"./demos/emphasis": "./src/demos/emphasis.astro",
|
|
13
|
+
"./demos/promise": "./src/demos/promise.astro",
|
|
14
|
+
"./demos/positions": "./src/demos/positions.astro",
|
|
15
|
+
"./demos/stacked": "./src/demos/stacked.astro",
|
|
16
|
+
"./demos/dedupe": "./src/demos/dedupe.astro",
|
|
17
|
+
"./demos/actions": "./src/demos/actions.astro",
|
|
18
|
+
"./demos/history": "./src/demos/history.astro"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"index.ts"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"astro-component"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@astrojs/check": "^0.9.8",
|
|
29
|
+
"astro": "^6.1.9",
|
|
30
|
+
"typescript": "^6.0.3"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"astro": "^4.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@iconify-json/lucide": "^1.2.103",
|
|
37
|
+
"astro-icon": "^1.1.5",
|
|
38
|
+
"@pindoba/astro-button": "0.0.0-alpha-20260426220647",
|
|
39
|
+
"@pindoba/astro-dialog": "0.0.0-alpha-20260426220647",
|
|
40
|
+
"@pindoba/astro-group": "0.0.0-alpha-20260426220647",
|
|
41
|
+
"@pindoba/astro-radio": "0.0.0-alpha-20260426220647",
|
|
42
|
+
"@pindoba/core-toast": "0.0.0-alpha-20260426220647",
|
|
43
|
+
"@pindoba/styles-toast": "0.0.0-alpha-20260426220647",
|
|
44
|
+
"@pindoba/styled-system": "0.0.0-alpha-20260426220647",
|
|
45
|
+
"@pindoba/astro-alert": "0.0.0-alpha-20260426220647",
|
|
46
|
+
"@pindoba/astro-badge": "0.0.0-alpha-20260426220647"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"check": "astro check",
|
|
53
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Alert from "@pindoba/astro-alert";
|
|
3
|
+
import Badge from "@pindoba/astro-badge";
|
|
4
|
+
import Button from "@pindoba/astro-button";
|
|
5
|
+
import Dialog from "@pindoba/astro-dialog";
|
|
6
|
+
import DialogManagerScript from "@pindoba/astro-dialog/script";
|
|
7
|
+
import Group from "@pindoba/astro-group";
|
|
8
|
+
import Radio from "@pindoba/astro-radio";
|
|
9
|
+
import { Icon } from "astro-icon/components";
|
|
10
|
+
import { connectToaster } from "@pindoba/core-toast";
|
|
11
|
+
import type { ToastPosition, ToasterConfig } from "@pindoba/core-toast";
|
|
12
|
+
import type { AlertFeedback } from "@pindoba/astro-alert";
|
|
13
|
+
|
|
14
|
+
export interface ToasterProps extends Partial<ToasterConfig> {
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
position = "bottom-right",
|
|
20
|
+
stacked = true,
|
|
21
|
+
expandOnHover = true,
|
|
22
|
+
duration = 4000,
|
|
23
|
+
limit = 5,
|
|
24
|
+
pauseOnHover = true,
|
|
25
|
+
swipeToDismiss = true,
|
|
26
|
+
showProgressBar = true,
|
|
27
|
+
enableHistory = false,
|
|
28
|
+
maxHistoryItems = 50,
|
|
29
|
+
historyDrawer = "right",
|
|
30
|
+
defaultEmphasis = "secondary",
|
|
31
|
+
storageKey,
|
|
32
|
+
class: className,
|
|
33
|
+
}: ToasterProps = Astro.props;
|
|
34
|
+
|
|
35
|
+
const config: Partial<ToasterConfig> = {
|
|
36
|
+
position,
|
|
37
|
+
stacked,
|
|
38
|
+
expandOnHover,
|
|
39
|
+
duration,
|
|
40
|
+
limit,
|
|
41
|
+
pauseOnHover,
|
|
42
|
+
swipeToDismiss,
|
|
43
|
+
showProgressBar,
|
|
44
|
+
enableHistory,
|
|
45
|
+
maxHistoryItems,
|
|
46
|
+
historyDrawer,
|
|
47
|
+
defaultEmphasis,
|
|
48
|
+
storageKey,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const api = connectToaster({ class: className });
|
|
52
|
+
const positions: ToastPosition[] = [
|
|
53
|
+
"top-left",
|
|
54
|
+
"top-right",
|
|
55
|
+
"top-center",
|
|
56
|
+
"bottom-left",
|
|
57
|
+
"bottom-right",
|
|
58
|
+
"bottom-center",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const types = [
|
|
62
|
+
"primary",
|
|
63
|
+
"success",
|
|
64
|
+
"danger",
|
|
65
|
+
"warning",
|
|
66
|
+
"neutral",
|
|
67
|
+
"loading",
|
|
68
|
+
] as const;
|
|
69
|
+
const typeFeedbackMap: Record<(typeof types)[number], AlertFeedback> = {
|
|
70
|
+
primary: "default",
|
|
71
|
+
success: "success",
|
|
72
|
+
danger: "danger",
|
|
73
|
+
warning: "warning",
|
|
74
|
+
neutral: "neutral",
|
|
75
|
+
loading: "loading",
|
|
76
|
+
};
|
|
77
|
+
// Badge has no "loading" — fall back to primary for the dedupe chip.
|
|
78
|
+
const badgeFeedbackMap: Record<
|
|
79
|
+
(typeof types)[number],
|
|
80
|
+
"success" | "danger" | "warning" | "neutral" | "primary"
|
|
81
|
+
> = {
|
|
82
|
+
primary: "primary",
|
|
83
|
+
success: "success",
|
|
84
|
+
danger: "danger",
|
|
85
|
+
warning: "warning",
|
|
86
|
+
neutral: "neutral",
|
|
87
|
+
loading: "primary",
|
|
88
|
+
};
|
|
89
|
+
// History items reuse Alert's feedback variants directly.
|
|
90
|
+
const historyFeedbackMap: Record<
|
|
91
|
+
(typeof types)[number],
|
|
92
|
+
"success" | "danger" | "warning" | "default" | "neutral" | "loading"
|
|
93
|
+
> = {
|
|
94
|
+
primary: "default",
|
|
95
|
+
success: "success",
|
|
96
|
+
danger: "danger",
|
|
97
|
+
warning: "warning",
|
|
98
|
+
neutral: "neutral",
|
|
99
|
+
loading: "loading",
|
|
100
|
+
};
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
/*
|
|
105
|
+
astro-icon scans the *visible* page output to build its SVG sprite — icons
|
|
106
|
+
that only appear inside <template> tags (toast row + history item templates
|
|
107
|
+
below) are skipped, so cloned templates would render as empty stamps. Force
|
|
108
|
+
the scanner to include them by mounting a hidden block of Icon refs.
|
|
109
|
+
*/
|
|
110
|
+
}
|
|
111
|
+
<div hidden aria-hidden="true">
|
|
112
|
+
<Icon name="lucide:x" />
|
|
113
|
+
<Icon name="lucide:check" />
|
|
114
|
+
<Icon name="lucide:triangle-alert" />
|
|
115
|
+
<Icon name="lucide:badge-alert" />
|
|
116
|
+
<Icon name="lucide:loader-circle" />
|
|
117
|
+
<Icon name="lucide:megaphone" />
|
|
118
|
+
<Icon name="lucide:bell" />
|
|
119
|
+
<Icon name="lucide:check-check" />
|
|
120
|
+
<Icon name="lucide:trash-2" />
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div
|
|
124
|
+
{...api.rootProps}
|
|
125
|
+
data-toaster-root
|
|
126
|
+
data-toaster-config={JSON.stringify(config)}
|
|
127
|
+
>
|
|
128
|
+
{
|
|
129
|
+
positions.map((pos) => (
|
|
130
|
+
<div
|
|
131
|
+
class={api.viewportClass(pos)}
|
|
132
|
+
data-toast-viewport={pos}
|
|
133
|
+
data-position={pos}
|
|
134
|
+
data-stacked={stacked ? "true" : "false"}
|
|
135
|
+
data-expanded="false"
|
|
136
|
+
data-empty="true"
|
|
137
|
+
style="display: none;"
|
|
138
|
+
role="region"
|
|
139
|
+
aria-label={`Toasts ${pos}`}
|
|
140
|
+
/>
|
|
141
|
+
))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
{
|
|
145
|
+
/* Templates per type × variant × emphasis. attach-toaster picks at runtime. */
|
|
146
|
+
}
|
|
147
|
+
{
|
|
148
|
+
types.flatMap((type) =>
|
|
149
|
+
(["with-title", "no-title"] as const).flatMap((variant) =>
|
|
150
|
+
(["primary", "secondary", "tertiary"] as const).map((emphasis) => (
|
|
151
|
+
<template
|
|
152
|
+
data-toast-template={type}
|
|
153
|
+
data-toast-variant={variant}
|
|
154
|
+
data-toast-emphasis={emphasis}
|
|
155
|
+
>
|
|
156
|
+
<div
|
|
157
|
+
class={api.classes.row}
|
|
158
|
+
data-state="active"
|
|
159
|
+
data-feedback={type}
|
|
160
|
+
data-emphasis={emphasis}
|
|
161
|
+
role={type === "danger" ? "alert" : "status"}
|
|
162
|
+
aria-live={type === "danger" ? "assertive" : "polite"}
|
|
163
|
+
aria-atomic="true"
|
|
164
|
+
>
|
|
165
|
+
<Alert
|
|
166
|
+
feedback={typeFeedbackMap[type]}
|
|
167
|
+
emphasis={emphasis}
|
|
168
|
+
title={
|
|
169
|
+
variant === "with-title" ? "TITLE_PLACEHOLDER" : undefined
|
|
170
|
+
}
|
|
171
|
+
passThrough={{
|
|
172
|
+
title: { props: { "data-slot": "title" } },
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
<span data-slot="content" />
|
|
176
|
+
<Fragment slot="actions">
|
|
177
|
+
<span data-slot="actions" data-action-mount />
|
|
178
|
+
<Button
|
|
179
|
+
size="xs"
|
|
180
|
+
emphasis="ghost"
|
|
181
|
+
data-slot="action-template"
|
|
182
|
+
aria-hidden="true"
|
|
183
|
+
style="display:none"
|
|
184
|
+
/>
|
|
185
|
+
<Button
|
|
186
|
+
size="xs"
|
|
187
|
+
shape="square"
|
|
188
|
+
emphasis="ghost"
|
|
189
|
+
aria-label="Dismiss"
|
|
190
|
+
data-slot="dismiss"
|
|
191
|
+
>
|
|
192
|
+
<Icon name="lucide:x" width={14} height={14} />
|
|
193
|
+
</Button>
|
|
194
|
+
</Fragment>
|
|
195
|
+
</Alert>
|
|
196
|
+
<span
|
|
197
|
+
class={api.classes.dedupeBadge}
|
|
198
|
+
data-slot="dedupe"
|
|
199
|
+
data-count="1"
|
|
200
|
+
>
|
|
201
|
+
<Badge
|
|
202
|
+
size="sm"
|
|
203
|
+
feedback={badgeFeedbackMap[type]}
|
|
204
|
+
emphasis="primary"
|
|
205
|
+
>
|
|
206
|
+
<span data-slot="dedupe-content" />
|
|
207
|
+
</Badge>
|
|
208
|
+
</span>
|
|
209
|
+
{showProgressBar && (
|
|
210
|
+
<span class={api.classes.progressBar} data-slot="progress" />
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</template>
|
|
214
|
+
)),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
{/* Custom toast template (no Alert wrapper) */}
|
|
220
|
+
<template data-toast-template="custom">
|
|
221
|
+
<div
|
|
222
|
+
class={api.classes.row}
|
|
223
|
+
data-state="active"
|
|
224
|
+
data-feedback="neutral"
|
|
225
|
+
role="status"
|
|
226
|
+
aria-live="polite"
|
|
227
|
+
>
|
|
228
|
+
<div data-slot="content"></div>
|
|
229
|
+
<span class={api.classes.dedupeBadge} data-slot="dedupe" data-count="1">
|
|
230
|
+
<Badge size="sm" feedback="neutral" emphasis="primary">
|
|
231
|
+
<span data-slot="dedupe-content"></span>
|
|
232
|
+
</Badge>
|
|
233
|
+
</span>
|
|
234
|
+
{
|
|
235
|
+
showProgressBar && (
|
|
236
|
+
<span class={api.classes.progressBar} data-slot="progress" />
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
</div>
|
|
240
|
+
</template>
|
|
241
|
+
|
|
242
|
+
{/* Toast Center dialog (always rendered when enableHistory) */}
|
|
243
|
+
{enableHistory && <DialogManagerScript />}
|
|
244
|
+
{
|
|
245
|
+
enableHistory && (
|
|
246
|
+
<Dialog
|
|
247
|
+
id="pindoba-toast-center"
|
|
248
|
+
title="Notifications"
|
|
249
|
+
drawer={historyDrawer || undefined}
|
|
250
|
+
data-toast-center-dialog
|
|
251
|
+
passThrough={{
|
|
252
|
+
wrapper: { style: { minHeight: 0, overflow: "hidden" } },
|
|
253
|
+
content: {
|
|
254
|
+
style: { minHeight: 0, overflow: "hidden", flex: "1 1 auto" },
|
|
255
|
+
},
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
<div class={api.classes.centerBody}>
|
|
259
|
+
<div class={api.classes.centerTabs} role="tablist">
|
|
260
|
+
<Group>
|
|
261
|
+
<Radio
|
|
262
|
+
id="pindoba-toast-center-tab-all"
|
|
263
|
+
appearance="button"
|
|
264
|
+
size="sm"
|
|
265
|
+
name="pindoba-toast-center-filter"
|
|
266
|
+
value="all"
|
|
267
|
+
checked
|
|
268
|
+
data-toast-center-tab="all"
|
|
269
|
+
>
|
|
270
|
+
All
|
|
271
|
+
<Badge
|
|
272
|
+
size="sm"
|
|
273
|
+
feedback="neutral"
|
|
274
|
+
class={api.classes.centerCount}
|
|
275
|
+
>
|
|
276
|
+
<span data-toast-center-count="all">0</span>
|
|
277
|
+
</Badge>
|
|
278
|
+
</Radio>
|
|
279
|
+
<Radio
|
|
280
|
+
id="pindoba-toast-center-tab-unread"
|
|
281
|
+
appearance="button"
|
|
282
|
+
size="sm"
|
|
283
|
+
name="pindoba-toast-center-filter"
|
|
284
|
+
value="unread"
|
|
285
|
+
data-toast-center-tab="unread"
|
|
286
|
+
>
|
|
287
|
+
Unread
|
|
288
|
+
<Badge
|
|
289
|
+
size="sm"
|
|
290
|
+
feedback="primary"
|
|
291
|
+
class={api.classes.centerCount}
|
|
292
|
+
>
|
|
293
|
+
<span data-toast-center-count="unread">0</span>
|
|
294
|
+
</Badge>
|
|
295
|
+
</Radio>
|
|
296
|
+
</Group>
|
|
297
|
+
<div class={api.classes.centerActions}>
|
|
298
|
+
<Button emphasis="ghost" size="sm" data-toast-center-mark-all>
|
|
299
|
+
<Icon name="lucide:check-check" width={16} height={16} />
|
|
300
|
+
Mark all read
|
|
301
|
+
</Button>
|
|
302
|
+
<Button emphasis="ghost" size="sm" data-toast-center-clear>
|
|
303
|
+
<Icon name="lucide:trash-2" width={16} height={16} />
|
|
304
|
+
Clear
|
|
305
|
+
</Button>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<ul class={api.classes.centerList} data-toast-center-list />
|
|
309
|
+
<p class={api.classes.centerEmpty} data-toast-center-empty>
|
|
310
|
+
No notifications.
|
|
311
|
+
</p>
|
|
312
|
+
</div>
|
|
313
|
+
{types.flatMap((type) =>
|
|
314
|
+
(["with-title", "no-title"] as const).flatMap((variant) =>
|
|
315
|
+
(["primary", "secondary", "tertiary"] as const).map((emphasis) => (
|
|
316
|
+
<template
|
|
317
|
+
data-toast-center-item-template={type}
|
|
318
|
+
data-toast-center-item-variant={variant}
|
|
319
|
+
data-toast-center-item-emphasis={emphasis}
|
|
320
|
+
>
|
|
321
|
+
<li class={api.classes.centerItem} data-has-recurrences="false">
|
|
322
|
+
{variant === "with-title" ? (
|
|
323
|
+
<Alert
|
|
324
|
+
feedback={historyFeedbackMap[type]}
|
|
325
|
+
emphasis={emphasis}
|
|
326
|
+
title="TITLE_PLACEHOLDER"
|
|
327
|
+
passThrough={{
|
|
328
|
+
title: { props: { "data-history-slot": "title" } },
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
<span data-history-slot="content" />
|
|
332
|
+
<div
|
|
333
|
+
class={api.classes.centerRecurrences}
|
|
334
|
+
data-history-slot="recurrences"
|
|
335
|
+
>
|
|
336
|
+
<p
|
|
337
|
+
class={api.classes.centerRecurrencesSummary}
|
|
338
|
+
data-history-slot="recurrences-summary"
|
|
339
|
+
/>
|
|
340
|
+
<ul
|
|
341
|
+
class={api.classes.centerRecurrenceList}
|
|
342
|
+
data-history-slot="recurrences-list"
|
|
343
|
+
/>
|
|
344
|
+
</div>
|
|
345
|
+
<Fragment slot="actions">
|
|
346
|
+
<span
|
|
347
|
+
class={api.classes.centerLatestTime}
|
|
348
|
+
data-history-slot="latest-time"
|
|
349
|
+
/>
|
|
350
|
+
</Fragment>
|
|
351
|
+
</Alert>
|
|
352
|
+
) : (
|
|
353
|
+
<Alert
|
|
354
|
+
feedback={historyFeedbackMap[type]}
|
|
355
|
+
emphasis={emphasis}
|
|
356
|
+
>
|
|
357
|
+
<span data-history-slot="content" />
|
|
358
|
+
<div
|
|
359
|
+
class={api.classes.centerRecurrences}
|
|
360
|
+
data-history-slot="recurrences"
|
|
361
|
+
>
|
|
362
|
+
<p
|
|
363
|
+
class={api.classes.centerRecurrencesSummary}
|
|
364
|
+
data-history-slot="recurrences-summary"
|
|
365
|
+
/>
|
|
366
|
+
<ul
|
|
367
|
+
class={api.classes.centerRecurrenceList}
|
|
368
|
+
data-history-slot="recurrences-list"
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
371
|
+
<Fragment slot="actions">
|
|
372
|
+
<span
|
|
373
|
+
class={api.classes.centerLatestTime}
|
|
374
|
+
data-history-slot="latest-time"
|
|
375
|
+
/>
|
|
376
|
+
</Fragment>
|
|
377
|
+
</Alert>
|
|
378
|
+
)}
|
|
379
|
+
<span
|
|
380
|
+
class={api.classes.centerUnreadDot}
|
|
381
|
+
data-history-slot="unread-dot"
|
|
382
|
+
/>
|
|
383
|
+
</li>
|
|
384
|
+
</template>
|
|
385
|
+
)),
|
|
386
|
+
),
|
|
387
|
+
)}
|
|
388
|
+
<template data-history-recurrence-item>
|
|
389
|
+
<li
|
|
390
|
+
class={api.classes.centerRecurrenceItem}
|
|
391
|
+
data-history-slot="recurrence-row"
|
|
392
|
+
>
|
|
393
|
+
<span data-history-slot="recurrence-time" />
|
|
394
|
+
</li>
|
|
395
|
+
</template>
|
|
396
|
+
</Dialog>
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
<script>
|
|
402
|
+
import {
|
|
403
|
+
attachToaster,
|
|
404
|
+
markAllAsRead,
|
|
405
|
+
clearHistory,
|
|
406
|
+
} from "@pindoba/core-toast";
|
|
407
|
+
import type { ToasterConfig } from "@pindoba/core-toast";
|
|
408
|
+
|
|
409
|
+
// Multiple Toasters share one store — attach each one but only the first
|
|
410
|
+
// claim wins (handled inside attachToaster, which also portals viewports
|
|
411
|
+
// and the Toast Center dialog to document.body). Skip roots that don't
|
|
412
|
+
// carry an Astro config attribute — those belong to Svelte/React Toasters
|
|
413
|
+
// that configure themselves via their own onMount/effect.
|
|
414
|
+
const roots = document.querySelectorAll<HTMLElement>(
|
|
415
|
+
"[data-toaster-root][data-toaster-config]",
|
|
416
|
+
);
|
|
417
|
+
for (const root of Array.from(roots)) {
|
|
418
|
+
const raw = root.getAttribute("data-toaster-config");
|
|
419
|
+
let config: Partial<ToasterConfig> = {};
|
|
420
|
+
try {
|
|
421
|
+
config = raw ? (JSON.parse(raw) as Partial<ToasterConfig>) : {};
|
|
422
|
+
} catch {
|
|
423
|
+
/* ignore */
|
|
424
|
+
}
|
|
425
|
+
attachToaster(root, config);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Bind Toast Center action buttons. Close is provided by the pindoba Dialog.
|
|
429
|
+
document
|
|
430
|
+
.querySelector("[data-toast-center-mark-all]")
|
|
431
|
+
?.addEventListener("click", () => markAllAsRead());
|
|
432
|
+
document
|
|
433
|
+
.querySelector("[data-toast-center-clear]")
|
|
434
|
+
?.addEventListener("click", () => clearHistory());
|
|
435
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row" })}>
|
|
8
|
+
<Button data-toast-action="with-action">With action button</Button>
|
|
9
|
+
<Button data-toast-action="no-title">Title-less</Button>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<Toaster position="bottom-right" />
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { toast } from "@pindoba/astro-toast/runtime";
|
|
16
|
+
|
|
17
|
+
document
|
|
18
|
+
.querySelectorAll<HTMLElement>("[data-toast-action]")
|
|
19
|
+
.forEach((btn) => {
|
|
20
|
+
btn.addEventListener("click", () => {
|
|
21
|
+
const action = btn.dataset.toastAction;
|
|
22
|
+
if (action === "with-action") {
|
|
23
|
+
toast.neutral({
|
|
24
|
+
title: "Email archived",
|
|
25
|
+
content: "Tap undo to restore it.",
|
|
26
|
+
action: {
|
|
27
|
+
label: "Undo",
|
|
28
|
+
onClick: () => toast.success({ title: "Restored" }),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
} else if (action === "no-title") {
|
|
32
|
+
toast.warning({
|
|
33
|
+
content:
|
|
34
|
+
"Title-less toasts use the row layout, like a compact alert.",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row" })}>
|
|
8
|
+
<Button data-toast-dedupe>Fire same id</Button>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<Toaster position="bottom-right" />
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import { toast } from "@pindoba/astro-toast/runtime";
|
|
15
|
+
|
|
16
|
+
document
|
|
17
|
+
.querySelector("[data-toast-dedupe]")
|
|
18
|
+
?.addEventListener("click", () => {
|
|
19
|
+
toast.success({
|
|
20
|
+
id: "dedupe-demo",
|
|
21
|
+
title: "Saved",
|
|
22
|
+
content:
|
|
23
|
+
"Same id — count badge ticks up and the toast pops to the front.",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row", wrap: "wrap" })}>
|
|
8
|
+
<Button data-toast-demo="primary">Primary</Button>
|
|
9
|
+
<Button data-toast-demo="success">Success</Button>
|
|
10
|
+
<Button data-toast-demo="danger">Danger</Button>
|
|
11
|
+
<Button data-toast-demo="warning">Warning</Button>
|
|
12
|
+
<Button data-toast-demo="neutral">Neutral</Button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<Toaster position="bottom-right" />
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { toast } from "@pindoba/astro-toast/runtime";
|
|
19
|
+
|
|
20
|
+
document.querySelectorAll<HTMLElement>("[data-toast-demo]").forEach((btn) => {
|
|
21
|
+
btn.addEventListener("click", () => {
|
|
22
|
+
const type = btn.dataset.toastDemo;
|
|
23
|
+
if (type === "primary") {
|
|
24
|
+
toast.primary({
|
|
25
|
+
title: "New mention",
|
|
26
|
+
content: "Jane tagged you in a doc.",
|
|
27
|
+
});
|
|
28
|
+
} else if (type === "success") {
|
|
29
|
+
toast.success({ title: "Saved", content: "Your changes are live." });
|
|
30
|
+
} else if (type === "danger") {
|
|
31
|
+
toast.danger({
|
|
32
|
+
title: "Couldn't save",
|
|
33
|
+
content: "Network is unreachable.",
|
|
34
|
+
});
|
|
35
|
+
} else if (type === "warning") {
|
|
36
|
+
toast.warning({
|
|
37
|
+
title: "Heads up",
|
|
38
|
+
content: "Disk is almost full.",
|
|
39
|
+
});
|
|
40
|
+
} else if (type === "neutral") {
|
|
41
|
+
toast.neutral({ title: "FYI", content: "Cache rebuilt." });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row", wrap: "wrap" })}>
|
|
8
|
+
<Button data-emphasis-demo="default">Default (secondary)</Button>
|
|
9
|
+
<Button data-emphasis-demo="primary">Primary (loud)</Button>
|
|
10
|
+
<Button data-emphasis-demo="tertiary">Tertiary (quietest)</Button>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<Toaster position="bottom-right" />
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
import { toast } from "@pindoba/astro-toast/runtime";
|
|
17
|
+
|
|
18
|
+
document
|
|
19
|
+
.querySelectorAll<HTMLElement>("[data-emphasis-demo]")
|
|
20
|
+
.forEach((btn) => {
|
|
21
|
+
btn.addEventListener("click", () => {
|
|
22
|
+
const variant = btn.dataset.emphasisDemo;
|
|
23
|
+
if (variant === "default") {
|
|
24
|
+
toast.success({
|
|
25
|
+
title: "Saved",
|
|
26
|
+
content: "Default toasts use secondary emphasis.",
|
|
27
|
+
});
|
|
28
|
+
} else if (variant === "primary") {
|
|
29
|
+
toast.danger({
|
|
30
|
+
title: "Outage detected",
|
|
31
|
+
content: "Reserve primary for can't-miss messages.",
|
|
32
|
+
emphasis: "primary",
|
|
33
|
+
});
|
|
34
|
+
} else if (variant === "tertiary") {
|
|
35
|
+
toast.neutral({
|
|
36
|
+
title: "Heads up",
|
|
37
|
+
content: "Tertiary is the quietest variant.",
|
|
38
|
+
emphasis: "tertiary",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row" })}>
|
|
8
|
+
<Button data-toast-history="push">Push toast</Button>
|
|
9
|
+
<Button data-toast-history="open">
|
|
10
|
+
Open notifications (<span data-history-unread-count>0</span>)
|
|
11
|
+
</Button>
|
|
12
|
+
<Button data-toast-history="mark-read">Mark all read</Button>
|
|
13
|
+
<Button data-toast-history="clear">Clear history</Button>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<Toaster
|
|
17
|
+
position="bottom-right"
|
|
18
|
+
enableHistory={true}
|
|
19
|
+
storageKey="pindoba-toast-demo"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import {
|
|
24
|
+
toast,
|
|
25
|
+
toggleCenter,
|
|
26
|
+
markAllAsRead,
|
|
27
|
+
clearHistory,
|
|
28
|
+
$unreadCount,
|
|
29
|
+
} from "@pindoba/astro-toast/runtime";
|
|
30
|
+
|
|
31
|
+
$unreadCount.subscribe((count) => {
|
|
32
|
+
document
|
|
33
|
+
.querySelectorAll<HTMLElement>("[data-history-unread-count]")
|
|
34
|
+
.forEach((el) => (el.textContent = String(count)));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
type DemoToast = {
|
|
38
|
+
type: "primary" | "success" | "danger" | "warning" | "neutral";
|
|
39
|
+
title: string;
|
|
40
|
+
content: string;
|
|
41
|
+
};
|
|
42
|
+
const samples: DemoToast[] = [
|
|
43
|
+
{ type: "success", title: "Saved", content: "Changes published." },
|
|
44
|
+
{ type: "danger", title: "Failed", content: "Unable to reach server." },
|
|
45
|
+
{ type: "warning", title: "Heads up", content: "Disk almost full." },
|
|
46
|
+
{ type: "neutral", title: "FYI", content: "Cache rebuilt." },
|
|
47
|
+
{
|
|
48
|
+
type: "primary",
|
|
49
|
+
title: "New mention",
|
|
50
|
+
content: "Jane tagged you in a doc.",
|
|
51
|
+
},
|
|
52
|
+
{ type: "success", title: "Deployed", content: "Build finished in 12s." },
|
|
53
|
+
{ type: "warning", title: "Slow query", content: "Check the indexes." },
|
|
54
|
+
{ type: "danger", title: "Payment failed", content: "Card was declined." },
|
|
55
|
+
{ type: "neutral", title: "Reminder", content: "Standup in 5 minutes." },
|
|
56
|
+
{ type: "primary", title: "Sync complete", content: "Latest data is in." },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
document
|
|
60
|
+
.querySelectorAll<HTMLElement>("[data-toast-history]")
|
|
61
|
+
.forEach((btn) => {
|
|
62
|
+
btn.addEventListener("click", () => {
|
|
63
|
+
const action = btn.dataset.toastHistory;
|
|
64
|
+
if (action === "push") {
|
|
65
|
+
const sample = samples[Math.floor(Math.random() * samples.length)]!;
|
|
66
|
+
toast[sample.type]({ title: sample.title, content: sample.content });
|
|
67
|
+
} else if (action === "open") {
|
|
68
|
+
toggleCenter(true);
|
|
69
|
+
} else if (action === "mark-read") {
|
|
70
|
+
markAllAsRead();
|
|
71
|
+
} else if (action === "clear") {
|
|
72
|
+
clearHistory();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as Default } from "./default.astro";
|
|
2
|
+
export { default as Emphasis } from "./emphasis.astro";
|
|
3
|
+
export { default as Promise } from "./promise.astro";
|
|
4
|
+
export { default as Positions } from "./positions.astro";
|
|
5
|
+
export { default as Stacked } from "./stacked.astro";
|
|
6
|
+
export { default as Dedupe } from "./dedupe.astro";
|
|
7
|
+
export { default as Actions } from "./actions.astro";
|
|
8
|
+
export { default as History } from "./history.astro";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { grid } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={grid({ columns: 3, gap: "sm" })}>
|
|
8
|
+
<Button data-toast-position="top-left">Top left</Button>
|
|
9
|
+
<Button data-toast-position="top-center">Top center</Button>
|
|
10
|
+
<Button data-toast-position="top-right">Top right</Button>
|
|
11
|
+
<Button data-toast-position="bottom-left">Bottom left</Button>
|
|
12
|
+
<Button data-toast-position="bottom-center">Bottom center</Button>
|
|
13
|
+
<Button data-toast-position="bottom-right">Bottom right</Button>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<Toaster position="bottom-right" />
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { toast } from "@pindoba/astro-toast/runtime";
|
|
20
|
+
import type { ToastPosition } from "@pindoba/astro-toast/runtime";
|
|
21
|
+
|
|
22
|
+
document
|
|
23
|
+
.querySelectorAll<HTMLElement>("[data-toast-position]")
|
|
24
|
+
.forEach((btn) => {
|
|
25
|
+
btn.addEventListener("click", () => {
|
|
26
|
+
const position = btn.dataset.toastPosition as ToastPosition;
|
|
27
|
+
toast.neutral({
|
|
28
|
+
title: position,
|
|
29
|
+
content: "Per-toast position override.",
|
|
30
|
+
position,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row" })}>
|
|
8
|
+
<Button data-toast-demo="promise-resolve">Resolve in 1.5s</Button>
|
|
9
|
+
<Button data-toast-demo="promise-reject">Reject in 1.5s</Button>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<Toaster position="bottom-right" />
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { toast } from "@pindoba/astro-toast/runtime";
|
|
16
|
+
|
|
17
|
+
document.querySelectorAll<HTMLElement>("[data-toast-demo]").forEach((btn) => {
|
|
18
|
+
btn.addEventListener("click", () => {
|
|
19
|
+
const action = btn.dataset.toastDemo;
|
|
20
|
+
if (action === "promise-resolve") {
|
|
21
|
+
toast.promise(
|
|
22
|
+
new Promise<string>((res) => setTimeout(() => res("ok"), 1500)),
|
|
23
|
+
{
|
|
24
|
+
loading: "Uploading…",
|
|
25
|
+
success: "Uploaded",
|
|
26
|
+
error: "Upload failed",
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
} else if (action === "promise-reject") {
|
|
30
|
+
toast
|
|
31
|
+
.promise(
|
|
32
|
+
new Promise<never>((_, rej) =>
|
|
33
|
+
setTimeout(() => rej(new Error("503")), 1500),
|
|
34
|
+
),
|
|
35
|
+
{
|
|
36
|
+
loading: "Uploading…",
|
|
37
|
+
success: "Uploaded",
|
|
38
|
+
error: "Upload failed — try again",
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
.catch(() => {
|
|
42
|
+
/* swallow — toast already showed */
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from "@pindoba/astro-button";
|
|
3
|
+
import Toaster from "../Toaster.astro";
|
|
4
|
+
import { stack } from "@pindoba/styled-system/patterns";
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div class={stack({ gap: "sm", direction: "row" })}>
|
|
8
|
+
<Button data-toast-spawn>Spawn random toast</Button>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<Toaster position="bottom-right" stacked={true} expandOnHover={true} />
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import { toast, getToastStore } from "@pindoba/astro-toast/runtime";
|
|
15
|
+
|
|
16
|
+
document
|
|
17
|
+
.querySelector("[data-toast-spawn]")
|
|
18
|
+
?.addEventListener("click", () => {
|
|
19
|
+
// Reapply the demo's Toaster config — needed when several demos share
|
|
20
|
+
// one document and the user wants to see this demo's settings now.
|
|
21
|
+
getToastStore().setConfig({ stacked: true, expandOnHover: true });
|
|
22
|
+
const types = ["success", "danger", "warning", "neutral"] as const;
|
|
23
|
+
const type = types[Math.floor(Math.random() * types.length)];
|
|
24
|
+
toast[type]({
|
|
25
|
+
title: type,
|
|
26
|
+
content: `Spawned at ${new Date().toLocaleTimeString()}`,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
</script>
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Astro runtime exports — consumers import from `@pindoba/astro-toast/runtime`.
|
|
3
|
+
* Includes the imperative `toast()` API, history/center handles, and the
|
|
4
|
+
* `getToaster()` accessor.
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
toast,
|
|
8
|
+
attachToaster,
|
|
9
|
+
getToaster,
|
|
10
|
+
getToastStore,
|
|
11
|
+
$history,
|
|
12
|
+
$isCenterOpen,
|
|
13
|
+
$unreadCount,
|
|
14
|
+
markAsRead,
|
|
15
|
+
markAllAsRead,
|
|
16
|
+
clearHistory,
|
|
17
|
+
toggleCenter,
|
|
18
|
+
getToastHistoryHandles,
|
|
19
|
+
} from "@pindoba/core-toast";
|
|
20
|
+
export type {
|
|
21
|
+
ToastInput,
|
|
22
|
+
ToastEntry,
|
|
23
|
+
ToastType,
|
|
24
|
+
ToastPosition,
|
|
25
|
+
ToastContent,
|
|
26
|
+
ToastContentRenderer,
|
|
27
|
+
ToastAction,
|
|
28
|
+
ToasterConfig,
|
|
29
|
+
HistoryEntry,
|
|
30
|
+
UseToastHistory,
|
|
31
|
+
} from "@pindoba/core-toast";
|