@neasg/design-system 0.4.14 → 0.4.15

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.d.ts CHANGED
@@ -11,8 +11,8 @@ export type { CardGridProps, CardListItemProps, CardListProps, CardProps, CardSp
11
11
  export type { BadgeProps } from "./badge";
12
12
  export { MessageAttachmentRow, MessageItem, MessageNewDivider, MessageThread, formatMessageDate, } from "./message-item";
13
13
  export type { MessageAttachmentRowProps, MessageItemAttachment, MessageItemData, MessageItemProps, MessageNewDividerProps, MessageThreadProps, } from "./message-item";
14
- export { NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
15
- export type { NotificationAction, NotificationFilter, NotificationIcon, NotificationItemData, NotificationItemProps, NotificationKind, NotificationListProps, NotificationPopoverProps, NotificationTriggerProps, NotificationTone, } from "./notification";
14
+ export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
15
+ export type { NotificationAction, NotificationFilter, NotificationIcon, NotificationItemData, NotificationItemProps, NotificationKind, NotificationListProps, NotificationPopoverProps, NotificationTimestamp, NotificationTriggerProps, NotificationTone, FormatNotificationTimestampOptions, } from "./notification";
16
16
  export { RoutingTimeline } from "./routing-timeline";
17
17
  export type { RoutingTimelineItem, RoutingTimelineItemKind, RoutingTimelineProps, } from "./routing-timeline";
18
18
  export { RichTextEditor, formatFileSize, replaceTemplateVariables, sanitizeRichTextHtml, } from "./rich-text-editor";
@@ -78,7 +78,7 @@ export { RadioGroup } from "./radio-group";
78
78
  export type { RadioGroupOption, RadioGroupProps } from "./radio-group";
79
79
  export { ScrollHint } from "./scroll-hint";
80
80
  export type { ScrollHintProps } from "./scroll-hint";
81
- export { TaskModeShell } from "./task-mode-shell";
81
+ export { TaskModeShell, TASK_MODE_SHELL_ANIMATION_DURATION_MS, } from "./task-mode-shell";
82
82
  export type { TaskModeShellProps } from "./task-mode-shell";
83
83
  export { useFileUpload } from "./use-file-upload";
84
84
  export type { FileUploadProgress, FileUploadResult, FileUploadStatus, UseFileUploadOptions, UseFileUploadReturn, } from "./use-file-upload";
@@ -132,7 +132,7 @@ export type { TableColumnVisibilityMenuProps } from "./table-column-visibility";
132
132
  export { useTableSort } from "./use-table-sort";
133
133
  export type { UseTableSortOptions, UseTableSortReturn, } from "./use-table-sort";
134
134
  export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, TableRowSkeleton, TABLE_SKELETON_PRESETS, } from "./table";
135
- export type { TableColumn, TableColumnWidths, TableProps, TableRowTone, TableSortDirection, TableSortingProps, TableSkeletonConfig, TableRowSkeletonProps, } from "./table";
135
+ export type { TableColumn, TableColumnWidths, TableProps, TableResizeMode, TableRowTone, TableSortDirection, TableSortingProps, TableSkeletonConfig, TableRowSkeletonProps, } from "./table";
136
136
  export { Tabs, TabsRoot, TabWithDropdown, type TabsItem, type TabsItemLabel, type TabsItemLabelProps, type TabsProps, TabsList, TabsTrigger, TabsContent, type TabWithDropdownProps, useTabsContext, type TabDropdownSection, } from "./tabs";
137
137
  export { Tooltip, TooltipRoot, TooltipTrigger, TooltipContent, TooltipProvider, type TooltipProps, } from "./tooltip";
138
138
  export { Toaster, toast } from "./toaster";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { Alert, AlertAction, AlertConsent, alertVariants } from "./alert";
5
5
  export { Badge, badgeVariants } from "./badge";
6
6
  export { Card, CardGrid, CardSplitGrid, CardList, CardListItem, } from "./card";
7
7
  export { MessageAttachmentRow, MessageItem, MessageNewDivider, MessageThread, formatMessageDate, } from "./message-item";
8
- export { NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
8
+ export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
9
9
  export { RoutingTimeline } from "./routing-timeline";
10
10
  export { RichTextEditor, formatFileSize, replaceTemplateVariables, sanitizeRichTextHtml, } from "./rich-text-editor";
11
11
  export { Calendar, CalendarDayButton } from "./calendar";
@@ -40,7 +40,7 @@ export { FilterPopover, getFilterSelectionLabel } from "./filter-popover";
40
40
  export { Progress } from "./progress";
41
41
  export { RadioGroup } from "./radio-group";
42
42
  export { ScrollHint } from "./scroll-hint";
43
- export { TaskModeShell } from "./task-mode-shell";
43
+ export { TaskModeShell, TASK_MODE_SHELL_ANIMATION_DURATION_MS, } from "./task-mode-shell";
44
44
  export { useFileUpload } from "./use-file-upload";
45
45
  export { Popover } from "./popover";
46
46
  export { PopoverMenuContent, PopoverMenuCheckboxItem, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, } from "./popover-menu";
@@ -5,9 +5,11 @@ import { FileText } from "lucide-react";
5
5
  import { DownloadIcon } from "./animated-icons";
6
6
  import { AvatarProfile } from "./avatar";
7
7
  import { Badge } from "./badge";
8
+ import { formatNotificationTimestamp } from "./notification";
8
9
  import { Separator } from "./separator";
9
10
  import { Typography } from "./typography";
10
11
  import { cn } from "./lib/utils";
12
+ const MESSAGE_TIMESTAMP_REFRESH_MS = 60 * 1000;
11
13
  export const formatMessageDate = (date) => {
12
14
  const d = new Date(date);
13
15
  const hours = d.getHours();
@@ -52,6 +54,7 @@ export function MessageItem({ message, caseId, showNewBadge = false, newMessageL
52
54
  var _a;
53
55
  const messageRef = React.useRef(null);
54
56
  const [hasBeenViewed, setHasBeenViewed] = React.useState(message.isRead);
57
+ const [timestampNow, setTimestampNow] = React.useState(() => new Date());
55
58
  const isVisibleEnoughRef = React.useRef(false);
56
59
  const isOfficer = message.senderRole === "OFFICER";
57
60
  const isApplicant = message.senderRole === "APPLICANT";
@@ -116,6 +119,13 @@ export function MessageItem({ message, caseId, showNewBadge = false, newMessageL
116
119
  onMarkAsRead,
117
120
  onMarkedAsRead,
118
121
  ]);
122
+ React.useEffect(() => {
123
+ if (formatTimestamp) {
124
+ return;
125
+ }
126
+ const intervalId = window.setInterval(() => setTimestampNow(new Date()), MESSAGE_TIMESTAMP_REFRESH_MS);
127
+ return () => window.clearInterval(intervalId);
128
+ }, [formatTimestamp]);
119
129
  const senderName = isOfficer
120
130
  ? message.officerName || "Officer"
121
131
  : isApplicant
@@ -136,7 +146,9 @@ export function MessageItem({ message, caseId, showNewBadge = false, newMessageL
136
146
  const richContentClassName = cn("break-words leading-6", "[&_p]:my-0 [&_p+p]:mt-2", "[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5", "[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5", "[&_li]:my-1", "[&_a]:text-primary [&_a]:underline [&_a:hover]:text-primary/80", "[&_h1]:mb-2 [&_h1]:mt-3 [&_h1]:text-xl [&_h1]:font-semibold", "[&_h2]:mb-2 [&_h2]:mt-3 [&_h2]:text-lg [&_h2]:font-semibold", "[&_h3]:mb-1.5 [&_h3]:mt-3 [&_h3]:text-base [&_h3]:font-semibold", "[&_h4]:mb-1.5 [&_h4]:mt-3 [&_h4]:font-semibold", "[&_h5]:mb-1.5 [&_h5]:mt-3 [&_h5]:font-semibold", "[&_h6]:mb-1.5 [&_h6]:mt-3 [&_h6]:font-semibold", "[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", "[&_table]:my-2 [&_table]:w-full [&_table]:border-collapse", "[&_td]:border [&_td]:border-border [&_td]:p-2", "[&_th]:border [&_th]:border-border [&_th]:bg-[hsl(var(--table-header-background))] [&_th]:p-2 [&_th]:font-semibold", "[&_pre]:my-2 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:font-mono [&_pre]:text-[0.85em]", "[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.85em]", "[&_pre_code]:bg-transparent [&_pre_code]:p-0", "[&_mark]:rounded-sm [&_mark]:bg-[hsl(var(--status-warning))] [&_mark]:px-0.5 [&_mark]:text-[hsl(var(--status-warning-foreground))]", "[&_img]:h-auto [&_img]:max-w-full", contentClassName);
137
147
  return (_jsxs(_Fragment, { children: [showUnreadDivider && (_jsx(MessageNewDivider, { children: newMessageLabel })), _jsxs("article", { ref: messageRef, className: cn("min-w-0", className), children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-x-3 gap-y-1", children: [_jsx(AvatarProfile, { fallback: getInitials(initialsName), name: senderName, nameAs: "h3", nameTrailing: isApplicant ? (_jsx(Badge, { variant: "warning", bordered: true, children: "Applicant" })) : undefined, role: hasSenderProfileRole ? (_jsx("span", { "data-slot": "message-author-role", children: senderProfileRole })) : undefined, rolePlacement: "below", className: "min-w-0 flex-1 [column-gap:var(--space-3)]", avatarClassName: "h-9 w-9 shrink-0", fallbackClassName: "text-xs font-medium", nameClassName: "max-w-full whitespace-normal break-words [overflow-wrap:anywhere]" }), _jsx("time", { className: "shrink-0 whitespace-nowrap pt-0.5", dateTime: messageDate.toISOString(), children: _jsx(Typography, { as: "span", variant: "caption", className: "text-muted-foreground/60", children: formatTimestamp
138
148
  ? formatTimestamp(messageDate, message)
139
- : formatMessageDate(messageDate) }) })] }), _jsxs("div", { className: "ml-[calc(2.25rem+var(--space-3))] pt-2", children: [_jsx(Typography, { as: "div", variant: "bodySm", color: "inherit", className: richContentClassName, dangerouslySetInnerHTML: { __html: contentHtml } }), attachments.length > 0 ? (_jsx("div", { className: "mt-3 space-y-2", children: attachments.map((attachment, idx) => (_jsx(MessageAttachmentRow, { attachment: attachment, downloadUrl: getAttachmentDownloadUrl === null || getAttachmentDownloadUrl === void 0 ? void 0 : getAttachmentDownloadUrl(attachment, message, downloadCaseId) }, `${attachment.name}-${idx}`))) })) : null] })] })] }));
149
+ : formatNotificationTimestamp(messageDate, {
150
+ now: timestampNow,
151
+ }) }) })] }), _jsxs("div", { className: "ml-[calc(2.25rem+var(--space-3))] pt-2", children: [_jsx(Typography, { as: "div", variant: "bodySm", color: "inherit", className: richContentClassName, dangerouslySetInnerHTML: { __html: contentHtml } }), attachments.length > 0 ? (_jsx("div", { className: "mt-3 space-y-2", children: attachments.map((attachment, idx) => (_jsx(MessageAttachmentRow, { attachment: attachment, downloadUrl: getAttachmentDownloadUrl === null || getAttachmentDownloadUrl === void 0 ? void 0 : getAttachmentDownloadUrl(attachment, message, downloadCaseId) }, `${attachment.name}-${idx}`))) })) : null] })] })] }));
140
152
  }
141
153
  export function MessageThread({ messages, caseId, showNewBadge = false, newMessageLabel = "New Message", onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, className, itemClassName, contentClassName, }) {
142
154
  var _a;
@@ -8,6 +8,11 @@ export type NotificationIcon = React.ComponentType<{
8
8
  className?: string;
9
9
  }>;
10
10
  export type NotificationFilter = "all" | "unread";
11
+ export type NotificationTimestamp = string | number | Date;
12
+ export interface FormatNotificationTimestampOptions {
13
+ now?: NotificationTimestamp;
14
+ locale?: string | string[];
15
+ }
11
16
  export interface NotificationAction {
12
17
  id?: string;
13
18
  label: React.ReactNode;
@@ -21,6 +26,7 @@ export interface NotificationItemData {
21
26
  title: React.ReactNode;
22
27
  description?: React.ReactNode;
23
28
  meta?: React.ReactNode;
29
+ timestamp?: NotificationTimestamp;
24
30
  dateTime?: string;
25
31
  unread?: boolean;
26
32
  kind?: NotificationKind;
@@ -75,8 +81,9 @@ export interface NotificationTriggerProps extends Omit<ButtonProps, "children">
75
81
  label?: React.ReactNode;
76
82
  triggerVariant?: "default" | "sidebar";
77
83
  }
84
+ declare function formatNotificationTimestamp(timestamp: NotificationTimestamp, { now, locale }?: FormatNotificationTimestampOptions): string;
78
85
  declare const NotificationTrigger: React.ForwardRefExoticComponent<NotificationTriggerProps & React.RefAttributes<HTMLButtonElement>>;
79
86
  declare function NotificationItem({ notification, density, labelLines, descriptionLines, unreadLabel, className, onClick, onSelect, ...props }: NotificationItemProps): import("react/jsx-runtime").JSX.Element;
80
87
  declare function NotificationList({ notifications, density, variant, labelLines, descriptionLines, unreadLabel, emptyState, itemClassName, className, onNotificationClick, ...props }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
81
88
  declare function NotificationPopover({ trigger, notifications, title, filter, defaultFilter, onFilterChange, showFilterTabs, height, minHeight, maxHeight, emptyState, listClassName, itemClassName, unreadLabel, markAllAsReadLabel, onNotificationClick, onMarkAllAsRead, contentClassName, align, ...popoverProps }: NotificationPopoverProps): import("react/jsx-runtime").JSX.Element;
82
- export { NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, };
89
+ export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, };
@@ -32,6 +32,67 @@ const notificationToneClasses = {
32
32
  success: "border-status-success-foreground/20 bg-status-success text-status-success-foreground",
33
33
  warning: "border-status-warning-foreground/20 bg-status-warning text-status-warning-foreground",
34
34
  };
35
+ const MINUTE_MS = 60 * 1000;
36
+ const HOUR_MS = 60 * MINUTE_MS;
37
+ const DAY_MS = 24 * HOUR_MS;
38
+ const WEEK_MS = 7 * DAY_MS;
39
+ const NOTIFICATION_TIMESTAMP_REFRESH_MS = MINUTE_MS;
40
+ function parseNotificationTimestamp(timestamp) {
41
+ if (timestamp === undefined) {
42
+ return undefined;
43
+ }
44
+ const date = timestamp instanceof Date
45
+ ? timestamp
46
+ : typeof timestamp === "number"
47
+ ? new Date(timestamp)
48
+ : new Date(timestamp);
49
+ return Number.isFinite(date.getTime()) ? date : undefined;
50
+ }
51
+ function formatNotificationTimestamp(timestamp, { now = new Date(), locale } = {}) {
52
+ const date = parseNotificationTimestamp(timestamp);
53
+ const nowDate = parseNotificationTimestamp(now);
54
+ if (!date || !nowDate) {
55
+ return "";
56
+ }
57
+ const elapsedMs = nowDate.getTime() - date.getTime();
58
+ const absoluteElapsedMs = Math.abs(elapsedMs);
59
+ const isPast = elapsedMs >= 0;
60
+ if (absoluteElapsedMs < MINUTE_MS) {
61
+ return "Just now";
62
+ }
63
+ const tense = isPast ? "ago" : "from now";
64
+ if (absoluteElapsedMs < HOUR_MS) {
65
+ const minutes = Math.round(absoluteElapsedMs / MINUTE_MS);
66
+ return `${minutes} min ${tense}`;
67
+ }
68
+ if (absoluteElapsedMs < DAY_MS) {
69
+ const hours = Math.round(absoluteElapsedMs / HOUR_MS);
70
+ return `${hours}h ${tense}`;
71
+ }
72
+ if (isPast && absoluteElapsedMs < 2 * DAY_MS) {
73
+ return "Yesterday";
74
+ }
75
+ if (absoluteElapsedMs < WEEK_MS) {
76
+ const days = Math.round(absoluteElapsedMs / DAY_MS);
77
+ return `${days} days ${tense}`;
78
+ }
79
+ return new Intl.DateTimeFormat(locale, {
80
+ day: "numeric",
81
+ month: "short",
82
+ ...(date.getFullYear() === nowDate.getFullYear() ? {} : { year: "numeric" }),
83
+ }).format(date);
84
+ }
85
+ function useNotificationTimestampNow(timestamp) {
86
+ const [now, setNow] = React.useState(() => new Date());
87
+ React.useEffect(() => {
88
+ if (timestamp === undefined) {
89
+ return;
90
+ }
91
+ const intervalId = window.setInterval(() => setNow(new Date()), NOTIFICATION_TIMESTAMP_REFRESH_MS);
92
+ return () => window.clearInterval(intervalId);
93
+ }, [timestamp]);
94
+ return now;
95
+ }
35
96
  const NotificationTrigger = React.forwardRef(({ count = 0, countMax = 9, countSize = "md", hideCountWhenZero = true, isCollapsed = false, label = "Notifications", triggerVariant = "default", className, size, variant = "ghost", "aria-label": ariaLabel, ...props }, ref) => {
36
97
  const resolvedAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (isCollapsed && typeof label === "string" ? label : undefined);
37
98
  return (_jsxs(Button, { ref: ref, type: "button", variant: variant, size: size !== null && size !== void 0 ? size : (isCollapsed ? "icon" : "default"), "aria-label": resolvedAriaLabel, className: cn("relative", triggerVariant === "sidebar" &&
@@ -58,7 +119,7 @@ function getNotificationTone(notification) {
58
119
  return ((_a = notification.tone) !== null && _a !== void 0 ? _a : notificationKindTones[(_b = notification.kind) !== null && _b !== void 0 ? _b : "system"]);
59
120
  }
60
121
  function NotificationItem({ notification, density = "default", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", className, onClick, onSelect, ...props }) {
61
- var _a;
122
+ var _a, _b, _c;
62
123
  const Icon = getNotificationIcon(notification);
63
124
  const tone = getNotificationTone(notification);
64
125
  const isUnread = Boolean(notification.unread);
@@ -66,14 +127,21 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
66
127
  const hasActions = actions.length > 0;
67
128
  const isInteractive = Boolean(!hasActions && (notification.href || onClick || onSelect));
68
129
  const hasHoverSurface = isInteractive || hasActions;
69
- const meta = notification.meta ? (notification.dateTime ? (_jsx("time", { dateTime: notification.dateTime, children: notification.meta })) : (_jsx("span", { children: notification.meta }))) : null;
130
+ const timestamp = notification.timestamp;
131
+ const now = useNotificationTimestampNow(timestamp);
132
+ const formattedTimestamp = timestamp !== undefined
133
+ ? formatNotificationTimestamp(timestamp, { now })
134
+ : undefined;
135
+ const metaContent = (_b = notification.meta) !== null && _b !== void 0 ? _b : formattedTimestamp;
136
+ const timestampDateTime = (_c = parseNotificationTimestamp(timestamp)) === null || _c === void 0 ? void 0 : _c.toISOString();
137
+ const meta = metaContent ? (timestampDateTime || notification.dateTime ? (_jsx("time", { dateTime: timestampDateTime !== null && timestampDateTime !== void 0 ? timestampDateTime : notification.dateTime, children: metaContent })) : (_jsx("span", { children: metaContent }))) : null;
70
138
  const handleClick = (event) => {
71
139
  onClick === null || onClick === void 0 ? void 0 : onClick(event);
72
140
  if (!event.defaultPrevented) {
73
141
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(notification);
74
142
  }
75
143
  };
76
- const content = (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: cn("mt-0.5 flex shrink-0 items-center justify-center rounded-control border", density === "compact" ? "h-7 w-7" : "h-8 w-8", notificationToneClasses[tone]), children: _jsx(Icon, { className: density === "compact" ? "h-3.5 w-3.5" : "h-4 w-4" }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-start gap-3", children: [_jsx(Typography, { as: "span", variant: "bodySm", className: cn("min-w-0 text-left text-foreground", isUnread ? "font-semibold" : "font-medium", labelLineClasses[labelLines]), children: notification.title }), meta || (isUnread && unreadLabel !== null) ? (_jsxs("span", { className: "flex min-w-0 shrink-0 items-center justify-end gap-1.5 text-right", children: [meta ? (_jsx(Typography, { as: "span", variant: "caption", className: "min-w-0 max-w-24 truncate whitespace-nowrap text-muted-foreground/60", children: meta })) : null, isUnread && unreadLabel !== null ? (_jsx("span", { "data-slot": "notification-unread-indicator", className: "mt-1 h-2.5 w-2.5 shrink-0 rounded-full bg-primary ring-2 ring-background", children: _jsx("span", { className: "sr-only", children: unreadLabel }) })) : null] })) : null] }), notification.description !== undefined ? (_jsx(Typography, { as: "span", variant: "caption", className: cn("mt-1.5 block text-left", isUnread ? "text-foreground/75" : "text-muted-foreground", descriptionLineClasses[descriptionLines]), children: notification.description })) : null, hasActions ? (_jsx("span", { className: "mt-4 flex flex-wrap gap-2", children: actions.map((action, index) => {
144
+ const content = (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: cn("mt-0.5 flex shrink-0 items-center justify-center rounded-control border", density === "compact" ? "h-7 w-7" : "h-8 w-8", notificationToneClasses[tone]), children: _jsx(Icon, { className: density === "compact" ? "h-3.5 w-3.5" : "h-4 w-4" }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-start gap-3", children: [_jsx(Typography, { as: "span", variant: "bodySm", className: cn("min-w-0 text-left text-foreground", isUnread ? "font-semibold" : "font-medium", labelLineClasses[labelLines]), children: notification.title }), meta || (isUnread && unreadLabel !== null) ? (_jsxs("span", { className: "flex min-w-0 shrink-0 items-center justify-end gap-1.5 text-right", children: [meta ? (_jsx(Typography, { as: "span", variant: "caption", className: "shrink-0 whitespace-nowrap text-muted-foreground/70", children: meta })) : null, isUnread && unreadLabel !== null ? (_jsx("span", { "data-slot": "notification-unread-indicator", className: "mt-1 h-2.5 w-2.5 shrink-0 rounded-full bg-primary ring-2 ring-background", children: _jsx("span", { className: "sr-only", children: unreadLabel }) })) : null] })) : null] }), notification.description !== undefined ? (_jsx(Typography, { as: "span", variant: "caption", className: cn("mt-1.5 block text-left", isUnread ? "text-foreground/75" : "text-muted-foreground", descriptionLineClasses[descriptionLines]), children: notification.description })) : null, hasActions ? (_jsx("span", { className: "mt-4 flex flex-wrap gap-2", children: actions.map((action, index) => {
77
145
  var _a, _b;
78
146
  return (_jsx(Button, { type: "button", variant: (_a = action.variant) !== null && _a !== void 0 ? _a : (index === 0 ? "outline" : "default"), size: "sm", disabled: action.disabled, className: cn("min-w-24", action.className), onClick: (event) => {
79
147
  var _a;
@@ -118,4 +186,4 @@ function NotificationPopover({ trigger, notifications, title = "Notifications",
118
186
  { value: "unread", label: "Unread", content: null },
119
187
  ] })) : null] }), _jsx(PopoverMenuDivider, { className: "mx-0 my-0 shrink-0 bg-border/90" }), _jsx(PopoverMenuScrollArea, { maxHeight: "max-h-none", className: "scrollbar-none min-h-0 flex-1 space-y-0", children: _jsx(NotificationList, { notifications: visibleNotifications, variant: "plain", emptyState: resolvedEmptyState, itemClassName: cn("py-3", itemClassName), unreadLabel: unreadLabel, onNotificationClick: onNotificationClick, className: cn("gap-0", listClassName) }) }), onMarkAllAsRead ? (_jsx(PopoverMenuFooter, { inset: "flush", className: "border-border/80", children: _jsx(PopoverMenuFooterAction, { onClick: onMarkAllAsRead, disabled: resolvedUnreadCount === 0, children: markAllAsReadLabel }) })) : null] }));
120
188
  }
121
- export { NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, };
189
+ export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, };
package/dist/popover.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as PopoverPrimitive from "@radix-ui/react-popover";
3
+ import { type TooltipProps } from "./tooltip";
3
4
  type PopoverRootProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>;
4
5
  type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>;
5
6
  export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "modal" | "onOpenChange" | "open">, Omit<PopoverContentProps, "children" | "className"> {
@@ -7,6 +8,10 @@ export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "mo
7
8
  children: React.ReactNode;
8
9
  surface?: "panel" | "menu";
9
10
  contentClassName?: string;
11
+ triggerTooltip?: React.ReactNode;
12
+ triggerTooltipSide?: TooltipProps["side"];
13
+ triggerTooltipSideOffset?: TooltipProps["sideOffset"];
14
+ triggerTooltipClassName?: TooltipProps["contentClassName"];
10
15
  }
11
- declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, surface, contentClassName, align, sideOffset, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
16
+ declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, surface, contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align, sideOffset, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
12
17
  export { Popover };
package/dist/popover.js CHANGED
@@ -2,7 +2,9 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { cn } from "./lib/utils";
4
4
  import { PopoverContent, PopoverRoot, PopoverTrigger, } from "./popover-primitive";
5
- function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, surface = "panel", contentClassName, align = "center", sideOffset = 4, ...contentProps }) {
6
- return (_jsxs(PopoverRoot, { defaultOpen: defaultOpen, modal: modal, onOpenChange: onOpenChange, open: open, children: [_jsx(PopoverTrigger, { asChild: true, children: trigger }), _jsx(PopoverContent, { align: align, sideOffset: sideOffset, className: cn(surface === "menu" && "overflow-hidden p-1", contentClassName), ...contentProps, children: children })] }));
5
+ import { Tooltip } from "./tooltip";
6
+ function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, surface = "panel", contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align = "center", sideOffset = 4, ...contentProps }) {
7
+ const triggerElement = _jsx(PopoverTrigger, { asChild: true, children: trigger });
8
+ return (_jsxs(PopoverRoot, { defaultOpen: defaultOpen, modal: modal, onOpenChange: onOpenChange, open: open, children: [triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsx(PopoverContent, { align: align, sideOffset: sideOffset, className: cn(surface === "menu" && "overflow-hidden p-1", contentClassName), ...contentProps, children: children })] }));
7
9
  }
8
10
  export { Popover };
@@ -11,6 +11,7 @@ export interface TableColumnVisibilityMenuProps<Row> extends PopoverPlacementPro
11
11
  trigger?: React.ReactElement;
12
12
  triggerLabel?: string;
13
13
  triggerClassName?: string;
14
+ triggerTooltip?: React.ReactNode;
14
15
  contentClassName?: string;
15
16
  className?: string;
16
17
  title?: React.ReactNode;
@@ -19,5 +20,5 @@ export interface TableColumnVisibilityMenuProps<Row> extends PopoverPlacementPro
19
20
  emptyMessage?: React.ReactNode;
20
21
  minVisibleColumns?: number;
21
22
  }
22
- declare function TableColumnVisibilityMenu<Row>({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel, triggerClassName, contentClassName, className, title, searchPlaceholder, restoreDefaultsLabel, emptyMessage, minVisibleColumns, align, sideOffset, side, }: TableColumnVisibilityMenuProps<Row>): import("react/jsx-runtime").JSX.Element;
23
+ declare function TableColumnVisibilityMenu<Row>({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel, triggerClassName, triggerTooltip, contentClassName, className, title, searchPlaceholder, restoreDefaultsLabel, emptyMessage, minVisibleColumns, align, sideOffset, side, }: TableColumnVisibilityMenuProps<Row>): import("react/jsx-runtime").JSX.Element;
23
24
  export { TableColumnVisibilityMenu };
@@ -52,7 +52,8 @@ function SortableColumnOption({ columnKey, label, checked, disabled, onToggle, }
52
52
  ? "cursor-grab active:cursor-grabbing"
53
53
  : "cursor-not-allowed opacity-40"), ...(checked ? { ...attributes, ...listeners } : {}), children: _jsx(GripVertical, { className: "h-4 w-4", "aria-hidden": "true" }) }), _jsx(Checkbox, { id: checkboxId, checked: checked, disabled: disabled, onChange: onToggle, "aria-label": label }), _jsx("label", { htmlFor: checkboxId, className: cn("min-w-0 flex-1 cursor-pointer truncate", disabled && "cursor-not-allowed text-muted-foreground"), children: label })] }));
54
54
  }
55
- function TableColumnVisibilityMenu({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel = "Customize columns", triggerClassName, contentClassName, className, title = "Customize columns", searchPlaceholder = "Search columns", restoreDefaultsLabel = "Restore defaults", emptyMessage = "No columns found", minVisibleColumns = 1, align = "end", sideOffset = 4, side, }) {
55
+ function TableColumnVisibilityMenu({ columns, visibleColumnKeys, onVisibleColumnKeysChange, defaultVisibleColumnKeys, trigger, triggerLabel = "Customize columns", triggerClassName, triggerTooltip, contentClassName, className, title = "Customize columns", searchPlaceholder = "Search columns", restoreDefaultsLabel = "Restore defaults", emptyMessage = "No columns found", minVisibleColumns = 1, align = "end", sideOffset = 4, side, }) {
56
+ const resolvedTriggerTooltip = triggerTooltip === undefined && !trigger ? triggerLabel : triggerTooltip;
56
57
  const [query, setQuery] = React.useState("");
57
58
  const sensors = useSensors(useSensor(PointerSensor, {
58
59
  activationConstraint: {
@@ -111,7 +112,7 @@ function TableColumnVisibilityMenu({ columns, visibleColumnKeys, onVisibleColumn
111
112
  commitVisibleColumnKeys(arrayMove(currentKeys, oldIndex, newIndex));
112
113
  }, [commitVisibleColumnKeys, currentKeys]);
113
114
  const defaultTrigger = (_jsx(Button, { type: "button", variant: "outline", size: "icon", "aria-label": triggerLabel, className: triggerClassName, children: _jsx(Columns3, { "aria-hidden": "true" }) }));
114
- return (_jsx(Popover, { trigger: trigger !== null && trigger !== void 0 ? trigger : defaultTrigger, align: align, side: side, sideOffset: sideOffset, contentClassName: cn("w-80 p-0", contentClassName), "aria-labelledby": title ? titleId : undefined, children: _jsxs("div", { className: cn("flex max-h-96 flex-col", className), children: [_jsxs("div", { className: "border-b p-2", children: [_jsx("label", { htmlFor: searchInputId, className: "sr-only", children: searchPlaceholder }), _jsx(SearchInputShell, { children: _jsx(InputControl, { id: searchInputId, value: query, onChange: (event) => setQuery(event.target.value), placeholder: searchPlaceholder, className: "h-8 bg-background pl-8 pr-2" }) })] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto p-1", children: [title ? (_jsx(PopoverMenuGroupLabel, { id: titleId, children: title })) : null, filteredColumns.length ? (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: filteredColumns.map((column) => column.key), strategy: verticalListSortingStrategy, children: filteredColumns.map((column) => {
115
+ return (_jsx(Popover, { trigger: trigger !== null && trigger !== void 0 ? trigger : defaultTrigger, triggerTooltip: resolvedTriggerTooltip, align: align, side: side, sideOffset: sideOffset, contentClassName: cn("w-80 p-0", contentClassName), "aria-labelledby": title ? titleId : undefined, children: _jsxs("div", { className: cn("flex max-h-96 flex-col", className), children: [_jsxs("div", { className: "border-b p-2", children: [_jsx("label", { htmlFor: searchInputId, className: "sr-only", children: searchPlaceholder }), _jsx(SearchInputShell, { children: _jsx(InputControl, { id: searchInputId, value: query, onChange: (event) => setQuery(event.target.value), placeholder: searchPlaceholder, className: "h-8 bg-background pl-8 pr-2" }) })] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto p-1", children: [title ? (_jsx(PopoverMenuGroupLabel, { id: titleId, children: title })) : null, filteredColumns.length ? (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: filteredColumns.map((column) => column.key), strategy: verticalListSortingStrategy, children: filteredColumns.map((column) => {
115
116
  const label = getColumnVisibilityLabel(column);
116
117
  const checked = visibleColumnKeySet.has(column.key);
117
118
  const disabled = checked && visibleColumnCount <= requiredVisibleColumns;
package/dist/table.d.ts CHANGED
@@ -8,6 +8,7 @@ interface TableContainerProps extends React.HTMLAttributes<HTMLDivElement> {
8
8
  }
9
9
  declare const TableContainer: React.ForwardRefExoticComponent<TableContainerProps & React.RefAttributes<HTMLDivElement>>;
10
10
  export type TableColumnWidths = Record<string, number>;
11
+ export type TableResizeMode = "pair" | "grow";
11
12
  declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
12
13
  declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
13
14
  declare const TableFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
@@ -130,6 +131,8 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
130
131
  rowTone?: TableRowTone | ((row: Row, index: number) => TableRowTone | undefined);
131
132
  /** Constrains the table container height so the table body scrolls instead of the surrounding surface. */
132
133
  maxHeight?: React.CSSProperties["maxHeight"];
134
+ /** Sets a minimum table container height. */
135
+ minHeight?: React.CSSProperties["minHeight"];
133
136
  sorting?: TableSortingProps;
134
137
  pagination?: Omit<PaginationProps, "className">;
135
138
  loading?: boolean;
@@ -138,6 +141,8 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
138
141
  visibleColumnKeys?: readonly string[];
139
142
  /** When true, every column is resizable unless its column config sets `resizable: false`. */
140
143
  resizableColumns?: boolean;
144
+ /** `pair` preserves total table width; `grow` resizes one column and lets the table widen. */
145
+ columnResizeMode?: TableResizeMode;
141
146
  /** Controlled pixel widths keyed by column key. */
142
147
  columnWidths?: TableColumnWidths;
143
148
  /** Initial pixel widths keyed by column key for uncontrolled resizing. */
@@ -146,10 +151,16 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
146
151
  onColumnWidthsChange?: (columnWidths: TableColumnWidths) => void;
147
152
  /** Optional hook for decorating resize handles without replacing table sizing behavior. */
148
153
  renderColumnResizeHandle?: (props: TableColumnResizeHandleRenderProps<Row>) => React.ReactNode;
154
+ /** Forces the browser table layout mode. Resizable tables always use fixed layout. */
155
+ tableLayout?: "auto" | "fixed";
156
+ /** Centers the empty state inside the table body when there are no rows. */
157
+ centerEmptyState?: boolean;
158
+ /** Minimum height for the centered empty-state area. */
159
+ emptyStateMinHeight?: React.CSSProperties["minHeight"];
149
160
  /** When true, applies alternating row backgrounds for easier scanning. */
150
161
  striped?: boolean;
151
162
  /** When set, rows become interactive: cursor-pointer, hover highlight, and click handling. */
152
163
  onRowClick?: (row: Row, index: number) => void;
153
164
  }
154
- declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, sorting, pagination, loading, loadingRows, visibleColumnKeys, resizableColumns, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, striped, onRowClick, onScroll, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
165
+ declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, minHeight, sorting, pagination, loading, loadingRows, visibleColumnKeys, resizableColumns, columnResizeMode, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, tableLayout, centerEmptyState, emptyStateMinHeight, striped, onRowClick, onScroll, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
155
166
  export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, SortableTableHead, TableRowSkeleton, };
package/dist/table.js CHANGED
@@ -14,6 +14,9 @@ TableContainer.displayName = "TableContainer";
14
14
  const DEFAULT_COLUMN_RESIZE_MIN_WIDTH = 64;
15
15
  const DEFAULT_COLUMN_RESIZE_START_WIDTH = 160;
16
16
  const COLUMN_RESIZE_KEYBOARD_STEP = 16;
17
+ const TABLE_MIN_WIDTH_CSS_VAR = "--table-min-width";
18
+ const DEFAULT_TABLE_MIN_HEIGHT = "var(--table-min-height, var(--app-table-min-height, 24rem))";
19
+ const DEFAULT_CENTERED_EMPTY_STATE_MIN_HEIGHT = "calc(var(--table-min-height, var(--app-table-min-height, 24rem)) - var(--table-empty-state-offset, var(--app-table-empty-state-offset, 5.75rem)))";
17
20
  function getTableColumnSizingStyle({ style, width, minWidth, maxWidth, treatWidthAsMinWidth = true, }) {
18
21
  const resolvedMinWidth = minWidth !== null && minWidth !== void 0 ? minWidth : (treatWidthAsMinWidth ? width : undefined);
19
22
  return {
@@ -101,6 +104,28 @@ function getColumnResizeBaseWidth(column, columnWidths, elementWidth) {
101
104
  var _a, _b, _c, _d;
102
105
  return ((_d = (_c = (_b = (_a = columnWidths[column.key]) !== null && _a !== void 0 ? _a : parsePixelSize(column.width)) !== null && _b !== void 0 ? _b : (elementWidth && elementWidth > 0 ? elementWidth : undefined)) !== null && _c !== void 0 ? _c : parsePixelSize(column.minWidth)) !== null && _d !== void 0 ? _d : DEFAULT_COLUMN_RESIZE_START_WIDTH);
103
106
  }
107
+ function hasColumnWidthOverrides(columnWidths) {
108
+ return Object.keys(columnWidths).length > 0;
109
+ }
110
+ function getMeasuredColumnWidths(resizeHandle, columns, fallbackColumnWidths) {
111
+ const table = resizeHandle.closest("table");
112
+ const headerCells = table === null || table === void 0 ? void 0 : table.querySelectorAll("thead th");
113
+ if (!(headerCells === null || headerCells === void 0 ? void 0 : headerCells.length)) {
114
+ return fallbackColumnWidths;
115
+ }
116
+ return columns.reduce((widths, column, index) => {
117
+ var _a;
118
+ const measuredWidth = (_a = headerCells.item(index)) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width;
119
+ widths[column.key] =
120
+ measuredWidth && Number.isFinite(measuredWidth) && measuredWidth > 0
121
+ ? Math.round(measuredWidth)
122
+ : getColumnResizeBaseWidth(column, fallbackColumnWidths);
123
+ return widths;
124
+ }, {});
125
+ }
126
+ function getTableMinWidth(columns, columnWidths) {
127
+ return columns.reduce((total, column) => total + getColumnResizeBaseWidth(column, columnWidths), 0);
128
+ }
104
129
  function getColumnResizeLabel(column) {
105
130
  if (column.visibilityLabel) {
106
131
  return column.visibilityLabel;
@@ -243,6 +268,26 @@ const TableColumnResizeHandle = React.forwardRef(function TableColumnResizeHandl
243
268
  : COLUMN_RESIZE_KEYBOARD_STEP);
244
269
  }, className: cn("group absolute right-0 top-0 z-[1] flex h-full w-4 cursor-col-resize touch-none items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className), children: _jsx("span", { "aria-hidden": "true", className: "h-6 w-px rounded-full bg-[hsl(var(--table-border))] transition-colors group-hover:bg-primary group-focus-visible:bg-primary" }) }));
245
270
  });
271
+ const TableColumnGrowResizeHandle = React.forwardRef(function TableColumnGrowResizeHandle({ column, currentWidth, minWidth, maxWidth, onResizeStart, onResizeByKeyboard, className, onClick, ...props }, ref) {
272
+ const label = getColumnResizeLabel(column);
273
+ return (_jsx("button", { ...props, ref: ref, type: "button", "aria-label": `Resize ${label} column`, title: `Resize ${label} column`, "aria-valuenow": currentWidth, "aria-valuemin": minWidth, "aria-valuemax": maxWidth, onPointerDown: (event) => onResizeStart(column, event), onClick: (event) => {
274
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
275
+ if (event.defaultPrevented) {
276
+ return;
277
+ }
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+ }, onKeyDown: (event) => {
281
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
282
+ return;
283
+ }
284
+ event.preventDefault();
285
+ event.stopPropagation();
286
+ onResizeByKeyboard(column, event.key === "ArrowLeft"
287
+ ? -COLUMN_RESIZE_KEYBOARD_STEP
288
+ : COLUMN_RESIZE_KEYBOARD_STEP);
289
+ }, className: cn("group absolute right-0 top-0 z-[1] flex h-full w-4 cursor-col-resize touch-none items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className), children: _jsx("span", { "aria-hidden": "true", className: "h-6 w-px rounded-full bg-[hsl(var(--table-border))] transition-colors group-hover:bg-primary group-focus-visible:bg-primary" }) }));
290
+ });
246
291
  function getDefaultSkeleton(column) {
247
292
  var _a, _b;
248
293
  return {
@@ -282,15 +327,21 @@ function getVisibleColumns(columns, visibleColumnKeys) {
282
327
  function isStickyResizeBoundary(column, nextColumn) {
283
328
  return Boolean(column.sticky === "right" || nextColumn.sticky);
284
329
  }
285
- function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, sorting, pagination, loading = false, loadingRows = 5, visibleColumnKeys, resizableColumns = false, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, striped = false, onRowClick, onScroll, style, ...props }) {
330
+ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, minHeight, sorting, pagination, loading = false, loadingRows = 5, visibleColumnKeys, resizableColumns = false, columnResizeMode = "pair", columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, tableLayout, centerEmptyState = false, emptyStateMinHeight = DEFAULT_CENTERED_EMPTY_STATE_MIN_HEIGHT, striped = false, onRowClick, onScroll, style, ...props }) {
286
331
  const visibleColumns = getVisibleColumns(columns, visibleColumnKeys);
332
+ const isGrowResizeMode = columnResizeMode === "grow";
287
333
  const isColumnResizingEnabled = resizableColumns || visibleColumns.some((column) => column.resizable);
288
334
  // Use auto layout by default so the browser can grow a column to contain its
289
335
  // nowrap header. Resizable columns need fixed layout so neighbour columns do
290
336
  // not redistribute width while a handle is dragged.
291
- const shouldUseFixedLayout = isColumnResizingEnabled;
337
+ const shouldUseFixedLayout = tableLayout === "fixed" || isColumnResizingEnabled;
292
338
  const [uncontrolledColumnWidths, setUncontrolledColumnWidths] = React.useState(() => ({ ...(defaultColumnWidths !== null && defaultColumnWidths !== void 0 ? defaultColumnWidths : {}) }));
293
339
  const resolvedColumnWidths = columnWidths !== null && columnWidths !== void 0 ? columnWidths : uncontrolledColumnWidths;
340
+ const hasResolvedColumnWidths = hasColumnWidthOverrides(resolvedColumnWidths);
341
+ const growTableMinWidth = isGrowResizeMode
342
+ ? getTableMinWidth(visibleColumns, resolvedColumnWidths)
343
+ : undefined;
344
+ const resolvedMinHeight = minHeight !== null && minHeight !== void 0 ? minHeight : (centerEmptyState ? DEFAULT_TABLE_MIN_HEIGHT : undefined);
294
345
  const columnWidthsRef = React.useRef(resolvedColumnWidths);
295
346
  const resizeCleanupRef = React.useRef(null);
296
347
  const tableContainerRef = React.useRef(null);
@@ -355,6 +406,17 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
355
406
  const resizeColumnByKeyboard = React.useCallback((column, nextColumn, delta) => {
356
407
  resizeColumnPairByDelta(column, nextColumn, delta);
357
408
  }, [resizeColumnPairByDelta]);
409
+ const resizeGrowColumn = React.useCallback((column, width, baseColumnWidths = columnWidthsRef.current) => {
410
+ const nextWidth = clampColumnResizeWidth(column, width);
411
+ commitColumnWidths((currentColumnWidths) => ({
412
+ ...currentColumnWidths,
413
+ ...baseColumnWidths,
414
+ [column.key]: nextWidth,
415
+ }));
416
+ }, [commitColumnWidths]);
417
+ const resizeGrowColumnByKeyboard = React.useCallback((column, delta) => {
418
+ resizeGrowColumn(column, getColumnResizeBaseWidth(column, columnWidthsRef.current) + delta);
419
+ }, [resizeGrowColumn]);
358
420
  const startColumnResize = React.useCallback((column, nextColumn, event) => {
359
421
  var _a;
360
422
  event.preventDefault();
@@ -375,13 +437,47 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
375
437
  const cleanup = () => {
376
438
  ownerDocument.removeEventListener("pointermove", handlePointerMove);
377
439
  ownerDocument.removeEventListener("pointerup", handlePointerUp);
440
+ ownerDocument.removeEventListener("pointercancel", handlePointerUp);
378
441
  ownerDocument.documentElement.style.cursor = previousCursor;
379
442
  resizeCleanupRef.current = null;
380
443
  };
381
444
  resizeCleanupRef.current = cleanup;
382
445
  ownerDocument.addEventListener("pointermove", handlePointerMove);
383
446
  ownerDocument.addEventListener("pointerup", handlePointerUp);
447
+ ownerDocument.addEventListener("pointercancel", handlePointerUp);
384
448
  }, [resizeColumnPairByDelta]);
449
+ const startGrowColumnResize = React.useCallback((column, event) => {
450
+ var _a, _b;
451
+ event.preventDefault();
452
+ event.stopPropagation();
453
+ (_a = resizeCleanupRef.current) === null || _a === void 0 ? void 0 : _a.call(resizeCleanupRef);
454
+ const ownerDocument = event.currentTarget.ownerDocument;
455
+ const startX = event.clientX;
456
+ const startColumnWidths = hasColumnWidthOverrides(columnWidthsRef.current)
457
+ ? columnWidthsRef.current
458
+ : getMeasuredColumnWidths(event.currentTarget, visibleColumns, columnWidthsRef.current);
459
+ const startWidth = (_b = startColumnWidths[column.key]) !== null && _b !== void 0 ? _b : getColumnResizeBaseWidth(column, startColumnWidths);
460
+ const previousCursor = ownerDocument.documentElement.style.cursor;
461
+ ownerDocument.documentElement.style.cursor = "col-resize";
462
+ const handlePointerMove = (pointerEvent) => {
463
+ resizeGrowColumn(column, startWidth + pointerEvent.clientX - startX, startColumnWidths);
464
+ };
465
+ const handlePointerUp = () => {
466
+ var _a;
467
+ (_a = resizeCleanupRef.current) === null || _a === void 0 ? void 0 : _a.call(resizeCleanupRef);
468
+ };
469
+ const cleanup = () => {
470
+ ownerDocument.removeEventListener("pointermove", handlePointerMove);
471
+ ownerDocument.removeEventListener("pointerup", handlePointerUp);
472
+ ownerDocument.removeEventListener("pointercancel", handlePointerUp);
473
+ ownerDocument.documentElement.style.cursor = previousCursor;
474
+ resizeCleanupRef.current = null;
475
+ };
476
+ resizeCleanupRef.current = cleanup;
477
+ ownerDocument.addEventListener("pointermove", handlePointerMove);
478
+ ownerDocument.addEventListener("pointerup", handlePointerUp);
479
+ ownerDocument.addEventListener("pointercancel", handlePointerUp);
480
+ }, [resizeGrowColumn, visibleColumns]);
385
481
  const renderResizeHandle = React.useCallback((column, columnIndex) => {
386
482
  var _a, _b;
387
483
  const nextColumn = visibleColumns[columnIndex + 1];
@@ -394,18 +490,25 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
394
490
  if (stickyResizeBoundary) {
395
491
  return null;
396
492
  }
397
- const handle = (_jsx(TableColumnResizeHandle, { column: column, nextColumn: nextColumn, currentWidth: getResolvedResizableColumnWidth(column, resolvedColumnWidths), minWidth: getColumnResizeMinWidth(column), maxWidth: Number.isFinite(getColumnResizeMaxWidth(column))
398
- ? getColumnResizeMaxWidth(column)
399
- : undefined, onResizeStart: startColumnResize, onResizeByKeyboard: resizeColumnByKeyboard }));
493
+ const currentWidth = isGrowResizeMode
494
+ ? getColumnResizeBaseWidth(column, resolvedColumnWidths)
495
+ : getResolvedResizableColumnWidth(column, resolvedColumnWidths);
496
+ const maxWidth = Number.isFinite(getColumnResizeMaxWidth(column))
497
+ ? getColumnResizeMaxWidth(column)
498
+ : undefined;
499
+ const handle = isGrowResizeMode ? (_jsx(TableColumnGrowResizeHandle, { column: column, currentWidth: currentWidth, minWidth: getColumnResizeMinWidth(column), maxWidth: maxWidth, onResizeStart: startGrowColumnResize, onResizeByKeyboard: resizeGrowColumnByKeyboard })) : (_jsx(TableColumnResizeHandle, { column: column, nextColumn: nextColumn, currentWidth: currentWidth, minWidth: getColumnResizeMinWidth(column), maxWidth: maxWidth, onResizeStart: startColumnResize, onResizeByKeyboard: resizeColumnByKeyboard }));
400
500
  if (renderColumnResizeHandle) {
401
501
  return renderColumnResizeHandle({ column, nextColumn, handle });
402
502
  }
403
503
  return handle;
404
504
  }, [
505
+ isGrowResizeMode,
405
506
  resizableColumns,
406
507
  renderColumnResizeHandle,
508
+ resizeGrowColumnByKeyboard,
407
509
  resizeColumnByKeyboard,
408
510
  resolvedColumnWidths,
511
+ startGrowColumnResize,
409
512
  startColumnResize,
410
513
  visibleColumns,
411
514
  ]);
@@ -413,10 +516,18 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
413
516
  onScroll === null || onScroll === void 0 ? void 0 : onScroll(event);
414
517
  updateStickyScrollState(event.currentTarget);
415
518
  }, [onScroll, updateStickyScrollState]);
519
+ const resolvedEmptyState = centerEmptyState ? (_jsx("div", { "data-slot": "table-empty-state", className: "flex w-full min-w-0 items-center justify-center whitespace-normal text-center [&>*]:mx-auto", style: { minHeight: emptyStateMinHeight }, children: emptyState })) : (emptyState);
416
520
  const container = (_jsx(TableContainer, { ref: tableContainerRef, onScroll: handleTableContainerScroll, className: cn("w-full", loading && "pointer-events-none select-none", className, containerClassName), style: {
417
521
  ...style,
522
+ ...(growTableMinWidth !== undefined
523
+ ? { [TABLE_MIN_WIDTH_CSS_VAR]: `${growTableMinWidth}px` }
524
+ : {}),
418
525
  ...(maxHeight !== undefined ? { maxHeight } : {}),
419
- }, ...props, children: _jsxs(TableRoot, { className: cn(shouldUseFixedLayout && "table-fixed", tableClassName), children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx("colgroup", { children: visibleColumns.map((column) => (_jsx("col", { style: getTableColumnSizingStyle({
526
+ ...(resolvedMinHeight !== undefined ? { minHeight: resolvedMinHeight } : {}),
527
+ }, ...props, children: _jsxs(TableRoot, { className: cn(shouldUseFixedLayout && "table-fixed", isGrowResizeMode &&
528
+ (hasResolvedColumnWidths
529
+ ? "w-[var(--table-min-width)]"
530
+ : "min-w-[var(--table-min-width)]"), tableClassName), children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx("colgroup", { children: visibleColumns.map((column) => (_jsx("col", { style: getTableColumnSizingStyle({
420
531
  width: getRenderedColumnWidth({
421
532
  column,
422
533
  columnWidths: resolvedColumnWidths,
@@ -458,7 +569,7 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
458
569
  : null);
459
570
  return (_jsx(TableCell, { width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), wrap: column.wrap, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "cell", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.cellClassName), children: value }, column.key));
460
571
  }) }, getRowKey(row, rowIndex)));
461
- })) : (_jsx(TableRow, { className: "cursor-default hover:bg-card", children: _jsx(TableCell, { colSpan: Math.max(visibleColumns.length, 1), className: "p-6", children: emptyState }) })) })] }) }));
572
+ })) : (_jsx(TableRow, { className: "cursor-default hover:bg-card", children: _jsx(TableCell, { colSpan: Math.max(visibleColumns.length, 1), className: "p-6", children: resolvedEmptyState }) })) })] }) }));
462
573
  if (!pagination) {
463
574
  return (_jsxs("div", { className: "space-y-4", children: [toolbar, container] }));
464
575
  }
@@ -1,13 +1,17 @@
1
1
  import * as React from "react";
2
+ import { type BackButtonProps } from "./back-button";
3
+ export declare const TASK_MODE_SHELL_ANIMATION_DURATION_MS = 250;
2
4
  export interface TaskModeShellProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
3
- header: React.ReactNode;
5
+ header?: React.ReactNode;
4
6
  children: React.ReactNode;
5
7
  loadingProgress?: number;
6
8
  showLoadingBar?: boolean;
7
9
  loadingLabel?: string;
8
10
  ready?: boolean;
9
11
  exiting?: boolean;
12
+ backButtonProps?: BackButtonProps;
10
13
  headerClassName?: string;
14
+ headerContentClassName?: string;
11
15
  contentOffsetClassName?: string;
12
16
  bodyClassName?: string;
13
17
  mainPanelClassName?: string;
@@ -3,26 +3,39 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { GripVertical } from "lucide-react";
5
5
  import { cn } from "./lib/utils";
6
+ import { BackButton } from "./back-button";
6
7
  import { rightPanelEdgeClassName, rightPanelEdgeClosedClassName, rightPanelEdgeDividerClassName, rightPanelEdgeOpenClassName, rightPanelResizeHandleSurfaceClassName, } from "./layout-right-panel-edge";
7
8
  import { Progress } from "./progress";
8
9
  const TASK_MODE_SIDE_PANEL_DEFAULT_SIZE = 50;
9
10
  const TASK_MODE_SIDE_PANEL_MIN_SIZE = 28;
10
11
  const TASK_MODE_SIDE_PANEL_MAX_SIZE = 72;
11
12
  const TASK_MODE_SIDE_PANEL_SIZE_CSS_VAR = "--task-mode-side-panel-size";
13
+ export const TASK_MODE_SHELL_ANIMATION_DURATION_MS = 250;
12
14
  function clampSidePanelSize(size, minSize, maxSize) {
13
15
  return Math.min(Math.max(size, minSize), maxSize);
14
16
  }
15
17
  function getResizableSplitTemplate() {
16
18
  return `minmax(0, calc(100% - var(${TASK_MODE_SIDE_PANEL_SIZE_CSS_VAR}))) minmax(0, var(${TASK_MODE_SIDE_PANEL_SIZE_CSS_VAR}))`;
17
19
  }
18
- export const TaskModeShell = React.forwardRef(({ className, header, children, loadingProgress = 0, showLoadingBar = false, loadingLabel = "Loading task", ready = true, exiting = false, headerClassName, contentOffsetClassName = "pt-16", bodyClassName, mainPanelClassName, sidePanel, sidePanelOpen = false, sidePanelClassName, sidePanelAriaLabel, sidePanelResizable = false, sidePanelDefaultSize = TASK_MODE_SIDE_PANEL_DEFAULT_SIZE, sidePanelMinSize = TASK_MODE_SIDE_PANEL_MIN_SIZE, sidePanelMaxSize = TASK_MODE_SIDE_PANEL_MAX_SIZE, splitDividerClassName, ...props }, ref) => {
20
+ export const TaskModeShell = React.forwardRef(({ className, header, children, loadingProgress = 0, showLoadingBar = false, loadingLabel = "Loading task", ready = true, exiting = false, backButtonProps, headerClassName, headerContentClassName, contentOffsetClassName = "pt-16", bodyClassName, mainPanelClassName, sidePanel, sidePanelOpen = false, sidePanelClassName, sidePanelAriaLabel, sidePanelResizable = false, sidePanelDefaultSize = TASK_MODE_SIDE_PANEL_DEFAULT_SIZE, sidePanelMinSize = TASK_MODE_SIDE_PANEL_MIN_SIZE, sidePanelMaxSize = TASK_MODE_SIDE_PANEL_MAX_SIZE, splitDividerClassName, ...props }, ref) => {
19
21
  const hasSidePanel = sidePanel !== undefined && sidePanel !== null;
22
+ const hasHeaderContent = header !== undefined && header !== null;
20
23
  const splitOpen = hasSidePanel && sidePanelOpen;
21
24
  const splitContainerRef = React.useRef(null);
25
+ const [entered, setEntered] = React.useState(false);
22
26
  const resolvedSidePanelMinSize = Math.min(sidePanelMinSize, sidePanelMaxSize);
23
27
  const resolvedSidePanelMaxSize = Math.max(sidePanelMinSize, sidePanelMaxSize);
24
28
  const [sidePanelSize, setSidePanelSize] = React.useState(() => clampSidePanelSize(sidePanelDefaultSize, resolvedSidePanelMinSize, resolvedSidePanelMaxSize));
25
29
  const sidePanelSizeRef = React.useRef(sidePanelSize);
30
+ const taskModeVisible = entered && ready && !exiting;
31
+ const taskModeState = exiting
32
+ ? "exiting"
33
+ : taskModeVisible
34
+ ? "entered"
35
+ : "entering";
36
+ React.useEffect(() => {
37
+ setEntered(true);
38
+ }, []);
26
39
  React.useEffect(() => {
27
40
  sidePanelSizeRef.current = sidePanelSize;
28
41
  }, [sidePanelSize]);
@@ -96,9 +109,10 @@ export const TaskModeShell = React.forwardRef(({ className, header, children, lo
96
109
  sidePanelResizable,
97
110
  splitOpen,
98
111
  ]);
99
- return (_jsxs("div", { ref: ref, className: cn("relative min-h-screen w-full bg-background text-foreground", className), ...props, children: [showLoadingBar || loadingProgress > 0 ? (_jsx(Progress, { value: loadingProgress, size: "xs", shape: "square", label: loadingLabel, className: cn("absolute left-0 top-0 z-50 transition-opacity duration-300 ease-out", showLoadingBar ? "opacity-100" : "pointer-events-none opacity-0"), trackClassName: "bg-transparent", indicatorClassName: "bg-emerald-600" })) : null, _jsxs("div", { className: cn("transition-all duration-[250ms] ease-out", ready && !exiting
100
- ? "translate-y-0 opacity-100"
101
- : "translate-y-4 opacity-0"), children: [_jsx("header", { className: cn("fixed left-0 right-0 top-0 z-30 border-b bg-background", headerClassName), children: header }), _jsx("main", { className: cn("w-full", contentOffsetClassName), children: hasSidePanel ? (_jsxs("div", { ref: splitContainerRef, className: cn("relative grid min-h-0 w-full overflow-hidden transition-[grid-template-columns] duration-300 ease-in-out", bodyClassName), style: splitStyle, children: [_jsx("section", { className: cn("min-h-0 min-w-0", mainPanelClassName), children: children }), _jsx("aside", { "aria-hidden": !splitOpen, "aria-label": sidePanelAriaLabel, className: cn("min-h-0 min-w-0 bg-[hsl(var(--layout-right-panel-background))] transition-[border-color,box-shadow,opacity] duration-300 ease-in-out", rightPanelEdgeClassName, sidePanelClassName, splitOpen
112
+ const renderedHeader = backButtonProps ? (_jsxs("div", { className: cn("flex h-14 items-center justify-between gap-4 px-6", headerContentClassName), children: [_jsx(BackButton, { ...backButtonProps, className: cn("text-base", backButtonProps.className) }), hasHeaderContent ? (_jsx("div", { className: "flex min-w-0 items-center gap-2", children: header })) : null] })) : (header);
113
+ return (_jsxs("div", { ref: ref, className: cn("relative min-h-screen w-full bg-background text-foreground", className), ...props, children: [showLoadingBar || loadingProgress > 0 ? (_jsx(Progress, { value: loadingProgress, size: "xs", shape: "square", label: loadingLabel, className: cn("absolute left-0 top-0 z-50 transition-opacity duration-300 ease-out", showLoadingBar ? "opacity-100" : "pointer-events-none opacity-0"), trackClassName: "bg-transparent", indicatorClassName: "bg-emerald-600" })) : null, _jsxs("div", { "data-slot": "task-mode-shell-content", "data-state": taskModeState, className: cn("motion-safe:transition-[opacity,transform] motion-safe:[transition-duration:250ms] motion-safe:ease-out motion-reduce:transition-none", taskModeVisible
114
+ ? "opacity-100 motion-safe:translate-y-0 motion-safe:scale-100"
115
+ : "pointer-events-none opacity-0 motion-safe:translate-y-3 motion-safe:scale-[0.99]"), children: [_jsx("header", { className: cn("fixed left-0 right-0 top-0 z-30 border-b bg-background", headerClassName), children: renderedHeader }), _jsx("main", { className: cn("w-full", contentOffsetClassName), children: hasSidePanel ? (_jsxs("div", { ref: splitContainerRef, className: cn("relative grid min-h-0 w-full overflow-hidden transition-[grid-template-columns] duration-300 ease-in-out", bodyClassName), style: splitStyle, children: [_jsx("section", { className: cn("min-h-0 min-w-0", mainPanelClassName), children: children }), _jsx("aside", { "aria-hidden": !splitOpen, "aria-label": sidePanelAriaLabel, className: cn("min-h-0 min-w-0 bg-[hsl(var(--layout-right-panel-background))] transition-[border-color,box-shadow,opacity] duration-300 ease-in-out", rightPanelEdgeClassName, sidePanelClassName, splitOpen
102
116
  ? ["opacity-100", rightPanelEdgeOpenClassName]
103
117
  : [
104
118
  "pointer-events-none overflow-hidden opacity-0",
package/dist/toaster.js CHANGED
@@ -1,27 +1,48 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { CheckCircle2, CircleAlert, CircleX, Info } from "lucide-react";
3
4
  import { Toaster as SonnerToaster, toast } from "sonner";
4
5
  import { buttonVariants } from "./button";
5
6
  import { XIcon } from "./animated-icons/x";
6
7
  import { cn } from "./lib/utils";
7
8
  import { typographyVariants } from "./typography";
9
+ const toastActionClassName = "!ml-0 !mr-0 !mt-0 !self-start !justify-self-start";
10
+ const toastLayoutStyle = {
11
+ display: "grid",
12
+ gridTemplateColumns: "auto var(--space-2) auto minmax(0, 1fr)",
13
+ alignItems: "start",
14
+ columnGap: "0",
15
+ rowGap: "var(--space-2)",
16
+ "--toast-button-margin-start": "0px",
17
+ "--toast-button-margin-end": "0px",
18
+ "--toast-icon-margin-start": "0px",
19
+ "--toast-icon-margin-end": "0px",
20
+ "--toast-svg-margin-start": "0px",
21
+ "--toast-svg-margin-end": "0px",
22
+ };
8
23
  function Toaster({ position = "top-right", visibleToasts = 3, closeButton = true, duration = 4000, }) {
9
24
  return (_jsx(SonnerToaster, { position: position, visibleToasts: visibleToasts, closeButton: closeButton, duration: duration, icons: {
25
+ success: _jsx(CheckCircle2, { "aria-hidden": "true", className: "h-4 w-4" }),
26
+ info: _jsx(Info, { "aria-hidden": "true", className: "h-4 w-4" }),
27
+ warning: _jsx(CircleAlert, { "aria-hidden": "true", className: "h-4 w-4" }),
28
+ error: _jsx(CircleX, { "aria-hidden": "true", className: "h-4 w-4" }),
10
29
  close: _jsx(XIcon, { size: 14 }),
11
30
  }, toastOptions: {
31
+ unstyled: true,
32
+ style: toastLayoutStyle,
12
33
  classNames: {
13
- toast: "group relative border border-border bg-background text-foreground shadow-md rounded-overlay p-3 pr-9 flex items-start gap-2",
14
- content: "min-w-0 flex-1 gap-1",
15
- title: cn(typographyVariants({ variant: "bodySm" }), "font-medium"),
16
- description: cn(typographyVariants({ variant: "bodySm" }), "text-muted-foreground"),
17
- icon: "mt-0.5 text-muted-foreground",
18
- actionButton: cn(buttonVariants({ variant: "link" }), "!h-auto !min-h-0 !self-start !p-0 !text-sm !font-medium !text-primary !no-underline hover:!text-primary/80 hover:!no-underline"),
19
- cancelButton: cn(buttonVariants({ variant: "link" }), "!h-auto !min-h-0 !self-start !bg-transparent !p-0 !text-sm !font-medium !text-muted-foreground !no-underline hover:!bg-transparent hover:!text-foreground hover:!no-underline"),
20
- closeButton: "!absolute !left-auto !right-2 !top-2 !h-6 !w-6 !translate-x-0 !translate-y-0 !rounded-control !border-0 !bg-transparent !text-muted-foreground hover:!bg-accent hover:!text-foreground",
21
- success: "border-[hsl(var(--status-success-foreground))]/20 [&_[data-icon]]:text-[hsl(var(--status-success-foreground))]",
22
- error: "border-destructive/20 [&_[data-icon]]:text-[hsl(var(--status-destructive-foreground))]",
23
- warning: "border-[hsl(var(--status-warning-foreground))]/20 [&_[data-icon]]:text-[hsl(var(--status-warning-foreground))]",
24
- info: "border-[hsl(var(--status-info-foreground))]/20 [&_[data-icon]]:text-[hsl(var(--status-info-foreground))]",
34
+ toast: "group relative !grid w-[var(--width)] !grid-cols-[auto_var(--space-2)_auto_minmax(0,1fr)] !items-start !gap-x-0 !gap-y-2 !rounded-overlay !border !border-border/80 !bg-popover !p-3 !pr-10 !text-popover-foreground !shadow-overlay-strong [&_[data-action]]:col-start-1 [&_[data-button]]:row-start-2 [&_[data-button]]:!ml-0 [&_[data-button]]:!mr-0 [&_[data-cancel]]:col-start-1 [&_[data-content]]:col-span-full [&_[data-content]]:col-start-1 [&_[data-content]]:row-start-1 [&_[data-icon]]:col-start-1 [&_[data-icon]]:row-start-1 [&:has([data-cancel])_[data-action]]:col-start-3 [&:has([data-icon])]:!grid-cols-[auto_var(--space-3)_auto_var(--space-2)_auto_minmax(0,1fr)] [&:has([data-icon])_[data-action]]:col-start-3 [&:has([data-icon])_[data-cancel]]:col-start-3 [&:has([data-icon])_[data-content]]:col-start-3 [&:has([data-icon]):has([data-cancel])_[data-action]]:col-start-5",
35
+ content: "flex min-w-0 flex-col gap-1",
36
+ title: cn(typographyVariants({ variant: "bodySm" }), "!font-medium !text-popover-foreground"),
37
+ description: cn(typographyVariants({ variant: "bodySm" }), "!text-muted-foreground"),
38
+ icon: "!mx-0 flex !h-8 !w-8 shrink-0 !items-center !justify-center self-start rounded-control !border !border-border !bg-muted !text-muted-foreground [&>svg]:!m-0 [&>svg]:!h-4 [&>svg]:!w-4",
39
+ actionButton: cn(buttonVariants({ variant: "default", size: "compact" }), toastActionClassName, "!bg-primary !text-primary-foreground hover:!bg-primary/90"),
40
+ cancelButton: cn(buttonVariants({ variant: "outline", size: "compact" }), toastActionClassName, "!border-[hsl(var(--button-outline-border))] !bg-[hsl(var(--button-outline-background))] !text-foreground hover:!bg-[hsl(var(--button-outline-hover-background))]"),
41
+ closeButton: "!absolute !left-auto !right-2.5 !top-2.5 !flex !h-6 !w-6 !translate-x-0 !translate-y-0 !items-center !justify-center !rounded-control !border-0 !bg-transparent !text-muted-foreground hover:!bg-accent hover:!text-foreground",
42
+ success: "border-[hsl(var(--status-success-foreground))]/20 [&_[data-icon]]:!border-status-success-foreground/20 [&_[data-icon]]:!bg-status-success [&_[data-icon]]:!text-status-success-foreground",
43
+ error: "border-destructive/20 [&_[data-icon]]:!border-status-destructive-foreground/20 [&_[data-icon]]:!bg-status-destructive [&_[data-icon]]:!text-status-destructive-foreground",
44
+ warning: "border-[hsl(var(--status-warning-foreground))]/20 [&_[data-icon]]:!border-status-warning-foreground/20 [&_[data-icon]]:!bg-status-warning [&_[data-icon]]:!text-status-warning-foreground",
45
+ info: "border-[hsl(var(--status-info-foreground))]/20 [&_[data-icon]]:!border-status-info-foreground/20 [&_[data-icon]]:!bg-status-info [&_[data-icon]]:!text-status-info-foreground",
25
46
  },
26
47
  } }));
27
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neasg/design-system",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",