@sarunyu/system-one 4.4.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/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import React__default, { forwardRef, useState, useRef, useEffect, useCallback, useId, useMemo, useLayoutEffect, useContext, createContext } from "react";
5
5
  import { clsx } from "clsx";
6
6
  import { twMerge } from "tailwind-merge";
7
- import { BookmarkSimpleIcon, BroadcastIcon, CalendarBlank, MapPin, Users, XCircle, CheckCircle, Lock, Check, Plus, Circle, Minus, CaretLeft, CaretRight, CaretDoubleLeft, CaretDoubleRight, CaretUp, CaretDown, X, MagnifyingGlass, ArrowUp, ArrowDown, ArrowsDownUp, Clock } from "@phosphor-icons/react";
7
+ import { BellSimple, FunnelSimple, CheckCircle, Warning, XCircle, Info, BookmarkSimpleIcon, BroadcastIcon as BroadcastIcon$1, CalendarBlank, MapPin, Users, Lock, Check, Plus, Circle, Minus, CaretLeft, CaretRight, CaretDoubleLeft, CaretDoubleRight, CaretUp, CaretDown, X, ImageSquare, MegaphoneSimple, GearSix, MagnifyingGlass, ArrowUp, ArrowDown, ArrowsDownUp, Clock } from "@phosphor-icons/react";
8
8
  import { DayPicker, useNavigation } from "react-day-picker";
9
9
  import * as Popover from "@radix-ui/react-popover";
10
10
  import { Drawer as Drawer$1 } from "vaul";
@@ -228,6 +228,141 @@ const Button = forwardRef(function Button2({
228
228
  );
229
229
  });
230
230
  Button.displayName = "Button";
231
+ function formatCount(count, maxCount) {
232
+ if (count > maxCount) return `${maxCount}+`;
233
+ return String(count);
234
+ }
235
+ function Badge({
236
+ variant = "button",
237
+ count = 0,
238
+ maxCount = 99,
239
+ label = "Filter",
240
+ iconOnly = false,
241
+ icon,
242
+ notificationState,
243
+ className,
244
+ ...props
245
+ }) {
246
+ const hasCount = count > 0;
247
+ const isActive = hasCount;
248
+ const resolvedNotificationState = notificationState ?? (hasCount ? "noti" : "default");
249
+ const notificationIsFilled = resolvedNotificationState === "active" || resolvedNotificationState === "noti";
250
+ const showNotificationDot = resolvedNotificationState === "noti" && hasCount;
251
+ const visualIcon = variant === "notification" ? notificationIsFilled ? /* @__PURE__ */ jsx(BellSimple, { size: 19, weight: "fill" }) : /* @__PURE__ */ jsx(BellSimple, { size: 19, weight: "regular" }) : icon ?? /* @__PURE__ */ jsx(FunnelSimple, { size: 18, weight: "regular" });
252
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative inline-flex", className), children: [
253
+ variant === "notification" ? /* @__PURE__ */ jsx(
254
+ Button,
255
+ {
256
+ "aria-label": "Notification",
257
+ size: "icon-xs",
258
+ variant: "plain-black",
259
+ className: cn(
260
+ "text-subtle-text",
261
+ notificationIsFilled && "text-primary-action"
262
+ ),
263
+ ...props,
264
+ children: visualIcon
265
+ }
266
+ ) : iconOnly ? /* @__PURE__ */ jsx(
267
+ Button,
268
+ {
269
+ "aria-label": label,
270
+ size: "icon-md",
271
+ variant: isActive ? "outline" : "outline-black",
272
+ className: cn(isActive && "bg-primary-action-light border-primary-action-light"),
273
+ ...props,
274
+ children: visualIcon
275
+ }
276
+ ) : /* @__PURE__ */ jsx(
277
+ Button,
278
+ {
279
+ size: "md",
280
+ leftIcon: visualIcon,
281
+ variant: isActive ? "outline" : "outline-black",
282
+ className: cn(isActive && "bg-primary-action-light border-primary-action-light"),
283
+ ...props,
284
+ children: label
285
+ }
286
+ ),
287
+ (variant === "notification" ? showNotificationDot : hasCount) && /* @__PURE__ */ jsx(
288
+ "div",
289
+ {
290
+ className: cn(
291
+ "absolute flex items-center justify-center rounded-[60px] px-1",
292
+ 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"
293
+ ),
294
+ children: /* @__PURE__ */ jsx("p", { className: "text-center text-xs leading-4 text-on-primary-action", children: formatCount(count, maxCount) })
295
+ }
296
+ )
297
+ ] });
298
+ }
299
+ const alertStyles = {
300
+ normal: {
301
+ container: "bg-default-secondary",
302
+ text: "text-default-secondary",
303
+ icon: "text-default-secondary"
304
+ },
305
+ information: {
306
+ container: "bg-[var(--bg-info-light)]",
307
+ text: "text-[var(--text-info-primary)]",
308
+ icon: "text-[var(--icon-brand-primary)]"
309
+ },
310
+ success: {
311
+ container: "bg-[var(--bg-success-light)]",
312
+ text: "text-[var(--text-success-primary)]",
313
+ icon: "text-[var(--icon-success)]"
314
+ },
315
+ warning: {
316
+ container: "bg-[var(--bg-warning-soft)]",
317
+ text: "text-[var(--text-warning-primary)]",
318
+ icon: "text-[var(--icon-warning)]"
319
+ },
320
+ critical: {
321
+ container: "bg-[var(--bg-danger-light)]",
322
+ text: "text-[var(--text-danger-primary)]",
323
+ icon: "text-[var(--icon-danger)]"
324
+ }
325
+ };
326
+ function AlertStatusIcon({
327
+ status,
328
+ className
329
+ }) {
330
+ if (status === "success") return /* @__PURE__ */ jsx(CheckCircle, { size: 16, weight: "fill", className });
331
+ if (status === "warning") return /* @__PURE__ */ jsx(Warning, { size: 16, weight: "fill", className });
332
+ if (status === "critical") return /* @__PURE__ */ jsx(XCircle, { size: 16, weight: "fill", className });
333
+ return /* @__PURE__ */ jsx(Info, { size: 16, weight: "fill", className });
334
+ }
335
+ const Alert = forwardRef(function Alert2({ status = "normal", message, multiline = false, className }, ref) {
336
+ const style = alertStyles[status];
337
+ return /* @__PURE__ */ jsxs(
338
+ "div",
339
+ {
340
+ ref,
341
+ role: "status",
342
+ className: cn(
343
+ "flex w-full items-center gap-1.5 rounded px-2 py-1",
344
+ multiline && "items-start",
345
+ style.container,
346
+ className
347
+ ),
348
+ children: [
349
+ /* @__PURE__ */ jsx(AlertStatusIcon, { status, className: cn("shrink-0", multiline && "mt-0.5", style.icon) }),
350
+ /* @__PURE__ */ jsx(
351
+ "p",
352
+ {
353
+ className: cn(
354
+ "min-w-0 flex-1 text-sm leading-5 font-normal",
355
+ multiline ? "line-clamp-2" : "truncate",
356
+ style.text
357
+ ),
358
+ children: message
359
+ }
360
+ )
361
+ ]
362
+ }
363
+ );
364
+ });
365
+ Alert.displayName = "Alert";
231
366
  function BannerMedia({ src, alt }) {
232
367
  if (!src) {
233
368
  return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "absolute inset-0 bg-muted" });
@@ -254,7 +389,7 @@ function DurationBadge({
254
389
  size === "lg" ? "bottom-[6px] right-[6px] h-[20px] px-[4px]" : "bottom-[4px] right-[4px] h-[16px] px-[2px]"
255
390
  ),
256
391
  children: [
257
- isUpcoming && /* @__PURE__ */ jsx(BroadcastIcon, { size: 14, className: "text-white" }),
392
+ isUpcoming && /* @__PURE__ */ jsx(BroadcastIcon$1, { size: 14, className: "text-white" }),
258
393
  /* @__PURE__ */ jsx(
259
394
  "p",
260
395
  {
@@ -3721,6 +3856,293 @@ function ModalActions({
3721
3856
  )
3722
3857
  ] });
3723
3858
  }
3859
+ function NotificationDivider({ label }) {
3860
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-2", children: [
3861
+ /* @__PURE__ */ jsx("p", { className: "shrink-0 text-sm leading-5 text-subtle-text", children: label }),
3862
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "h-px min-w-0 flex-1 bg-divider" })
3863
+ ] });
3864
+ }
3865
+ function NotificationRow({
3866
+ item,
3867
+ onItemClick
3868
+ }) {
3869
+ const rowType = item.type ?? "icon";
3870
+ const showImage = rowType === "image";
3871
+ const showUnread = Boolean(item.unread);
3872
+ return /* @__PURE__ */ jsxs(
3873
+ "div",
3874
+ {
3875
+ className: cn(
3876
+ "flex w-full items-start gap-3 px-4 py-3",
3877
+ showUnread ? "bg-primary-action-light/40" : "bg-background"
3878
+ ),
3879
+ role: "button",
3880
+ tabIndex: 0,
3881
+ onClick: () => onItemClick == null ? void 0 : onItemClick(item),
3882
+ onKeyDown: (e) => {
3883
+ if (e.key === "Enter" || e.key === " ") {
3884
+ e.preventDefault();
3885
+ onItemClick == null ? void 0 : onItemClick(item);
3886
+ }
3887
+ },
3888
+ children: [
3889
+ /* @__PURE__ */ jsx("div", { className: "flex w-10 shrink-0 items-start justify-center py-0.5", children: showImage ? item.imageSrc ? /* @__PURE__ */ jsx(
3890
+ "img",
3891
+ {
3892
+ alt: "",
3893
+ className: "h-10 w-10 rounded object-cover",
3894
+ src: item.imageSrc
3895
+ }
3896
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded bg-disabled-bg text-disabled", children: /* @__PURE__ */ jsx(ImageSquare, { size: 20, weight: "regular" }) }) : /* @__PURE__ */ jsx("div", { className: "flex h-6 w-6 items-center justify-center text-subtle-text", children: item.icon ?? /* @__PURE__ */ jsx(Circle, { size: 20, weight: "regular" }) }) }),
3897
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3898
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2", children: [
3899
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 flex-1 truncate text-base leading-6 font-bold text-foreground", children: item.title }),
3900
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
3901
+ showUnread && /* @__PURE__ */ jsx(
3902
+ "span",
3903
+ {
3904
+ "aria-hidden": "true",
3905
+ className: "h-2 w-2 rounded-full bg-primary-action"
3906
+ }
3907
+ ),
3908
+ /* @__PURE__ */ jsx("p", { className: "text-xs leading-4 text-muted-foreground", children: item.time })
3909
+ ] }),
3910
+ /* @__PURE__ */ jsx("p", { className: "col-start-1 mt-1 line-clamp-2 text-sm leading-5 text-muted-foreground", children: item.description })
3911
+ ] }),
3912
+ item.actionLabel && /* @__PURE__ */ jsx(
3913
+ Button,
3914
+ {
3915
+ className: "mt-2",
3916
+ size: "md",
3917
+ variant: "primary",
3918
+ onClick: (e) => {
3919
+ var _a;
3920
+ e.stopPropagation();
3921
+ (_a = item.onActionClick) == null ? void 0 : _a.call(item);
3922
+ },
3923
+ children: item.actionLabel
3924
+ }
3925
+ )
3926
+ ] })
3927
+ ]
3928
+ }
3929
+ );
3930
+ }
3931
+ const Notification = forwardRef(
3932
+ function Notification2({
3933
+ groups,
3934
+ badgeCount,
3935
+ panelWidth = 375,
3936
+ emptyText = "No notifications",
3937
+ clearBadgeOnOpen = true,
3938
+ open,
3939
+ defaultOpen,
3940
+ onOpenChange,
3941
+ onBadgeCleared,
3942
+ onItemClick,
3943
+ className,
3944
+ panelClassName
3945
+ }, ref) {
3946
+ const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);
3947
+ const [isBadgeCleared, setIsBadgeCleared] = useState(false);
3948
+ const controlled = open !== void 0;
3949
+ const resolvedOpen = controlled ? open : internalOpen;
3950
+ const unreadCount = useMemo(
3951
+ () => groups.reduce(
3952
+ (acc, group) => acc + group.items.filter((item) => Boolean(item.unread)).length,
3953
+ 0
3954
+ ),
3955
+ [groups]
3956
+ );
3957
+ const nextCount = badgeCount ?? unreadCount;
3958
+ const prevCountRef = useRef(nextCount);
3959
+ useEffect(() => {
3960
+ const prevCount = prevCountRef.current;
3961
+ if (nextCount <= 0 || nextCount > prevCount) {
3962
+ setIsBadgeCleared(false);
3963
+ }
3964
+ prevCountRef.current = nextCount;
3965
+ }, [nextCount]);
3966
+ const displayCount = clearBadgeOnOpen && isBadgeCleared ? 0 : nextCount;
3967
+ const hasItems = groups.some((group) => group.items.length > 0);
3968
+ const handleOpenChange = (next) => {
3969
+ if (next && clearBadgeOnOpen && nextCount > 0) {
3970
+ setIsBadgeCleared(true);
3971
+ onBadgeCleared == null ? void 0 : onBadgeCleared();
3972
+ }
3973
+ if (!controlled) setInternalOpen(next);
3974
+ onOpenChange == null ? void 0 : onOpenChange(next);
3975
+ };
3976
+ return /* @__PURE__ */ jsxs(Popover.Root, { open: resolvedOpen, onOpenChange: handleOpenChange, children: [
3977
+ /* @__PURE__ */ jsx("div", { ref, className: cn("inline-flex", className), children: /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
3978
+ Badge,
3979
+ {
3980
+ variant: "notification",
3981
+ count: displayCount,
3982
+ maxCount: 99,
3983
+ notificationState: displayCount > 0 ? "noti" : resolvedOpen ? "active" : "default",
3984
+ "aria-label": "Open notifications"
3985
+ }
3986
+ ) }) }) }),
3987
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
3988
+ Popover.Content,
3989
+ {
3990
+ align: "end",
3991
+ sideOffset: 10,
3992
+ className: cn(
3993
+ "z-50 overflow-hidden rounded-lg border border-border bg-background shadow-lg",
3994
+ panelClassName
3995
+ ),
3996
+ style: { width: panelWidth },
3997
+ children: /* @__PURE__ */ jsxs("div", { className: "max-h-[480px] overflow-y-auto py-2", children: [
3998
+ !hasItems && /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center text-sm text-muted-foreground", children: emptyText }),
3999
+ groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
4000
+ /* @__PURE__ */ jsx(NotificationDivider, { label: group.label }),
4001
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-divider", children: group.items.map((item) => /* @__PURE__ */ jsx(
4002
+ NotificationRow,
4003
+ {
4004
+ item,
4005
+ onItemClick
4006
+ },
4007
+ item.id
4008
+ )) })
4009
+ ] }, group.label))
4010
+ ] })
4011
+ }
4012
+ ) })
4013
+ ] });
4014
+ }
4015
+ );
4016
+ Notification.displayName = "Notification";
4017
+ const statusStyles = {
4018
+ information: {
4019
+ bg: "bg-[var(--bg-info-light)]",
4020
+ text: "text-[var(--text-info-primary)]",
4021
+ icon: "text-[var(--icon-brand-primary)]",
4022
+ link: "text-[var(--text-brand-link-primary)]"
4023
+ },
4024
+ success: {
4025
+ bg: "bg-[var(--bg-success-light)]",
4026
+ text: "text-[var(--text-success-primary)]",
4027
+ icon: "text-[var(--icon-success)]",
4028
+ link: "text-[var(--text-success-link)]"
4029
+ },
4030
+ warning: {
4031
+ bg: "bg-[var(--bg-warning-soft)]",
4032
+ text: "text-[var(--text-warning-primary)]",
4033
+ icon: "text-[var(--icon-warning)]",
4034
+ link: "text-[var(--text-warning-link)]"
4035
+ },
4036
+ critical: {
4037
+ bg: "bg-[var(--bg-danger-light)]",
4038
+ text: "text-[var(--text-danger-primary)]",
4039
+ icon: "text-[var(--icon-danger)]",
4040
+ link: "text-[var(--text-danger-link)]"
4041
+ }
4042
+ };
4043
+ function DefaultIcon({
4044
+ status,
4045
+ className
4046
+ }) {
4047
+ if (status === "success") return /* @__PURE__ */ jsx(CheckCircle, { className, size: 24, weight: "fill" });
4048
+ if (status === "warning") return /* @__PURE__ */ jsx(Warning, { className, size: 24, weight: "fill" });
4049
+ if (status === "critical") return /* @__PURE__ */ jsx(XCircle, { className, size: 24, weight: "fill" });
4050
+ return /* @__PURE__ */ jsx(Info, { className, size: 24, weight: "fill" });
4051
+ }
4052
+ function BroadcastIcon({
4053
+ status,
4054
+ className
4055
+ }) {
4056
+ if (status === "information") {
4057
+ return /* @__PURE__ */ jsx(MegaphoneSimple, { className, size: 20, weight: "fill" });
4058
+ }
4059
+ return /* @__PURE__ */ jsx(GearSix, { className, size: 20, weight: "fill" });
4060
+ }
4061
+ function ToastCloseButton({
4062
+ colorClass,
4063
+ onClose
4064
+ }) {
4065
+ return /* @__PURE__ */ jsx(
4066
+ "button",
4067
+ {
4068
+ type: "button",
4069
+ "aria-label": "Close toast",
4070
+ className: cn(
4071
+ "inline-flex h-[18px] w-[18px] shrink-0 cursor-pointer items-center justify-center",
4072
+ colorClass
4073
+ ),
4074
+ onClick: onClose,
4075
+ children: /* @__PURE__ */ jsx(X, { size: 12, weight: "bold" })
4076
+ }
4077
+ );
4078
+ }
4079
+ const Toast = forwardRef(function Toast2({
4080
+ variant = "default",
4081
+ status = "information",
4082
+ message,
4083
+ actionLabel,
4084
+ multiline = false,
4085
+ onActionClick,
4086
+ onClose,
4087
+ className
4088
+ }, ref) {
4089
+ const hasAction = Boolean(actionLabel);
4090
+ const effectiveStatus = variant === "broadcast" && status === "success" ? "information" : status;
4091
+ const effectiveStyle = statusStyles[effectiveStatus];
4092
+ const Icon = variant === "broadcast" ? BroadcastIcon : DefaultIcon;
4093
+ const showActions = variant === "default";
4094
+ return /* @__PURE__ */ jsxs(
4095
+ "div",
4096
+ {
4097
+ ref,
4098
+ role: "status",
4099
+ className: cn(
4100
+ "flex w-full p-3",
4101
+ 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",
4102
+ multiline && "items-start",
4103
+ effectiveStyle.bg,
4104
+ className
4105
+ ),
4106
+ children: [
4107
+ /* @__PURE__ */ jsxs(
4108
+ "div",
4109
+ {
4110
+ className: cn(
4111
+ "flex min-w-0 flex-1 gap-2",
4112
+ multiline ? "items-start" : "items-center",
4113
+ variant === "default" && !hasAction && "opacity-80"
4114
+ ),
4115
+ children: [
4116
+ /* @__PURE__ */ jsx("div", { className: cn("shrink-0", multiline && "pt-0.5"), children: /* @__PURE__ */ jsx(Icon, { status: effectiveStatus, className: effectiveStyle.icon }) }),
4117
+ /* @__PURE__ */ jsx("p", { className: cn("min-w-0 flex-1 text-sm leading-5 font-normal", effectiveStyle.text), children: message })
4118
+ ]
4119
+ }
4120
+ ),
4121
+ showActions && hasAction ? /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
4122
+ /* @__PURE__ */ jsx(
4123
+ "button",
4124
+ {
4125
+ type: "button",
4126
+ className: cn(
4127
+ "cursor-pointer text-sm leading-5 underline underline-offset-2",
4128
+ effectiveStyle.link
4129
+ ),
4130
+ onClick: onActionClick,
4131
+ children: actionLabel
4132
+ }
4133
+ ),
4134
+ /* @__PURE__ */ jsx(ToastCloseButton, { colorClass: effectiveStyle.icon, onClose })
4135
+ ] }) : showActions ? /* @__PURE__ */ jsx(ToastCloseButton, { colorClass: effectiveStyle.icon, onClose }) : null
4136
+ ]
4137
+ }
4138
+ );
4139
+ });
4140
+ Toast.displayName = "Toast";
4141
+ function ToastStack({ items, className, renderItem }) {
4142
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-2", className), children: items.map(
4143
+ (item) => renderItem ? /* @__PURE__ */ jsx("div", { children: renderItem(item) }, item.id) : /* @__PURE__ */ jsx(Toast, { ...item }, item.id)
4144
+ ) });
4145
+ }
3724
4146
  const OptionList = forwardRef(
3725
4147
  function OptionList2({
3726
4148
  options,
@@ -5354,6 +5776,8 @@ const TimeInput = forwardRef(
5354
5776
  );
5355
5777
  TimeInput.displayName = "TimeInput";
5356
5778
  export {
5779
+ Alert,
5780
+ Badge,
5357
5781
  BottomSheet,
5358
5782
  Button,
5359
5783
  Card,
@@ -5364,6 +5788,7 @@ export {
5364
5788
  DropdownMultiple,
5365
5789
  Input,
5366
5790
  Modal,
5791
+ Notification,
5367
5792
  OptionList,
5368
5793
  Radio,
5369
5794
  SearchInput,
@@ -5377,6 +5802,8 @@ export {
5377
5802
  Tag,
5378
5803
  TextArea,
5379
5804
  TimeInput,
5805
+ Toast,
5806
+ ToastStack,
5380
5807
  Toggle,
5381
5808
  cn,
5382
5809
  useIsMobile