@sarunyu/system-one 4.3.1 → 4.5.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 +8 -0
- package/README.md +7 -0
- package/dist/index.cjs +448 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +450 -39
- package/dist/index.js.map +1 -1
- package/dist/src/components/alert.d.ts +9 -0
- package/dist/src/components/alert.d.ts.map +1 -0
- package/dist/src/components/badge.d.ts +23 -0
- package/dist/src/components/badge.d.ts.map +1 -0
- package/dist/src/components/card.d.ts +1 -1
- package/dist/src/components/card.d.ts.map +1 -1
- package/dist/src/components/notification.d.ts +47 -0
- package/dist/src/components/notification.d.ts.map +1 -0
- package/dist/src/components/toast.d.ts +25 -0
- package/dist/src/components/toast.d.ts.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/llms.txt +160 -9
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -20,6 +20,10 @@ in this package.** This file is the short version: the rules you must follow.
|
|
|
20
20
|
- Custom tables → use `<Table>` + `<TableRow>` + `<TableHeaderCell>` + `<TableCell>`.
|
|
21
21
|
- Custom modals/dialogs/alerts → use `<Modal>` (wrap it in your own `fixed inset-0` backdrop).
|
|
22
22
|
- Custom bottom sheets / drawers from the bottom → use `<BottomSheet>` (it ships its own backdrop via Vaul).
|
|
23
|
+
- Custom inline status banners → use `<Alert>` (never hand-roll tinted divs with icons).
|
|
24
|
+
- Custom toast / snackbar notifications → use `<Toast>` / `<ToastStack>` in a `fixed` portal.
|
|
25
|
+
- Filter buttons with count badges or notification bell buttons → use `<Badge>`.
|
|
26
|
+
- Custom notification panels / bell popover → use `<Notification>`.
|
|
23
27
|
|
|
24
28
|
2. **Use token-backed Tailwind classes for color.** Never emit hard-coded colors:
|
|
25
29
|
- Hex (`#3b82f6`), arbitrary (`bg-[#...]`), and palette utilities
|
|
@@ -43,6 +47,10 @@ in this package.** This file is the short version: the rules you must follow.
|
|
|
43
47
|
- One `<Button variant="primary">` per context.
|
|
44
48
|
- `Modal` renders the panel only — provide your own `fixed inset-0` backdrop + open/close state. One primary action per modal.
|
|
45
49
|
- `BottomSheet` is mobile-only. On desktop, use `Modal` instead.
|
|
50
|
+
- `Alert` is always-visible (no open state). Pass `status` + `message`; add `onClose` only for dismissible alerts.
|
|
51
|
+
- `Toast` / `ToastStack` are floating — render them in a `fixed` portal (`fixed bottom-4 right-4 z-50`). Never inline them in page flow.
|
|
52
|
+
- `Badge` wraps a trigger element via children and adds a count or dot on top. Pass `variant="notification"` for the bell, `variant="filter"` for buttons.
|
|
53
|
+
- `Notification` manages its own popover; pass `groups` (array of `{ label, items }`). It renders both the bell trigger and the panel.
|
|
46
54
|
|
|
47
55
|
6. **Mobile forms and action-heavy modals MUST use `<BottomSheet>`, not `<Modal>`.**
|
|
48
56
|
Login, signup, settings panels, profile editors, any multi-field form,
|
package/README.md
CHANGED
|
@@ -68,6 +68,10 @@ ships no `Page`/`Section`/`Stack` primitives — use whatever layout looks right
|
|
|
68
68
|
| `Table` | Data tables (`TableRow` / `TableHeaderCell` / `TableCell`) |
|
|
69
69
|
| `Modal` | Centered overlay — dialog / content / alert variants |
|
|
70
70
|
| `BottomSheet` | Mobile-first bottom sheet (Vaul-based, with backdrop) |
|
|
71
|
+
| `Alert` | Inline persistent status banner — information / success / warning / critical |
|
|
72
|
+
| `Toast` / `ToastStack` | Floating transient notification — default and broadcast variants |
|
|
73
|
+
| `Badge` | Count badge overlay on a trigger (notification bell or filter button) |
|
|
74
|
+
| `Notification` | Bell trigger + popover panel with grouped notification items |
|
|
71
75
|
|
|
72
76
|
Full prop reference: [`llms.txt`](./llms.txt).
|
|
73
77
|
|
|
@@ -124,6 +128,9 @@ import type {
|
|
|
124
128
|
InputProps, DropdownOption,
|
|
125
129
|
ToggleSize, ToggleProps,
|
|
126
130
|
ModalVariant, ModalActionLayout,
|
|
131
|
+
AlertStatus,
|
|
132
|
+
ToastVariant, ToastStatus,
|
|
133
|
+
NotificationItem, NotificationGroup,
|
|
127
134
|
} from "@sarunyu/system-one";
|
|
128
135
|
```
|
|
129
136
|
|
package/dist/index.cjs
CHANGED
|
@@ -247,6 +247,154 @@ const Button = React.forwardRef(function Button2({
|
|
|
247
247
|
);
|
|
248
248
|
});
|
|
249
249
|
Button.displayName = "Button";
|
|
250
|
+
function formatCount(count, maxCount) {
|
|
251
|
+
if (count > maxCount) return `${maxCount}+`;
|
|
252
|
+
return String(count);
|
|
253
|
+
}
|
|
254
|
+
function Badge({
|
|
255
|
+
variant = "button",
|
|
256
|
+
count = 0,
|
|
257
|
+
maxCount = 99,
|
|
258
|
+
label = "Filter",
|
|
259
|
+
iconOnly = false,
|
|
260
|
+
icon,
|
|
261
|
+
notificationState,
|
|
262
|
+
className,
|
|
263
|
+
...props
|
|
264
|
+
}) {
|
|
265
|
+
const hasCount = count > 0;
|
|
266
|
+
const isActive = hasCount;
|
|
267
|
+
const resolvedNotificationState = notificationState ?? (hasCount ? "noti" : "default");
|
|
268
|
+
const notificationIsFilled = resolvedNotificationState === "active" || resolvedNotificationState === "noti";
|
|
269
|
+
const showNotificationDot = resolvedNotificationState === "noti" && hasCount;
|
|
270
|
+
const visualIcon = variant === "notification" ? notificationIsFilled ? /* @__PURE__ */ jsxRuntime.jsx(react.BellSimple, { size: 19, weight: "fill" }) : /* @__PURE__ */ jsxRuntime.jsx(react.BellSimple, { size: 19, weight: "regular" }) : icon ?? /* @__PURE__ */ jsxRuntime.jsx(react.FunnelSimple, { size: 18, weight: "regular" });
|
|
271
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative inline-flex", className), children: [
|
|
272
|
+
variant === "notification" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
273
|
+
Button,
|
|
274
|
+
{
|
|
275
|
+
"aria-label": "Notification",
|
|
276
|
+
size: "icon-xs",
|
|
277
|
+
variant: "plain-black",
|
|
278
|
+
className: cn(
|
|
279
|
+
"text-subtle-text",
|
|
280
|
+
notificationIsFilled && "text-primary-action"
|
|
281
|
+
),
|
|
282
|
+
...props,
|
|
283
|
+
children: visualIcon
|
|
284
|
+
}
|
|
285
|
+
) : iconOnly ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
286
|
+
Button,
|
|
287
|
+
{
|
|
288
|
+
"aria-label": label,
|
|
289
|
+
size: "icon-md",
|
|
290
|
+
variant: isActive ? "outline" : "outline-black",
|
|
291
|
+
className: cn(isActive && "bg-primary-action-light border-primary-action-light"),
|
|
292
|
+
...props,
|
|
293
|
+
children: visualIcon
|
|
294
|
+
}
|
|
295
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
296
|
+
Button,
|
|
297
|
+
{
|
|
298
|
+
size: "md",
|
|
299
|
+
leftIcon: visualIcon,
|
|
300
|
+
variant: isActive ? "outline" : "outline-black",
|
|
301
|
+
className: cn(isActive && "bg-primary-action-light border-primary-action-light"),
|
|
302
|
+
...props,
|
|
303
|
+
children: label
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
(variant === "notification" ? showNotificationDot : hasCount) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
307
|
+
"div",
|
|
308
|
+
{
|
|
309
|
+
className: cn(
|
|
310
|
+
"absolute flex items-center justify-center rounded-[60px] px-1",
|
|
311
|
+
variant === "notification" ? "-right-0.5 -top-0.5 h-[14px] min-w-[14px] bg-destructive" : "-right-1 -top-[7px] h-4 min-w-4 bg-primary-action"
|
|
312
|
+
),
|
|
313
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center text-xs leading-4 text-on-primary-action", children: formatCount(count, maxCount) })
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
] });
|
|
317
|
+
}
|
|
318
|
+
const alertStyles = {
|
|
319
|
+
normal: {
|
|
320
|
+
container: "bg-default-secondary",
|
|
321
|
+
text: "text-default-secondary",
|
|
322
|
+
icon: "text-default-secondary"
|
|
323
|
+
},
|
|
324
|
+
information: {
|
|
325
|
+
container: "bg-[var(--bg-info-light)]",
|
|
326
|
+
text: "text-[var(--text-info-primary)]",
|
|
327
|
+
icon: "text-[var(--icon-brand-primary)]"
|
|
328
|
+
},
|
|
329
|
+
success: {
|
|
330
|
+
container: "bg-[var(--bg-success-light)]",
|
|
331
|
+
text: "text-[var(--text-success-primary)]",
|
|
332
|
+
icon: "text-[var(--icon-success)]"
|
|
333
|
+
},
|
|
334
|
+
warning: {
|
|
335
|
+
container: "bg-[var(--bg-warning-soft)]",
|
|
336
|
+
text: "text-[var(--text-warning-primary)]",
|
|
337
|
+
icon: "text-[var(--icon-warning)]"
|
|
338
|
+
},
|
|
339
|
+
critical: {
|
|
340
|
+
container: "bg-[var(--bg-danger-light)]",
|
|
341
|
+
text: "text-[var(--text-danger-primary)]",
|
|
342
|
+
icon: "text-[var(--icon-danger)]"
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
function AlertStatusIcon({
|
|
346
|
+
status,
|
|
347
|
+
className
|
|
348
|
+
}) {
|
|
349
|
+
if (status === "success") return /* @__PURE__ */ jsxRuntime.jsx(react.CheckCircle, { size: 16, weight: "fill", className });
|
|
350
|
+
if (status === "warning") return /* @__PURE__ */ jsxRuntime.jsx(react.Warning, { size: 16, weight: "fill", className });
|
|
351
|
+
if (status === "critical") return /* @__PURE__ */ jsxRuntime.jsx(react.XCircle, { size: 16, weight: "fill", className });
|
|
352
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Info, { size: 16, weight: "fill", className });
|
|
353
|
+
}
|
|
354
|
+
const Alert = React.forwardRef(function Alert2({ status = "normal", message, multiline = false, className }, ref) {
|
|
355
|
+
const style = alertStyles[status];
|
|
356
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
ref,
|
|
360
|
+
role: "status",
|
|
361
|
+
className: cn(
|
|
362
|
+
"flex w-full items-center gap-1.5 rounded px-2 py-1",
|
|
363
|
+
multiline && "items-start",
|
|
364
|
+
style.container,
|
|
365
|
+
className
|
|
366
|
+
),
|
|
367
|
+
children: [
|
|
368
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlertStatusIcon, { status, className: cn("shrink-0", multiline && "mt-0.5", style.icon) }),
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
370
|
+
"p",
|
|
371
|
+
{
|
|
372
|
+
className: cn(
|
|
373
|
+
"min-w-0 flex-1 text-sm leading-5 font-normal",
|
|
374
|
+
multiline ? "line-clamp-2" : "truncate",
|
|
375
|
+
style.text
|
|
376
|
+
),
|
|
377
|
+
children: message
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
]
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
Alert.displayName = "Alert";
|
|
385
|
+
function BannerMedia({ src, alt }) {
|
|
386
|
+
if (!src) {
|
|
387
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", className: "absolute inset-0 bg-muted" });
|
|
388
|
+
}
|
|
389
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
390
|
+
"img",
|
|
391
|
+
{
|
|
392
|
+
alt,
|
|
393
|
+
className: "pointer-events-none absolute inset-0 size-full object-cover",
|
|
394
|
+
src
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
}
|
|
250
398
|
function DurationBadge({
|
|
251
399
|
duration,
|
|
252
400
|
size
|
|
@@ -340,7 +488,7 @@ const tagConfig = {
|
|
|
340
488
|
}
|
|
341
489
|
};
|
|
342
490
|
const Card = React.forwardRef(function Card2({
|
|
343
|
-
variant = "
|
|
491
|
+
variant = "default",
|
|
344
492
|
size = "desktop",
|
|
345
493
|
children,
|
|
346
494
|
title,
|
|
@@ -454,14 +602,7 @@ const Card = React.forwardRef(function Card2({
|
|
|
454
602
|
}
|
|
455
603
|
)
|
|
456
604
|
] }),
|
|
457
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-[84px] w-[149px] shrink-0 overflow-clip rounded-[6px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
458
|
-
"img",
|
|
459
|
-
{
|
|
460
|
-
alt: "social thumbnail",
|
|
461
|
-
className: "pointer-events-none absolute inset-0 size-full object-cover",
|
|
462
|
-
src: bannerSrc
|
|
463
|
-
}
|
|
464
|
-
) })
|
|
605
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-[84px] w-[149px] shrink-0 overflow-clip rounded-[6px]", children: /* @__PURE__ */ jsxRuntime.jsx(BannerMedia, { src: bannerSrc, alt: "social thumbnail" }) })
|
|
465
606
|
] }),
|
|
466
607
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
467
608
|
isDesktop ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-[12px]", children: [
|
|
@@ -544,14 +685,7 @@ const Card = React.forwardRef(function Card2({
|
|
|
544
685
|
imgHeight
|
|
545
686
|
),
|
|
546
687
|
children: [
|
|
547
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
548
|
-
"img",
|
|
549
|
-
{
|
|
550
|
-
alt: "live thumbnail",
|
|
551
|
-
className: "pointer-events-none absolute inset-0 size-full object-cover",
|
|
552
|
-
src: bannerSrc
|
|
553
|
-
}
|
|
554
|
-
),
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsx(BannerMedia, { src: bannerSrc, alt: "live thumbnail" }),
|
|
555
689
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
556
690
|
"div",
|
|
557
691
|
{
|
|
@@ -588,14 +722,7 @@ const Card = React.forwardRef(function Card2({
|
|
|
588
722
|
),
|
|
589
723
|
children: [
|
|
590
724
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-[184px] w-full shrink-0 overflow-clip", children: [
|
|
591
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
592
|
-
"img",
|
|
593
|
-
{
|
|
594
|
-
alt: "news banner",
|
|
595
|
-
className: "pointer-events-none absolute inset-0 size-full object-cover",
|
|
596
|
-
src: bannerSrc
|
|
597
|
-
}
|
|
598
|
-
),
|
|
725
|
+
/* @__PURE__ */ jsxRuntime.jsx(BannerMedia, { src: bannerSrc, alt: "news banner" }),
|
|
599
726
|
locked && /* @__PURE__ */ jsxRuntime.jsx(LockBadge, {})
|
|
600
727
|
] }),
|
|
601
728
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -625,14 +752,7 @@ const Card = React.forwardRef(function Card2({
|
|
|
625
752
|
),
|
|
626
753
|
children: [
|
|
627
754
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-[114px] w-[171px] shrink-0 overflow-clip", children: [
|
|
628
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
629
|
-
"img",
|
|
630
|
-
{
|
|
631
|
-
alt: "news banner",
|
|
632
|
-
className: "pointer-events-none absolute inset-0 size-full object-cover",
|
|
633
|
-
src: bannerSrc
|
|
634
|
-
}
|
|
635
|
-
),
|
|
755
|
+
/* @__PURE__ */ jsxRuntime.jsx(BannerMedia, { src: bannerSrc, alt: "news banner" }),
|
|
636
756
|
locked && /* @__PURE__ */ jsxRuntime.jsx(LockBadge, {})
|
|
637
757
|
] }),
|
|
638
758
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -670,11 +790,10 @@ const Card = React.forwardRef(function Card2({
|
|
|
670
790
|
className: cn("relative w-full shrink-0 overflow-clip", bannerClass),
|
|
671
791
|
children: [
|
|
672
792
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
673
|
-
|
|
793
|
+
BannerMedia,
|
|
674
794
|
{
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
src: bannerSrc
|
|
795
|
+
src: bannerSrc,
|
|
796
|
+
alt: variant === "news" ? "news banner" : "event banner"
|
|
678
797
|
}
|
|
679
798
|
),
|
|
680
799
|
locked && /* @__PURE__ */ jsxRuntime.jsx(LockBadge, { size })
|
|
@@ -3756,6 +3875,293 @@ function ModalActions({
|
|
|
3756
3875
|
)
|
|
3757
3876
|
] });
|
|
3758
3877
|
}
|
|
3878
|
+
function NotificationDivider({ label }) {
|
|
3879
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-4 py-2", children: [
|
|
3880
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-sm leading-5 text-subtle-text", children: label }),
|
|
3881
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", className: "h-px min-w-0 flex-1 bg-divider" })
|
|
3882
|
+
] });
|
|
3883
|
+
}
|
|
3884
|
+
function NotificationRow({
|
|
3885
|
+
item,
|
|
3886
|
+
onItemClick
|
|
3887
|
+
}) {
|
|
3888
|
+
const rowType = item.type ?? "icon";
|
|
3889
|
+
const showImage = rowType === "image";
|
|
3890
|
+
const showUnread = Boolean(item.unread);
|
|
3891
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3892
|
+
"div",
|
|
3893
|
+
{
|
|
3894
|
+
className: cn(
|
|
3895
|
+
"flex w-full items-start gap-3 px-4 py-3",
|
|
3896
|
+
showUnread ? "bg-primary-action-light/40" : "bg-background"
|
|
3897
|
+
),
|
|
3898
|
+
role: "button",
|
|
3899
|
+
tabIndex: 0,
|
|
3900
|
+
onClick: () => onItemClick == null ? void 0 : onItemClick(item),
|
|
3901
|
+
onKeyDown: (e) => {
|
|
3902
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3903
|
+
e.preventDefault();
|
|
3904
|
+
onItemClick == null ? void 0 : onItemClick(item);
|
|
3905
|
+
}
|
|
3906
|
+
},
|
|
3907
|
+
children: [
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-10 shrink-0 items-start justify-center py-0.5", children: showImage ? item.imageSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3909
|
+
"img",
|
|
3910
|
+
{
|
|
3911
|
+
alt: "",
|
|
3912
|
+
className: "h-10 w-10 rounded object-cover",
|
|
3913
|
+
src: item.imageSrc
|
|
3914
|
+
}
|
|
3915
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded bg-disabled-bg text-disabled", children: /* @__PURE__ */ jsxRuntime.jsx(react.ImageSquare, { size: 20, weight: "regular" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-6 w-6 items-center justify-center text-subtle-text", children: item.icon ?? /* @__PURE__ */ jsxRuntime.jsx(react.Circle, { size: 20, weight: "regular" }) }) }),
|
|
3916
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3917
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2", children: [
|
|
3918
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 truncate text-base leading-6 font-bold text-foreground", children: item.title }),
|
|
3919
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
3920
|
+
showUnread && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3921
|
+
"span",
|
|
3922
|
+
{
|
|
3923
|
+
"aria-hidden": "true",
|
|
3924
|
+
className: "h-2 w-2 rounded-full bg-primary-action"
|
|
3925
|
+
}
|
|
3926
|
+
),
|
|
3927
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs leading-4 text-muted-foreground", children: item.time })
|
|
3928
|
+
] }),
|
|
3929
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "col-start-1 mt-1 line-clamp-2 text-sm leading-5 text-muted-foreground", children: item.description })
|
|
3930
|
+
] }),
|
|
3931
|
+
item.actionLabel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3932
|
+
Button,
|
|
3933
|
+
{
|
|
3934
|
+
className: "mt-2",
|
|
3935
|
+
size: "md",
|
|
3936
|
+
variant: "primary",
|
|
3937
|
+
onClick: (e) => {
|
|
3938
|
+
var _a;
|
|
3939
|
+
e.stopPropagation();
|
|
3940
|
+
(_a = item.onActionClick) == null ? void 0 : _a.call(item);
|
|
3941
|
+
},
|
|
3942
|
+
children: item.actionLabel
|
|
3943
|
+
}
|
|
3944
|
+
)
|
|
3945
|
+
] })
|
|
3946
|
+
]
|
|
3947
|
+
}
|
|
3948
|
+
);
|
|
3949
|
+
}
|
|
3950
|
+
const Notification = React.forwardRef(
|
|
3951
|
+
function Notification2({
|
|
3952
|
+
groups,
|
|
3953
|
+
badgeCount,
|
|
3954
|
+
panelWidth = 375,
|
|
3955
|
+
emptyText = "No notifications",
|
|
3956
|
+
clearBadgeOnOpen = true,
|
|
3957
|
+
open,
|
|
3958
|
+
defaultOpen,
|
|
3959
|
+
onOpenChange,
|
|
3960
|
+
onBadgeCleared,
|
|
3961
|
+
onItemClick,
|
|
3962
|
+
className,
|
|
3963
|
+
panelClassName
|
|
3964
|
+
}, ref) {
|
|
3965
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
|
|
3966
|
+
const [isBadgeCleared, setIsBadgeCleared] = React.useState(false);
|
|
3967
|
+
const controlled = open !== void 0;
|
|
3968
|
+
const resolvedOpen = controlled ? open : internalOpen;
|
|
3969
|
+
const unreadCount = React.useMemo(
|
|
3970
|
+
() => groups.reduce(
|
|
3971
|
+
(acc, group) => acc + group.items.filter((item) => Boolean(item.unread)).length,
|
|
3972
|
+
0
|
|
3973
|
+
),
|
|
3974
|
+
[groups]
|
|
3975
|
+
);
|
|
3976
|
+
const nextCount = badgeCount ?? unreadCount;
|
|
3977
|
+
const prevCountRef = React.useRef(nextCount);
|
|
3978
|
+
React.useEffect(() => {
|
|
3979
|
+
const prevCount = prevCountRef.current;
|
|
3980
|
+
if (nextCount <= 0 || nextCount > prevCount) {
|
|
3981
|
+
setIsBadgeCleared(false);
|
|
3982
|
+
}
|
|
3983
|
+
prevCountRef.current = nextCount;
|
|
3984
|
+
}, [nextCount]);
|
|
3985
|
+
const displayCount = clearBadgeOnOpen && isBadgeCleared ? 0 : nextCount;
|
|
3986
|
+
const hasItems = groups.some((group) => group.items.length > 0);
|
|
3987
|
+
const handleOpenChange = (next) => {
|
|
3988
|
+
if (next && clearBadgeOnOpen && nextCount > 0) {
|
|
3989
|
+
setIsBadgeCleared(true);
|
|
3990
|
+
onBadgeCleared == null ? void 0 : onBadgeCleared();
|
|
3991
|
+
}
|
|
3992
|
+
if (!controlled) setInternalOpen(next);
|
|
3993
|
+
onOpenChange == null ? void 0 : onOpenChange(next);
|
|
3994
|
+
};
|
|
3995
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Popover__namespace.Root, { open: resolvedOpen, onOpenChange: handleOpenChange, children: [
|
|
3996
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("inline-flex", className), children: /* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3997
|
+
Badge,
|
|
3998
|
+
{
|
|
3999
|
+
variant: "notification",
|
|
4000
|
+
count: displayCount,
|
|
4001
|
+
maxCount: 99,
|
|
4002
|
+
notificationState: displayCount > 0 ? "noti" : resolvedOpen ? "active" : "default",
|
|
4003
|
+
"aria-label": "Open notifications"
|
|
4004
|
+
}
|
|
4005
|
+
) }) }) }),
|
|
4006
|
+
/* @__PURE__ */ jsxRuntime.jsx(Popover__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4007
|
+
Popover__namespace.Content,
|
|
4008
|
+
{
|
|
4009
|
+
align: "end",
|
|
4010
|
+
sideOffset: 10,
|
|
4011
|
+
className: cn(
|
|
4012
|
+
"z-50 overflow-hidden rounded-lg border border-border bg-background shadow-lg",
|
|
4013
|
+
panelClassName
|
|
4014
|
+
),
|
|
4015
|
+
style: { width: panelWidth },
|
|
4016
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-[480px] overflow-y-auto py-2", children: [
|
|
4017
|
+
!hasItems && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center text-sm text-muted-foreground", children: emptyText }),
|
|
4018
|
+
groups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
|
|
4019
|
+
/* @__PURE__ */ jsxRuntime.jsx(NotificationDivider, { label: group.label }),
|
|
4020
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-divider", children: group.items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4021
|
+
NotificationRow,
|
|
4022
|
+
{
|
|
4023
|
+
item,
|
|
4024
|
+
onItemClick
|
|
4025
|
+
},
|
|
4026
|
+
item.id
|
|
4027
|
+
)) })
|
|
4028
|
+
] }, group.label))
|
|
4029
|
+
] })
|
|
4030
|
+
}
|
|
4031
|
+
) })
|
|
4032
|
+
] });
|
|
4033
|
+
}
|
|
4034
|
+
);
|
|
4035
|
+
Notification.displayName = "Notification";
|
|
4036
|
+
const statusStyles = {
|
|
4037
|
+
information: {
|
|
4038
|
+
bg: "bg-[var(--bg-info-light)]",
|
|
4039
|
+
text: "text-[var(--text-info-primary)]",
|
|
4040
|
+
icon: "text-[var(--icon-brand-primary)]",
|
|
4041
|
+
link: "text-[var(--text-brand-link-primary)]"
|
|
4042
|
+
},
|
|
4043
|
+
success: {
|
|
4044
|
+
bg: "bg-[var(--bg-success-light)]",
|
|
4045
|
+
text: "text-[var(--text-success-primary)]",
|
|
4046
|
+
icon: "text-[var(--icon-success)]",
|
|
4047
|
+
link: "text-[var(--text-success-link)]"
|
|
4048
|
+
},
|
|
4049
|
+
warning: {
|
|
4050
|
+
bg: "bg-[var(--bg-warning-soft)]",
|
|
4051
|
+
text: "text-[var(--text-warning-primary)]",
|
|
4052
|
+
icon: "text-[var(--icon-warning)]",
|
|
4053
|
+
link: "text-[var(--text-warning-link)]"
|
|
4054
|
+
},
|
|
4055
|
+
critical: {
|
|
4056
|
+
bg: "bg-[var(--bg-danger-light)]",
|
|
4057
|
+
text: "text-[var(--text-danger-primary)]",
|
|
4058
|
+
icon: "text-[var(--icon-danger)]",
|
|
4059
|
+
link: "text-[var(--text-danger-link)]"
|
|
4060
|
+
}
|
|
4061
|
+
};
|
|
4062
|
+
function DefaultIcon({
|
|
4063
|
+
status,
|
|
4064
|
+
className
|
|
4065
|
+
}) {
|
|
4066
|
+
if (status === "success") return /* @__PURE__ */ jsxRuntime.jsx(react.CheckCircle, { className, size: 24, weight: "fill" });
|
|
4067
|
+
if (status === "warning") return /* @__PURE__ */ jsxRuntime.jsx(react.Warning, { className, size: 24, weight: "fill" });
|
|
4068
|
+
if (status === "critical") return /* @__PURE__ */ jsxRuntime.jsx(react.XCircle, { className, size: 24, weight: "fill" });
|
|
4069
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Info, { className, size: 24, weight: "fill" });
|
|
4070
|
+
}
|
|
4071
|
+
function BroadcastIcon({
|
|
4072
|
+
status,
|
|
4073
|
+
className
|
|
4074
|
+
}) {
|
|
4075
|
+
if (status === "information") {
|
|
4076
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.MegaphoneSimple, { className, size: 20, weight: "fill" });
|
|
4077
|
+
}
|
|
4078
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.GearSix, { className, size: 20, weight: "fill" });
|
|
4079
|
+
}
|
|
4080
|
+
function ToastCloseButton({
|
|
4081
|
+
colorClass,
|
|
4082
|
+
onClose
|
|
4083
|
+
}) {
|
|
4084
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4085
|
+
"button",
|
|
4086
|
+
{
|
|
4087
|
+
type: "button",
|
|
4088
|
+
"aria-label": "Close toast",
|
|
4089
|
+
className: cn(
|
|
4090
|
+
"inline-flex h-[18px] w-[18px] shrink-0 cursor-pointer items-center justify-center",
|
|
4091
|
+
colorClass
|
|
4092
|
+
),
|
|
4093
|
+
onClick: onClose,
|
|
4094
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 12, weight: "bold" })
|
|
4095
|
+
}
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
const Toast = React.forwardRef(function Toast2({
|
|
4099
|
+
variant = "default",
|
|
4100
|
+
status = "information",
|
|
4101
|
+
message,
|
|
4102
|
+
actionLabel,
|
|
4103
|
+
multiline = false,
|
|
4104
|
+
onActionClick,
|
|
4105
|
+
onClose,
|
|
4106
|
+
className
|
|
4107
|
+
}, ref) {
|
|
4108
|
+
const hasAction = Boolean(actionLabel);
|
|
4109
|
+
const effectiveStatus = variant === "broadcast" && status === "success" ? "information" : status;
|
|
4110
|
+
const effectiveStyle = statusStyles[effectiveStatus];
|
|
4111
|
+
const Icon = variant === "broadcast" ? BroadcastIcon : DefaultIcon;
|
|
4112
|
+
const showActions = variant === "default";
|
|
4113
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4114
|
+
"div",
|
|
4115
|
+
{
|
|
4116
|
+
ref,
|
|
4117
|
+
role: "status",
|
|
4118
|
+
className: cn(
|
|
4119
|
+
"flex w-full p-3",
|
|
4120
|
+
variant === "default" ? "items-center gap-2 rounded-lg shadow-[0px_1px_2px_0px_rgba(0,0,0,0.10),0px_1px_3px_1px_rgba(0,0,0,0.05)]" : "items-center gap-2",
|
|
4121
|
+
multiline && "items-start",
|
|
4122
|
+
effectiveStyle.bg,
|
|
4123
|
+
className
|
|
4124
|
+
),
|
|
4125
|
+
children: [
|
|
4126
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4127
|
+
"div",
|
|
4128
|
+
{
|
|
4129
|
+
className: cn(
|
|
4130
|
+
"flex min-w-0 flex-1 gap-2",
|
|
4131
|
+
multiline ? "items-start" : "items-center",
|
|
4132
|
+
variant === "default" && !hasAction && "opacity-80"
|
|
4133
|
+
),
|
|
4134
|
+
children: [
|
|
4135
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("shrink-0", multiline && "pt-0.5"), children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { status: effectiveStatus, className: effectiveStyle.icon }) }),
|
|
4136
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("min-w-0 flex-1 text-sm leading-5 font-normal", effectiveStyle.text), children: message })
|
|
4137
|
+
]
|
|
4138
|
+
}
|
|
4139
|
+
),
|
|
4140
|
+
showActions && hasAction ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
|
|
4141
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4142
|
+
"button",
|
|
4143
|
+
{
|
|
4144
|
+
type: "button",
|
|
4145
|
+
className: cn(
|
|
4146
|
+
"cursor-pointer text-sm leading-5 underline underline-offset-2",
|
|
4147
|
+
effectiveStyle.link
|
|
4148
|
+
),
|
|
4149
|
+
onClick: onActionClick,
|
|
4150
|
+
children: actionLabel
|
|
4151
|
+
}
|
|
4152
|
+
),
|
|
4153
|
+
/* @__PURE__ */ jsxRuntime.jsx(ToastCloseButton, { colorClass: effectiveStyle.icon, onClose })
|
|
4154
|
+
] }) : showActions ? /* @__PURE__ */ jsxRuntime.jsx(ToastCloseButton, { colorClass: effectiveStyle.icon, onClose }) : null
|
|
4155
|
+
]
|
|
4156
|
+
}
|
|
4157
|
+
);
|
|
4158
|
+
});
|
|
4159
|
+
Toast.displayName = "Toast";
|
|
4160
|
+
function ToastStack({ items, className, renderItem }) {
|
|
4161
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col gap-2", className), children: items.map(
|
|
4162
|
+
(item) => renderItem ? /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderItem(item) }, item.id) : /* @__PURE__ */ jsxRuntime.jsx(Toast, { ...item }, item.id)
|
|
4163
|
+
) });
|
|
4164
|
+
}
|
|
3759
4165
|
const OptionList = React.forwardRef(
|
|
3760
4166
|
function OptionList2({
|
|
3761
4167
|
options,
|
|
@@ -5388,6 +5794,8 @@ const TimeInput = React.forwardRef(
|
|
|
5388
5794
|
}
|
|
5389
5795
|
);
|
|
5390
5796
|
TimeInput.displayName = "TimeInput";
|
|
5797
|
+
exports.Alert = Alert;
|
|
5798
|
+
exports.Badge = Badge;
|
|
5391
5799
|
exports.BottomSheet = BottomSheet;
|
|
5392
5800
|
exports.Button = Button;
|
|
5393
5801
|
exports.Card = Card;
|
|
@@ -5398,6 +5806,7 @@ exports.Dropdown = Dropdown;
|
|
|
5398
5806
|
exports.DropdownMultiple = DropdownMultiple;
|
|
5399
5807
|
exports.Input = Input;
|
|
5400
5808
|
exports.Modal = Modal;
|
|
5809
|
+
exports.Notification = Notification;
|
|
5401
5810
|
exports.OptionList = OptionList;
|
|
5402
5811
|
exports.Radio = Radio;
|
|
5403
5812
|
exports.SearchInput = SearchInput;
|
|
@@ -5411,6 +5820,8 @@ exports.TableRow = TableRow;
|
|
|
5411
5820
|
exports.Tag = Tag;
|
|
5412
5821
|
exports.TextArea = TextArea;
|
|
5413
5822
|
exports.TimeInput = TimeInput;
|
|
5823
|
+
exports.Toast = Toast;
|
|
5824
|
+
exports.ToastStack = ToastStack;
|
|
5414
5825
|
exports.Toggle = Toggle;
|
|
5415
5826
|
exports.cn = cn;
|
|
5416
5827
|
exports.useIsMobile = useIsMobile;
|