@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 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 = "event",
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
- "img",
793
+ BannerMedia,
674
794
  {
675
- alt: variant === "news" ? "news banner" : "event banner",
676
- className: "pointer-events-none absolute inset-0 size-full object-cover",
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;