@beautinique/frontend-components 0.0.0-stage → 1.0.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.
Files changed (75) hide show
  1. package/README.md +100 -2
  2. package/dist/index.cjs +4 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2 -0
  5. package/dist/index.d.cts.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/button.d.cts +3 -0
  11. package/dist/lib/button.d.cts.map +1 -0
  12. package/dist/lib/button.d.ts +3 -0
  13. package/dist/lib/button.d.ts.map +1 -0
  14. package/dist/lib/constants.d.cts +5 -0
  15. package/dist/lib/constants.d.cts.map +1 -0
  16. package/dist/lib/constants.d.ts +5 -0
  17. package/dist/lib/constants.d.ts.map +1 -0
  18. package/dist/lib/tooltip.d.cts +6 -0
  19. package/dist/lib/tooltip.d.cts.map +1 -0
  20. package/dist/lib/tooltip.d.ts +6 -0
  21. package/dist/lib/tooltip.d.ts.map +1 -0
  22. package/dist/toast/ToastContainer.d.cts +6 -0
  23. package/dist/toast/ToastContainer.d.cts.map +1 -0
  24. package/dist/toast/ToastContainer.d.ts +6 -0
  25. package/dist/toast/ToastContainer.d.ts.map +1 -0
  26. package/dist/toast/index.cjs +618 -0
  27. package/dist/toast/index.cjs.map +1 -0
  28. package/dist/toast/index.d.cts +8 -0
  29. package/dist/toast/index.d.cts.map +1 -0
  30. package/dist/toast/index.d.ts +8 -0
  31. package/dist/toast/index.d.ts.map +1 -0
  32. package/dist/toast/index.js +603 -0
  33. package/dist/toast/index.js.map +1 -0
  34. package/dist/toast/toast-icons.d.cts +3 -0
  35. package/dist/toast/toast-icons.d.cts.map +1 -0
  36. package/dist/toast/toast-icons.d.ts +3 -0
  37. package/dist/toast/toast-icons.d.ts.map +1 -0
  38. package/dist/toast/toast.store.d.cts +4 -0
  39. package/dist/toast/toast.store.d.cts.map +1 -0
  40. package/dist/toast/toast.store.d.ts +4 -0
  41. package/dist/toast/toast.store.d.ts.map +1 -0
  42. package/dist/toast/toaster.d.cts +21 -0
  43. package/dist/toast/toaster.d.cts.map +1 -0
  44. package/dist/toast/toaster.d.ts +21 -0
  45. package/dist/toast/toaster.d.ts.map +1 -0
  46. package/dist/toast/types.d.cts +92 -0
  47. package/dist/toast/types.d.cts.map +1 -0
  48. package/dist/toast/types.d.ts +92 -0
  49. package/dist/toast/types.d.ts.map +1 -0
  50. package/dist/toast/uploads.d.cts +63 -0
  51. package/dist/toast/uploads.d.cts.map +1 -0
  52. package/dist/toast/uploads.d.ts +63 -0
  53. package/dist/toast/uploads.d.ts.map +1 -0
  54. package/dist/types/component.d.cts +27 -0
  55. package/dist/types/component.d.cts.map +1 -0
  56. package/dist/types/component.d.ts +27 -0
  57. package/dist/types/component.d.ts.map +1 -0
  58. package/dist/ui/Button.d.cts +3 -0
  59. package/dist/ui/Button.d.cts.map +1 -0
  60. package/dist/ui/Button.d.ts +3 -0
  61. package/dist/ui/Button.d.ts.map +1 -0
  62. package/dist/ui/Tooltip.d.cts +3 -0
  63. package/dist/ui/Tooltip.d.cts.map +1 -0
  64. package/dist/ui/Tooltip.d.ts +3 -0
  65. package/dist/ui/Tooltip.d.ts.map +1 -0
  66. package/dist/ui/index.cjs +205 -0
  67. package/dist/ui/index.cjs.map +1 -0
  68. package/dist/ui/index.d.cts +3 -0
  69. package/dist/ui/index.d.cts.map +1 -0
  70. package/dist/ui/index.d.ts +3 -0
  71. package/dist/ui/index.d.ts.map +1 -0
  72. package/dist/ui/index.js +202 -0
  73. package/dist/ui/index.js.map +1 -0
  74. package/package.json +100 -4
  75. package/source.css +11 -0
@@ -0,0 +1,603 @@
1
+ import { nanoid } from 'nanoid';
2
+ import { create } from 'zustand';
3
+ import { addCollection, Icon } from '@iconify/react';
4
+ import { useState, useRef, useCallback, useEffect } from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+
10
+ // src/lib/constants.ts
11
+ var TOAST_TYPES = [
12
+ "success",
13
+ "error",
14
+ "warning",
15
+ "progress",
16
+ "uploads",
17
+ "loading",
18
+ "default",
19
+ "custom"
20
+ ];
21
+ var TOAST_TYPE = Object.fromEntries(TOAST_TYPES.map((type) => [type, type]));
22
+
23
+ // src/toast/toast.store.ts
24
+ var useToastStore = create((set) => ({
25
+ toasts: [],
26
+ add: /* @__PURE__ */ __name((toast) => {
27
+ const id = nanoid();
28
+ set((state) => ({ toasts: [...state.toasts, { ...toast, id }] }));
29
+ return id;
30
+ }, "add"),
31
+ update: {
32
+ progress: /* @__PURE__ */ __name((id, progress) => {
33
+ set((state) => ({
34
+ toasts: state.toasts.map((toast) => toast.id === id ? { ...toast, progress } : toast)
35
+ }));
36
+ }, "progress"),
37
+ uploadItem: /* @__PURE__ */ __name((id, itemId, patch) => {
38
+ set((state) => ({
39
+ toasts: state.toasts.map(
40
+ (toast) => toast.id === id && toast.type === TOAST_TYPE.uploads ? {
41
+ ...toast,
42
+ items: toast.items.map(
43
+ (item) => item.id === itemId ? { ...item, ...patch } : item
44
+ )
45
+ } : toast
46
+ )
47
+ }));
48
+ }, "uploadItem")
49
+ },
50
+ remove: /* @__PURE__ */ __name((id) => {
51
+ set((state) => ({ toasts: state.toasts.filter((t) => t.id !== id) }));
52
+ }, "remove")
53
+ }));
54
+ var TOAST_ICON_SETS = [
55
+ {
56
+ prefix: "solar",
57
+ width: 24,
58
+ height: 24,
59
+ icons: {
60
+ // warning and error toasts
61
+ "danger-triangle-linear": {
62
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"><path d="M5.31171 10.7615C8.23007 5.58716 9.68925 3 12 3C14.3107 3 15.7699 5.58716 18.6883 10.7615L19.0519 11.4063C21.4771 15.7061 22.6897 17.856 21.5937 19.428C20.4978 21 17.7864 21 12.3637 21H11.6363C6.21356 21 3.50217 21 2.40626 19.428C1.31034 17.856 2.52291 15.7061 4.94805 11.4063L5.31171 10.7615Z"/><path d="M12 8V13"/><path stroke-linejoin="round" d="M12 16H12.0001"/></g>'
63
+ },
64
+ // success toast
65
+ "check-circle-linear": {
66
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path stroke-linejoin="round" d="M8.5 12.5L10.5 14.5L15.5 9.5"/></g>'
67
+ },
68
+ // an upload that is waiting its turn, in an uploads toast
69
+ "clock-circle-linear": {
70
+ body: '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path stroke-linejoin="round" d="M12 8V12L14.5 14.5"/></g>'
71
+ },
72
+ // default and custom toasts
73
+ "info-circle-outline": {
74
+ body: '<g fill="currentColor"><path d="M12 17.75C12.4142 17.75 12.75 17.4142 12.75 17V11C12.75 10.5858 12.4142 10.25 12 10.25C11.5858 10.25 11.25 10.5858 11.25 11V17C11.25 17.4142 11.5858 17.75 12 17.75Z"/><path d="M12 7C12.5523 7 13 7.44772 13 8C13 8.55228 12.5523 9 12 9C11.4477 9 11 8.55228 11 8C11 7.44772 11.4477 7 12 7Z"/><path fill-rule="evenodd" d="M1.25 12C1.25 6.06294 6.06294 1.25 12 1.25C17.9371 1.25 22.75 6.06294 22.75 12C22.75 17.9371 17.9371 22.75 12 22.75C6.06294 22.75 1.25 17.9371 1.25 12ZM12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 17.1086 6.89137 21.25 12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.9371 2.75 12 2.75Z" clip-rule="evenodd"/></g>'
75
+ }
76
+ }
77
+ },
78
+ {
79
+ prefix: "beautinique",
80
+ width: 24,
81
+ height: 24,
82
+ icons: {
83
+ /*
84
+ * The spinner of the loading toast and of an upload that is going on. It turns itself, inside
85
+ * the svg (`animateTransform`, around the centre of the ring), instead of being turned by a
86
+ * css `animate-spin` on the svg: the browser then draws the ring again at the exact angle on
87
+ * every frame. A css turn rotates a picture of the whole svg, and on a screen with a scaling of
88
+ * 125% an icon of 20px is 25 device pixels, so the middle of that picture is half a pixel off
89
+ * and the ring jumps up and down a little while it turns. Do not add `animate-spin` to it.
90
+ */
91
+ "loading-spin": {
92
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 3.34A10 10 0 1 0 22 12"><animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/></path>'
93
+ }
94
+ }
95
+ },
96
+ {
97
+ prefix: "lucide",
98
+ width: 24,
99
+ height: 24,
100
+ icons: {
101
+ // the close button of a closable toast
102
+ x: {
103
+ body: '<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6L6 18M6 6l12 12"/>'
104
+ }
105
+ }
106
+ }
107
+ ];
108
+ for (const iconSet of TOAST_ICON_SETS) addCollection(iconSet);
109
+ var TOAST_ICON_NAMES = TOAST_ICON_SETS.flatMap(
110
+ ({ prefix, icons }) => Object.keys(icons).map((name) => `${prefix}:${name}`)
111
+ );
112
+
113
+ // src/lib/button.ts
114
+ var getButtonCss = /* @__PURE__ */ __name((pattern) => {
115
+ switch (pattern) {
116
+ case "primary":
117
+ return "text-white bg-sky-blue-burst shadow-primary-btn hover:shadow-primary-btn-hover";
118
+ case "secondary":
119
+ return "text-secondary-invert bg-secondary shadow-secondary-btn hover:shadow-secondary-btn-hover";
120
+ case "tertiary":
121
+ return "text-tertiary-invert bg-tertiary shadow-tertiary-btn hover:shadow-tertiary-btn-hover";
122
+ case "outline":
123
+ return "text-primary border border-primary shadow-outline-btn hover:shadow-outline-btn-hover";
124
+ case "transparent":
125
+ default:
126
+ return "bg-transparent border border-primary/30 text-secondary";
127
+ }
128
+ }, "getButtonCss");
129
+ var Button = /* @__PURE__ */ __name(({
130
+ pattern,
131
+ content,
132
+ className = "",
133
+ leftIcon,
134
+ rightIcon,
135
+ buttonProps
136
+ }) => {
137
+ const btnCSS = getButtonCss(pattern);
138
+ const handleClick = /* @__PURE__ */ __name((e) => {
139
+ if (buttonProps?.disabled) return;
140
+ buttonProps?.onClick?.(e);
141
+ }, "handleClick");
142
+ return /* @__PURE__ */ jsxs(
143
+ "button",
144
+ {
145
+ ...buttonProps,
146
+ className: `group flex w-full cursor-pointer items-center justify-center gap-1 rounded-lg px-4 py-3 text-sm leading-4 transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-85 lg:px-5 lg:py-4 xl:text-base ${btnCSS} ${className} ${buttonProps?.className ?? ""}`,
147
+ type: buttonProps?.type ?? "button",
148
+ onClick: handleClick,
149
+ children: [
150
+ leftIcon && /* @__PURE__ */ jsx(Icon, { ...leftIcon }),
151
+ typeof content === "object" ? /* @__PURE__ */ jsx(Icon, { ...content }) : content,
152
+ rightIcon && /* @__PURE__ */ jsx(Icon, { ...rightIcon })
153
+ ]
154
+ }
155
+ );
156
+ }, "Button");
157
+
158
+ // src/toast/toaster.ts
159
+ var { add, update, remove } = useToastStore.getState();
160
+ var toaster = {
161
+ success: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.success }), "success"),
162
+ error: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.error }), "error"),
163
+ warning: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.warning }), "warning"),
164
+ default: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.default }), "default"),
165
+ loading: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.loading }), "loading"),
166
+ custom: /* @__PURE__ */ __name((data) => add(data), "custom"),
167
+ progress: {
168
+ start: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.progress }), "start"),
169
+ update: /* @__PURE__ */ __name((toastId, progress) => {
170
+ update.progress(toastId, progress);
171
+ }, "update"),
172
+ end: /* @__PURE__ */ __name((toastId) => {
173
+ remove(toastId);
174
+ }, "end")
175
+ },
176
+ uploads: {
177
+ start: /* @__PURE__ */ __name((data) => add({ ...data, type: TOAST_TYPE.uploads }), "start"),
178
+ update: /* @__PURE__ */ __name((toastId, itemId, patch) => {
179
+ update.uploadItem(toastId, itemId, patch);
180
+ }, "update")
181
+ },
182
+ remove: /* @__PURE__ */ __name((toastId) => {
183
+ remove(toastId);
184
+ }, "remove")
185
+ };
186
+ var withProgressToast = /* @__PURE__ */ __name(async ({
187
+ title,
188
+ description,
189
+ request
190
+ }) => {
191
+ const toastId = toaster.progress.start({ title, description, progress: 0 });
192
+ try {
193
+ const response = await request((event) => {
194
+ if (!event.total) return;
195
+ toaster.progress.update(toastId, Math.round(event.loaded * 100 / event.total));
196
+ });
197
+ toaster.progress.update(toastId, 100);
198
+ return response;
199
+ } finally {
200
+ toaster.progress.end(toastId);
201
+ }
202
+ }, "withProgressToast");
203
+
204
+ // src/toast/uploads.ts
205
+ var sum = /* @__PURE__ */ __name((numbers) => numbers.reduce((total, value) => total + value, 0), "sum");
206
+ var isUploadItemSettled = /* @__PURE__ */ __name(({ status }) => status === "success" || status === "error", "isUploadItemSettled");
207
+ var getUploadItemFilesDone = /* @__PURE__ */ __name(({
208
+ status,
209
+ loaded,
210
+ fileSizes
211
+ }) => {
212
+ if (status === "success") return fileSizes.length;
213
+ if (status !== "uploading") return 0;
214
+ let sent = 0;
215
+ let bytes = 0;
216
+ for (const size of fileSizes) {
217
+ bytes += size;
218
+ if (bytes > loaded) break;
219
+ sent += 1;
220
+ }
221
+ return Math.min(sent, Math.max(fileSizes.length - 1, 0));
222
+ }, "getUploadItemFilesDone");
223
+ var getUploadItemPercent = /* @__PURE__ */ __name(({
224
+ status,
225
+ loaded,
226
+ total
227
+ }) => {
228
+ if (status === "success") return 100;
229
+ if (status === "pending" || total <= 0) return 0;
230
+ return Math.min(99, Math.round(Math.min(loaded, total) * 100 / total));
231
+ }, "getUploadItemPercent");
232
+ var getUploadsSummary = /* @__PURE__ */ __name((items) => {
233
+ const failed = items.filter(({ status }) => status === "error").length;
234
+ const isSettled = items.every(isUploadItemSettled);
235
+ const bytesTotal = sum(items.map(({ total }) => total));
236
+ const bytesDone = sum(
237
+ items.map(
238
+ (item) => item.status === "success" ? item.total : Math.min(item.loaded, item.total)
239
+ )
240
+ );
241
+ let percent = bytesTotal > 0 ? Math.round(bytesDone * 100 / bytesTotal) : 0;
242
+ if (isSettled && failed === 0) percent = 100;
243
+ else percent = Math.min(percent, 99);
244
+ return {
245
+ percent,
246
+ filesDone: sum(items.map(getUploadItemFilesDone)),
247
+ filesTotal: sum(items.map(({ fileSizes }) => fileSizes.length)),
248
+ failed,
249
+ isSettled
250
+ };
251
+ }, "getUploadsSummary");
252
+ var getFormDataFiles = /* @__PURE__ */ __name((data, key = "files") => data.getAll(key).filter((value) => value instanceof File), "getFormDataFiles");
253
+ var createUploadsToast = /* @__PURE__ */ __name(({
254
+ title,
255
+ description,
256
+ items
257
+ }) => {
258
+ if (items.length === 0) return void 0;
259
+ const statuses = new Map(items.map(({ id }) => [id, "pending"]));
260
+ const toastId = toaster.uploads.start({
261
+ title,
262
+ description,
263
+ items: items.map(({ id, label, files }) => {
264
+ const fileSizes = files.map(({ size }) => size);
265
+ return { id, label, status: "pending", loaded: 0, total: sum(fileSizes), fileSizes };
266
+ })
267
+ });
268
+ const patch = /* @__PURE__ */ __name((id, changes) => {
269
+ if (changes.status) statuses.set(id, changes.status);
270
+ toaster.uploads.update(toastId, id, changes);
271
+ }, "patch");
272
+ return {
273
+ item: /* @__PURE__ */ __name((id) => ({
274
+ run: /* @__PURE__ */ __name(async (request) => {
275
+ patch(id, { status: "uploading" });
276
+ try {
277
+ const result = await request((event) => {
278
+ if (!event.total) return;
279
+ patch(id, { loaded: event.loaded, total: event.total });
280
+ });
281
+ patch(id, { status: "success" });
282
+ return result;
283
+ } catch (error) {
284
+ patch(id, { status: "error" });
285
+ throw error;
286
+ }
287
+ }, "run")
288
+ }), "item"),
289
+ end: /* @__PURE__ */ __name(() => {
290
+ for (const [id, status] of statuses) {
291
+ if (status === "pending" || status === "uploading") patch(id, { status: "error" });
292
+ }
293
+ }, "end")
294
+ };
295
+ }, "createUploadsToast");
296
+ var withUploadsToast = /* @__PURE__ */ __name(async ({
297
+ title,
298
+ description,
299
+ files,
300
+ request
301
+ }) => {
302
+ const group = createUploadsToast({
303
+ title,
304
+ description,
305
+ items: files.length > 0 ? [{ id: "files", label: description ?? title, files }] : []
306
+ });
307
+ if (!group) return request(() => void 0);
308
+ try {
309
+ return await group.item("files").run(request);
310
+ } finally {
311
+ group.end();
312
+ }
313
+ }, "withUploadsToast");
314
+ var runUploadsInToast = /* @__PURE__ */ __name(async ({
315
+ title,
316
+ description,
317
+ uploads,
318
+ run
319
+ }) => {
320
+ const group = createUploadsToast({
321
+ title,
322
+ description,
323
+ items: uploads.filter(({ files }) => files.length > 0)
324
+ });
325
+ const settled = await Promise.allSettled(uploads.map(({ id }) => run(id, group?.item(id))));
326
+ group?.end();
327
+ const results = {};
328
+ settled.forEach((result, index) => {
329
+ if (result.status === "fulfilled") results[uploads[index].id] = result.value;
330
+ });
331
+ const failure = settled.find(
332
+ (result) => result.status === "rejected"
333
+ );
334
+ if (failure) {
335
+ throw failure.reason instanceof Error ? failure.reason : new Error("Upload failed");
336
+ }
337
+ return results;
338
+ }, "runUploadsInToast");
339
+ var CircularProgress = /* @__PURE__ */ __name(({ progress, ...props }) => {
340
+ const radius = 10;
341
+ const circumference = 2 * Math.PI * radius;
342
+ const offset = circumference - progress / 100 * circumference;
343
+ return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", ...props, children: [
344
+ /* @__PURE__ */ jsx(
345
+ "circle",
346
+ {
347
+ cx: "12",
348
+ cy: "12",
349
+ r: radius,
350
+ fill: "none",
351
+ stroke: "currentColor",
352
+ strokeOpacity: 0.15,
353
+ strokeWidth: "1.5"
354
+ }
355
+ ),
356
+ /* @__PURE__ */ jsx(
357
+ "circle",
358
+ {
359
+ cx: "12",
360
+ cy: "12",
361
+ r: radius,
362
+ fill: "none",
363
+ stroke: "currentColor",
364
+ strokeWidth: "1.5",
365
+ strokeLinecap: "round",
366
+ strokeDasharray: circumference,
367
+ strokeDashoffset: offset,
368
+ transform: "rotate(-90 12 12)"
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsxs(
372
+ "text",
373
+ {
374
+ x: "12",
375
+ y: "12",
376
+ textAnchor: "middle",
377
+ dominantBaseline: "middle",
378
+ fontSize: "6",
379
+ fontWeight: "500",
380
+ fill: "currentColor",
381
+ children: [
382
+ progress,
383
+ "%"
384
+ ]
385
+ }
386
+ )
387
+ ] });
388
+ }, "CircularProgress");
389
+ var GREEN_RGB = "var(--primary-green-rgb)";
390
+ var RED_RGB = "var(--primary-red-rgb)";
391
+ var UPLOAD_ITEM_ICON = {
392
+ pending: { icon: "solar:clock-circle-linear", rgb: "var(--primary-rgb)" },
393
+ uploading: { icon: "beautinique:loading-spin", rgb: "var(--primary-rgb)" },
394
+ success: { icon: "solar:check-circle-linear", rgb: GREEN_RGB },
395
+ error: { icon: "solar:danger-triangle-linear", rgb: RED_RGB }
396
+ };
397
+ var uploadsRgb = /* @__PURE__ */ __name(({ isSettled, failed }) => {
398
+ if (!isSettled) return "var(--primary-rgb)";
399
+ return failed > 0 ? RED_RGB : GREEN_RGB;
400
+ }, "uploadsRgb");
401
+ var uploadsSummaryText = /* @__PURE__ */ __name(({ filesDone, filesTotal, failed, isSettled }) => {
402
+ if (!isSettled) return `${String(filesDone)} of ${String(filesTotal)} uploaded`;
403
+ if (failed === 0) return filesTotal === 1 ? "Uploaded" : `All ${String(filesTotal)} uploaded`;
404
+ return `${String(filesDone)} of ${String(filesTotal)} uploaded, ${String(failed)} failed`;
405
+ }, "uploadsSummaryText");
406
+ var uploadsHeading = /* @__PURE__ */ __name((title, { failed, isSettled }, uploads) => {
407
+ if (!isSettled) return title;
408
+ if (failed === 0) return "Upload complete";
409
+ return failed === uploads ? "Upload failed" : "Some uploads failed";
410
+ }, "uploadsHeading");
411
+ var UploadRow = /* @__PURE__ */ __name(({ item }) => {
412
+ const percent = getUploadItemPercent(item);
413
+ const { icon, rgb } = UPLOAD_ITEM_ICON[item.status];
414
+ return /* @__PURE__ */ jsxs("li", { "data-status": item.status, className: "flex items-center gap-1.5 md:gap-2", children: [
415
+ /* @__PURE__ */ jsx(
416
+ Icon,
417
+ {
418
+ icon,
419
+ className: "size-3.5 shrink-0 md:size-4",
420
+ style: { color: `rgb(${rgb})`, opacity: item.status === "pending" ? 0.4 : 1 }
421
+ }
422
+ ),
423
+ /* @__PURE__ */ jsx("span", { className: "text-primary w-20 shrink-0 truncate text-[9px] md:w-28 md:text-xs", children: item.label }),
424
+ /* @__PURE__ */ jsx(
425
+ "div",
426
+ {
427
+ role: "progressbar",
428
+ "aria-label": `${item.label} upload`,
429
+ "aria-valuemin": 0,
430
+ "aria-valuemax": 100,
431
+ "aria-valuenow": percent,
432
+ className: "bg-primary/10 h-1 min-w-0 flex-1 overflow-hidden rounded-full",
433
+ children: /* @__PURE__ */ jsx(
434
+ "div",
435
+ {
436
+ className: "h-full rounded-full transition-[width] duration-300",
437
+ style: { width: `${String(percent)}%`, backgroundColor: `rgb(${rgb})` }
438
+ }
439
+ )
440
+ }
441
+ ),
442
+ /* @__PURE__ */ jsxs("span", { className: "text-tertiary w-8 shrink-0 text-right text-[9px] tabular-nums md:text-xs", children: [
443
+ getUploadItemFilesDone(item),
444
+ "/",
445
+ item.fileSizes.length
446
+ ] })
447
+ ] });
448
+ }, "UploadRow");
449
+ var UploadsBody = /* @__PURE__ */ __name(({ title, description, items, summary, rgb }) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", "aria-live": "polite", children: [
450
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
451
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-1 text-xs font-semibold md:text-sm", style: { color: `rgb(${rgb})` }, children: uploadsHeading(title, summary, items.length) }),
452
+ description && /* @__PURE__ */ jsx("p", { className: "text-tertiary line-clamp-1 text-[9px]/3 md:text-xs", children: description }),
453
+ /* @__PURE__ */ jsx("p", { className: "text-tertiary text-[9px]/3 tabular-nums md:text-xs", children: uploadsSummaryText(summary) })
454
+ ] }),
455
+ items.length > 1 && /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: items.map((item) => /* @__PURE__ */ jsx(UploadRow, { item }, item.id)) })
456
+ ] }), "UploadsBody");
457
+ var cardConfig = /* @__PURE__ */ __name((type) => {
458
+ switch (type) {
459
+ case TOAST_TYPE.success:
460
+ return { iconName: "solar:check-circle-linear", rgb: GREEN_RGB };
461
+ case TOAST_TYPE.error:
462
+ return { iconName: "solar:danger-triangle-linear", rgb: RED_RGB };
463
+ case TOAST_TYPE.warning:
464
+ return { iconName: "solar:danger-triangle-linear", rgb: "var(--primary-yellow-rgb)" };
465
+ case TOAST_TYPE.progress:
466
+ case TOAST_TYPE.uploads:
467
+ return { iconName: "", rgb: "var(--primary-rgb)" };
468
+ case TOAST_TYPE.loading:
469
+ return { iconName: "beautinique:loading-spin", rgb: "var(--primary-rgb)" };
470
+ case TOAST_TYPE.default:
471
+ case TOAST_TYPE.custom:
472
+ default:
473
+ return { iconName: "solar:info-circle-outline", rgb: "var(--primary-rgb)" };
474
+ }
475
+ }, "cardConfig");
476
+ var Toaster = /* @__PURE__ */ __name((props) => {
477
+ const {
478
+ className = "",
479
+ type,
480
+ icon,
481
+ buttonProps,
482
+ isClosable = true,
483
+ autoClose = true,
484
+ closeTimer = 5e3
485
+ } = props;
486
+ const remove2 = useToastStore((s) => s.remove);
487
+ const [visible, setVisible] = useState(false);
488
+ const [mounted, setMounted] = useState(true);
489
+ const timeoutRef = useRef(null);
490
+ const uploadsSummary = props.type === TOAST_TYPE.uploads ? getUploadsSummary(props.items) : null;
491
+ const isUploadsSettled = uploadsSummary?.isSettled ?? false;
492
+ let resolvedAutoClose = autoClose;
493
+ let resolvedIsClosable = isClosable;
494
+ let resolvedCloseTimer = closeTimer;
495
+ if (TOAST_TYPE.loading === type || TOAST_TYPE.progress === type) {
496
+ resolvedAutoClose = false;
497
+ resolvedIsClosable = false;
498
+ } else if (uploadsSummary) {
499
+ resolvedAutoClose = isUploadsSettled;
500
+ resolvedIsClosable = isUploadsSettled;
501
+ resolvedCloseTimer = uploadsSummary.failed > 0 ? 8e3 : 2500;
502
+ }
503
+ const config = uploadsSummary ? { iconName: "", rgb: uploadsRgb(uploadsSummary) } : cardConfig(type);
504
+ const handleClose = useCallback(() => {
505
+ setVisible(false);
506
+ timeoutRef.current = setTimeout(() => {
507
+ setMounted(false);
508
+ remove2(props.id);
509
+ }, 300);
510
+ }, [remove2, props.id]);
511
+ useEffect(() => {
512
+ const enterTimer = setTimeout(() => {
513
+ setVisible(true);
514
+ }, 50);
515
+ let autoTimer = null;
516
+ if (resolvedAutoClose) {
517
+ autoTimer = setTimeout(() => {
518
+ handleClose();
519
+ }, resolvedCloseTimer);
520
+ }
521
+ return () => {
522
+ clearTimeout(enterTimer);
523
+ if (autoTimer) clearTimeout(autoTimer);
524
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
525
+ };
526
+ }, [resolvedAutoClose, resolvedCloseTimer, handleClose]);
527
+ if (!mounted) return null;
528
+ const isCustom = type === TOAST_TYPE.custom;
529
+ return /* @__PURE__ */ jsx(
530
+ "div",
531
+ {
532
+ className: `rounded-xl border-2 transition-all duration-300 ease-in-out ${visible ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"} ${className}`,
533
+ style: {
534
+ borderColor: `rgba(${config.rgb}, 0.2)`,
535
+ boxShadow: `0 10px 10px 0px rgba(${config.rgb}, 0.1)`
536
+ },
537
+ children: /* @__PURE__ */ jsxs("div", { className: "bg-secondary-invert flex w-full items-center gap-2 rounded-[10.25px] p-1.5 md:p-2.5 lg:p-3 [&>svg]:size-5 [&>svg]:shrink-0 [&>svg]:md:size-6 [&>svg]:lg:size-7", children: [
538
+ icon ?? (props.type === TOAST_TYPE.progress ? /* @__PURE__ */ jsx(
539
+ CircularProgress,
540
+ {
541
+ progress: props.progress,
542
+ style: { strokeColor: `rgb(${config.rgb})` }
543
+ }
544
+ ) : uploadsSummary ? /* @__PURE__ */ jsx(
545
+ CircularProgress,
546
+ {
547
+ progress: uploadsSummary.percent,
548
+ style: { color: `rgb(${config.rgb})` }
549
+ }
550
+ ) : /* @__PURE__ */ jsx(Icon, { icon: config.iconName, style: { color: `rgb(${config.rgb})` } })),
551
+ props.type === TOAST_TYPE.uploads && uploadsSummary ? /* @__PURE__ */ jsx(
552
+ UploadsBody,
553
+ {
554
+ title: props.title,
555
+ description: props.description,
556
+ items: props.items,
557
+ summary: uploadsSummary,
558
+ rgb: config.rgb
559
+ }
560
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center justify-between gap-2", children: [
561
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
562
+ !isCustom && "title" in props && /* @__PURE__ */ jsx(
563
+ "p",
564
+ {
565
+ className: "line-clamp-1 text-xs font-semibold md:text-sm",
566
+ style: { color: `rgb(${config.rgb})` },
567
+ children: props.title
568
+ }
569
+ ),
570
+ !isCustom && "description" in props && /* @__PURE__ */ jsx("p", { className: "text-tertiary line-clamp-2 text-[9px]/3 whitespace-pre-line md:text-xs", children: props.description }),
571
+ isCustom && "children" in props && props.children
572
+ ] }),
573
+ buttonProps && type !== TOAST_TYPE.loading && type !== TOAST_TYPE.progress && /* @__PURE__ */ jsx(
574
+ Button,
575
+ {
576
+ ...buttonProps,
577
+ content: buttonProps.content ?? "Try Again",
578
+ pattern: buttonProps.pattern ?? "secondary",
579
+ className: `w-fit! rounded-md! px-2! py-1! pt-1.5! text-[10px] whitespace-nowrap md:text-xs! ${buttonProps.className ?? ""}`
580
+ }
581
+ )
582
+ ] }),
583
+ resolvedIsClosable && /* @__PURE__ */ jsx(
584
+ Icon,
585
+ {
586
+ icon: "lucide:x",
587
+ className: "text-primary cursor-pointer md:size-5",
588
+ onClick: handleClose
589
+ }
590
+ )
591
+ ] })
592
+ }
593
+ );
594
+ }, "Toaster");
595
+ var ToastContainer = /* @__PURE__ */ __name(() => {
596
+ const toasts = useToastStore((s) => s.toasts);
597
+ if (toasts.length === 0) return null;
598
+ return /* @__PURE__ */ jsx("div", { className: "base:max-w-sm fixed right-2 bottom-2 z-999 flex w-full max-w-xs flex-col gap-2 sm:max-w-md md:right-4 md:bottom-4 md:max-w-lg lg:right-8 lg:bottom-8 lg:max-w-xl", children: toasts.map((toast) => /* @__PURE__ */ jsx(Toaster, { ...toast, id: toast.id }, toast.id)) });
599
+ }, "ToastContainer");
600
+
601
+ export { TOAST_ICON_NAMES, ToastContainer, Toaster, createUploadsToast, getFormDataFiles, getUploadItemFilesDone, getUploadItemPercent, getUploadsSummary, isUploadItemSettled, runUploadsInToast, toaster, useToastStore, withProgressToast, withUploadsToast };
602
+ //# sourceMappingURL=index.js.map
603
+ //# sourceMappingURL=index.js.map