@neasg/design-system 0.4.33 → 0.4.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/activity-card.d.ts +29 -0
  2. package/dist/activity-card.js +65 -0
  3. package/dist/autocomplete.d.ts +84 -0
  4. package/dist/autocomplete.js +267 -0
  5. package/dist/avatar.d.ts +3 -1
  6. package/dist/avatar.js +27 -10
  7. package/dist/badge.js +1 -1
  8. package/dist/button.d.ts +1 -1
  9. package/dist/button.js +1 -0
  10. package/dist/calendar.js +1 -1
  11. package/dist/card.d.ts +1 -1
  12. package/dist/card.js +1 -1
  13. package/dist/compound-search-input.d.ts +8 -0
  14. package/dist/compound-search-input.js +9 -0
  15. package/dist/date-input.js +3 -3
  16. package/dist/dialog.d.ts +3 -1
  17. package/dist/dialog.js +6 -2
  18. package/dist/draggable-tabs.js +9 -8
  19. package/dist/index.d.ts +12 -4
  20. package/dist/index.js +6 -2
  21. package/dist/label-value-grid.js +6 -4
  22. package/dist/layout.d.ts +39 -6
  23. package/dist/layout.js +118 -42
  24. package/dist/lib/utils.js +1 -0
  25. package/dist/message-item.d.ts +23 -3
  26. package/dist/message-item.js +115 -16
  27. package/dist/notification.d.ts +14 -3
  28. package/dist/notification.js +32 -7
  29. package/dist/popover-menu.d.ts +4 -2
  30. package/dist/popover-menu.js +65 -5
  31. package/dist/popover.d.ts +5 -2
  32. package/dist/popover.js +3 -3
  33. package/dist/rich-text-editor.d.ts +2 -1
  34. package/dist/rich-text-editor.js +28 -3
  35. package/dist/routing-timeline.d.ts +7 -2
  36. package/dist/routing-timeline.js +67 -29
  37. package/dist/search-input.d.ts +4 -0
  38. package/dist/search-input.js +2 -2
  39. package/dist/select-primitive.js +1 -1
  40. package/dist/select.d.ts +3 -1
  41. package/dist/select.js +9 -4
  42. package/dist/status-dot.d.ts +13 -0
  43. package/dist/status-dot.js +19 -0
  44. package/dist/styles.css +0 -19
  45. package/dist/table-column-filter.js +7 -5
  46. package/dist/table.d.ts +9 -1
  47. package/dist/table.js +30 -12
  48. package/dist/tabs.d.ts +1 -1
  49. package/dist/tabs.js +6 -4
  50. package/dist/textarea.d.ts +1 -0
  51. package/dist/textarea.js +3 -2
  52. package/dist/theme-switcher.d.ts +3 -1
  53. package/dist/theme-switcher.js +8 -2
  54. package/package.json +5 -1
@@ -25,6 +25,14 @@ export interface NotificationItemData {
25
25
  id: string;
26
26
  title: React.ReactNode;
27
27
  description?: React.ReactNode;
28
+ /** Person or system responsible for the event. */
29
+ actor?: React.ReactNode;
30
+ /** Optional status or category displayed in an activity-card header. */
31
+ badge?: React.ReactNode;
32
+ /** Per-item activity-card density. Falls back to the list density. */
33
+ activityDensity?: "compact" | "default";
34
+ /** `plain` matches activity events whose semantic glyph needs no tone tile. */
35
+ iconAppearance?: "plain" | "toned";
28
36
  meta?: React.ReactNode;
29
37
  timestamp?: NotificationTimestamp;
30
38
  dateTime?: string;
@@ -38,18 +46,21 @@ export interface NotificationItemData {
38
46
  export interface NotificationItemProps extends Omit<React.HTMLAttributes<HTMLElement>, "children" | "onSelect" | "title"> {
39
47
  notification: NotificationItemData;
40
48
  density?: "compact" | "default";
49
+ variant?: "row" | "card";
41
50
  labelLines?: 1 | 2;
42
51
  descriptionLines?: 1 | 2 | 3;
43
52
  unreadLabel?: React.ReactNode;
53
+ actorLabel?: React.ReactNode;
44
54
  onSelect?: (notification: NotificationItemData) => void;
45
55
  }
46
56
  export interface NotificationListProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
47
57
  notifications: NotificationItemData[];
48
58
  density?: "compact" | "default";
49
- variant?: "bordered" | "plain";
59
+ variant?: "bordered" | "plain" | "cards";
50
60
  labelLines?: 1 | 2;
51
61
  descriptionLines?: 1 | 2 | 3;
52
62
  unreadLabel?: React.ReactNode;
63
+ actorLabel?: React.ReactNode;
53
64
  emptyState?: React.ReactNode;
54
65
  itemClassName?: string;
55
66
  onNotificationClick?: (notification: NotificationItemData) => void;
@@ -83,7 +94,7 @@ export interface NotificationTriggerProps extends Omit<ButtonProps, "children">
83
94
  }
84
95
  declare function formatNotificationTimestamp(timestamp: NotificationTimestamp, { now, locale }?: FormatNotificationTimestampOptions): string;
85
96
  declare const NotificationTrigger: React.ForwardRefExoticComponent<NotificationTriggerProps & React.RefAttributes<HTMLButtonElement>>;
86
- declare function NotificationItem({ notification, density, labelLines, descriptionLines, unreadLabel, className, onClick, onSelect, ...props }: NotificationItemProps): import("react/jsx-runtime").JSX.Element;
87
- declare function NotificationList({ notifications, density, variant, labelLines, descriptionLines, unreadLabel, emptyState, itemClassName, className, onNotificationClick, ...props }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
97
+ declare function NotificationItem({ notification, density, variant, labelLines, descriptionLines, unreadLabel, actorLabel, className, onClick, onSelect, ...props }: NotificationItemProps): import("react/jsx-runtime").JSX.Element;
98
+ declare function NotificationList({ notifications, density, variant, labelLines, descriptionLines, unreadLabel, actorLabel, emptyState, itemClassName, className, onNotificationClick, ...props }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
88
99
  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;
89
100
  export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, };
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Bell, ClipboardCheck, FileText, MessageSquare, UserCheck, } from "lucide-react";
5
+ import { ActivityCard, ActivityCardActor, ActivityCardContent, } from "./activity-card";
5
6
  import { Button } from "./button";
6
7
  import { CountBadge } from "./count-badge";
7
8
  import { EmptyState } from "./empty-state";
@@ -118,8 +119,8 @@ function getNotificationTone(notification) {
118
119
  var _a, _b;
119
120
  return ((_a = notification.tone) !== null && _a !== void 0 ? _a : notificationKindTones[(_b = notification.kind) !== null && _b !== void 0 ? _b : "system"]);
120
121
  }
121
- function NotificationItem({ notification, density = "default", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", className, onClick, onSelect, ...props }) {
122
- var _a, _b, _c;
122
+ function NotificationItem({ notification, density = "default", variant = "row", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", actorLabel = "By", className, onClick, onSelect, ...props }) {
123
+ var _a, _b, _c, _d;
123
124
  const Icon = getNotificationIcon(notification);
124
125
  const tone = getNotificationTone(notification);
125
126
  const isUnread = Boolean(notification.unread);
@@ -141,7 +142,29 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
141
142
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(notification);
142
143
  }
143
144
  };
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) => {
145
+ if (variant === "card") {
146
+ const activityDensity = (_d = notification.activityDensity) !== null && _d !== void 0 ? _d : density;
147
+ const hasCardContent = notification.description !== undefined ||
148
+ notification.actor !== undefined ||
149
+ hasActions;
150
+ const activityElement = notification.href && !hasActions
151
+ ? "a"
152
+ : isInteractive
153
+ ? "button"
154
+ : "article";
155
+ const unreadIndicator = isUnread && unreadLabel !== null ? (_jsx("span", { "data-slot": "notification-unread-indicator", className: "block size-2.5 rounded-full bg-primary ring-2 ring-background", children: _jsx("span", { className: "sr-only", children: unreadLabel }) })) : null;
156
+ const cardTrailing = notification.badge || unreadIndicator ? (_jsxs("span", { className: "flex items-center gap-[var(--space-2)]", children: [notification.badge, unreadIndicator] })) : null;
157
+ return (_jsx(ActivityCard, { as: activityElement, "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, density: activityDensity, icon: _jsx(Icon, { className: activityDensity === "compact" ? "size-3.5" : "size-[24px]" }), iconTone: notification.iconAppearance === "plain" ? undefined : tone, title: notification.title, titleLines: labelLines, meta: meta, trailing: cardTrailing, href: notification.href, className: cn("group relative w-full text-left outline-none transition-colors", isUnread && "bg-primary/[0.045]", hasHoverSurface && "hover:bg-accent/70", isInteractive &&
158
+ "cursor-pointer focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", className), onClick: isInteractive ? handleClick : undefined, ...props, children: hasCardContent ? (_jsxs(ActivityCardContent, { density: activityDensity, children: [notification.description !== undefined ? (_jsx(Typography, { as: "div", variant: "bodySm", className: cn("text-left", isUnread ? "text-foreground/75" : "text-muted-foreground", descriptionLineClasses[descriptionLines]), children: notification.description })) : null, notification.actor !== undefined ? (_jsx(ActivityCardActor, { actor: notification.actor, label: actorLabel })) : null, hasActions ? (_jsx("div", { className: "flex flex-wrap gap-2", children: actions.map((action, index) => {
159
+ var _a, _b;
160
+ 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) => {
161
+ var _a;
162
+ event.stopPropagation();
163
+ (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action, notification, event);
164
+ }, children: action.label }, (_b = action.id) !== null && _b !== void 0 ? _b : index));
165
+ }) })) : null] })) : null }));
166
+ }
167
+ 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, notification.actor !== undefined ? (_jsxs(Typography, { as: "span", variant: "bodySm", className: "mt-2 block text-left font-semibold text-foreground", children: [actorLabel, " ", notification.actor] })) : null, hasActions ? (_jsx("span", { className: "mt-4 flex flex-wrap gap-2", children: actions.map((action, index) => {
145
168
  var _a, _b;
146
169
  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) => {
147
170
  var _a;
@@ -149,7 +172,7 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
149
172
  (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action, notification, event);
150
173
  }, children: action.label }, (_b = action.id) !== null && _b !== void 0 ? _b : index));
151
174
  }) })) : null] })] }));
152
- const itemClassName = cn("group relative flex w-full min-w-0 items-start gap-3 overflow-hidden border-b border-border/70 bg-card text-left text-card-foreground outline-none transition-colors last:border-b-0", density === "compact" ? "px-3 py-3" : "px-4 py-4", isUnread && "bg-primary/[0.045]", hasHoverSurface && "hover:bg-accent/70", isInteractive &&
175
+ const itemClassName = cn("group relative flex w-full min-w-0 items-start gap-3 overflow-hidden bg-card text-left text-card-foreground outline-none transition-colors", "border-b border-border/70 last:border-b-0", density === "compact" ? "px-3 py-3" : "px-4 py-4", isUnread && "bg-primary/[0.045]", hasHoverSurface && "hover:bg-accent/70", isInteractive &&
153
176
  "cursor-pointer focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", className);
154
177
  if (notification.href && !hasActions) {
155
178
  return (_jsx("a", { href: notification.href, "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, className: itemClassName, onClick: handleClick, ...props, children: content }));
@@ -159,11 +182,13 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
159
182
  }
160
183
  return (_jsx("div", { "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, className: itemClassName, ...props, children: content }));
161
184
  }
162
- function NotificationList({ notifications, density = "default", variant = "bordered", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", emptyState, itemClassName, className, onNotificationClick, ...props }) {
185
+ function NotificationList({ notifications, density = "default", variant = "bordered", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", actorLabel = "By", emptyState, itemClassName, className, onNotificationClick, ...props }) {
163
186
  const hasNotifications = notifications.length > 0;
164
187
  const resolvedEmptyState = emptyState !== null && emptyState !== void 0 ? emptyState : (_jsx(EmptyState, { message: "No notifications", description: "New updates will appear here.", icon: Bell }));
165
- return (_jsx("div", { "data-slot": "notification-list", className: cn("min-w-0 overflow-hidden bg-card text-card-foreground", !hasNotifications && "flex min-h-full flex-col", variant === "bordered" && "rounded-surface border border-border/70", className), ...props, children: hasNotifications
166
- ? notifications.map((notification) => (_jsx(NotificationItem, { notification: notification, density: density, labelLines: labelLines, descriptionLines: descriptionLines, unreadLabel: unreadLabel, className: itemClassName, onSelect: onNotificationClick }, notification.id)))
188
+ return (_jsx("div", { "data-slot": "notification-list", className: cn("min-w-0 text-card-foreground", !hasNotifications && "flex min-h-full flex-col", variant === "cards"
189
+ ? "space-y-[var(--space-4)] bg-transparent"
190
+ : "overflow-hidden bg-card", variant === "bordered" && "rounded-surface border border-border/70", className), ...props, children: hasNotifications
191
+ ? notifications.map((notification) => (_jsx(NotificationItem, { notification: notification, density: density, variant: variant === "cards" ? "card" : "row", labelLines: labelLines, descriptionLines: descriptionLines, unreadLabel: unreadLabel, actorLabel: actorLabel, className: itemClassName, onSelect: onNotificationClick }, notification.id)))
167
192
  : (_jsx("div", { className: "flex min-h-0 flex-1 flex-col justify-center", children: resolvedEmptyState })) }));
168
193
  }
169
194
  function NotificationPopover({ trigger, notifications, title = "Notifications", filter, defaultFilter = "all", onFilterChange, showFilterTabs = true, height = "h-[36rem]", minHeight = "min-h-[36rem]", maxHeight = "max-h-[36rem]", emptyState, listClassName, itemClassName, unreadLabel = "Unread", markAllAsReadLabel = "Mark all as read", onNotificationClick, onMarkAllAsRead, contentClassName, align = "end", ...popoverProps }) {
@@ -17,8 +17,10 @@ export interface PopoverMenuSectionProps extends React.HTMLAttributes<HTMLDivEle
17
17
  declare function PopoverMenuSection({ className, children, ...props }: PopoverMenuSectionProps): import("react/jsx-runtime").JSX.Element;
18
18
  export interface PopoverMenuItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
19
19
  active?: boolean;
20
+ /** Communicates destructive actions without changing menu-item sizing. */
21
+ tone?: "default" | "destructive";
20
22
  }
21
- declare function PopoverMenuItem({ className, children, active, type, ...props }: PopoverMenuItemProps): import("react/jsx-runtime").JSX.Element;
23
+ declare function PopoverMenuItem({ className, children, active, tone, type, ...props }: PopoverMenuItemProps): import("react/jsx-runtime").JSX.Element;
22
24
  export interface PopoverMenuCheckboxItemProps extends Omit<CheckboxProps, "className" | "label"> {
23
25
  description?: React.ReactNode;
24
26
  className?: string;
@@ -52,7 +54,7 @@ declare const PopoverMenuSearch: React.ForwardRefExoticComponent<Omit<PopoverMen
52
54
  export interface PopoverMenuScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
53
55
  maxHeight?: string;
54
56
  }
55
- declare function PopoverMenuScrollArea({ className, children, maxHeight, ...props }: PopoverMenuScrollAreaProps): import("react/jsx-runtime").JSX.Element;
57
+ declare function PopoverMenuScrollArea({ className, children, maxHeight, onScroll, ...props }: PopoverMenuScrollAreaProps): import("react/jsx-runtime").JSX.Element;
56
58
  export interface PopoverMenuViewProps extends React.HTMLAttributes<HTMLDivElement> {
57
59
  direction?: "forward" | "backward" | "none";
58
60
  viewKey?: React.Key;
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Check } from "lucide-react";
4
+ import { Check, ChevronUp } from "lucide-react";
5
+ import { ChevronDownIcon } from "./animated-icons/chevron-down";
5
6
  import { Button } from "./button";
6
7
  import { Checkbox } from "./checkbox";
7
8
  import { Input } from "./input";
@@ -20,9 +21,10 @@ function PopoverMenuHeader({ className, children, ...props }) {
20
21
  function PopoverMenuSection({ className, children, ...props }) {
21
22
  return (_jsx("div", { className: cn("space-y-0", className), ...props, children: children }));
22
23
  }
23
- function PopoverMenuItem({ className, children, active, type = "button", ...props }) {
24
+ function PopoverMenuItem({ className, children, active, tone = "default", type = "button", ...props }) {
24
25
  return (_jsxs(Button, { type: type, variant: "ghost", "data-slot": "popover-menu-item", className: cn("h-auto min-h-8 w-full justify-start gap-2 px-2 py-2 text-left font-normal whitespace-normal aria-[selected=true]:bg-highlight aria-[selected=true]:text-highlight-foreground aria-[selected=true]:[--muted-foreground:var(--instructional-foreground)] aria-[selected=true]:hover:bg-highlight/80 aria-[selected=true]:hover:text-highlight-foreground aria-[selected=true]:focus-visible:bg-highlight/80 aria-[selected=true]:focus-visible:text-highlight-foreground", popoverMenuItemFocusClassName, active &&
25
- "bg-highlight text-highlight-foreground [--muted-foreground:var(--instructional-foreground)] hover:bg-highlight/80 hover:text-highlight-foreground focus-visible:bg-highlight/80 focus-visible:text-highlight-foreground", className), ...props, children: [children, active ? _jsx(Check, { "aria-hidden": "true", className: "ml-auto h-4 w-4 shrink-0" }) : null] }));
26
+ "bg-highlight text-highlight-foreground [--muted-foreground:var(--instructional-foreground)] hover:bg-highlight/80 hover:text-highlight-foreground focus-visible:bg-highlight/80 focus-visible:text-highlight-foreground", tone === "destructive" &&
27
+ "text-destructive hover:bg-destructive/10 hover:text-destructive focus-visible:bg-destructive/10 focus-visible:text-destructive", className), ...props, children: [children, active ? _jsx(Check, { "aria-hidden": "true", className: "ml-auto h-4 w-4 shrink-0" }) : null] }));
26
28
  }
27
29
  function PopoverMenuCheckboxItem({ className, children, checked = false, description, disabled, onChange, readOnly, ...props }) {
28
30
  return (_jsxs("label", { className: cn("flex h-auto min-h-8 w-full cursor-pointer items-start gap-1.5 rounded-sm px-2 py-2 text-left text-popover-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-within:bg-accent focus-within:text-accent-foreground", popoverMenuItemFocusWithinClassName, checked &&
@@ -52,8 +54,66 @@ const PopoverMenuSearch = React.forwardRef(({ "aria-label": ariaLabel, className
52
54
  return (_jsx("div", { className: cn("relative -mx-1 -mt-1 mb-1 px-1 py-1 after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-border after:content-['']", containerClassName), children: _jsx(Input, { ref: ref, "data-popover-menu-search": "true", label: (_b = (_a = label !== null && label !== void 0 ? label : ariaLabel) !== null && _a !== void 0 ? _a : placeholder) !== null && _b !== void 0 ? _b : "Search", hideLabel: true, className: "gap-0", inputClassName: className, "aria-label": ariaLabel, placeholder: placeholder, ...props }) }));
53
55
  });
54
56
  PopoverMenuSearch.displayName = "PopoverMenuSearch";
55
- function PopoverMenuScrollArea({ className, children, maxHeight = "max-h-64", ...props }) {
56
- return (_jsx("div", { className: cn(maxHeight, "space-y-1 overflow-y-auto overflow-x-hidden", className), ...props, children: children }));
57
+ function PopoverMenuScrollButton({ direction, onAutoScroll, }) {
58
+ const autoScrollTimerRef = React.useRef(null);
59
+ const clearAutoScrollTimer = React.useCallback(() => {
60
+ if (autoScrollTimerRef.current === null)
61
+ return;
62
+ window.clearInterval(autoScrollTimerRef.current);
63
+ autoScrollTimerRef.current = null;
64
+ }, []);
65
+ const startAutoScroll = React.useCallback(() => {
66
+ if (autoScrollTimerRef.current !== null)
67
+ return;
68
+ autoScrollTimerRef.current = window.setInterval(() => onAutoScroll(direction), 50);
69
+ }, [direction, onAutoScroll]);
70
+ React.useEffect(() => clearAutoScrollTimer, [clearAutoScrollTimer]);
71
+ return (_jsx("div", { "aria-hidden": "true", "data-slot": `popover-menu-scroll-${direction}-button`, className: "-mx-1 flex shrink-0 cursor-pointer items-center justify-center py-1", onPointerDown: startAutoScroll, onPointerMove: startAutoScroll, onPointerLeave: clearAutoScrollTimer, children: direction === "up" ? (_jsx(ChevronUp, { className: "h-4 w-4" })) : (_jsx(ChevronDownIcon, { size: 16 })) }));
72
+ }
73
+ function PopoverMenuScrollArea({ className, children, maxHeight = "max-h-64", onScroll, ...props }) {
74
+ const scrollAreaRef = React.useRef(null);
75
+ const [canScrollUp, setCanScrollUp] = React.useState(false);
76
+ const [canScrollDown, setCanScrollDown] = React.useState(false);
77
+ const updateScrollControls = React.useCallback(() => {
78
+ const scrollArea = scrollAreaRef.current;
79
+ if (!scrollArea)
80
+ return;
81
+ const maxScroll = scrollArea.scrollHeight - scrollArea.clientHeight;
82
+ setCanScrollUp(scrollArea.scrollTop > 0);
83
+ setCanScrollDown(maxScroll > 0 && Math.ceil(scrollArea.scrollTop) < maxScroll);
84
+ }, []);
85
+ useIsomorphicLayoutEffect(() => {
86
+ const scrollArea = scrollAreaRef.current;
87
+ if (!scrollArea)
88
+ return;
89
+ updateScrollControls();
90
+ const animationFrame = window.requestAnimationFrame(updateScrollControls);
91
+ const resizeObserver = typeof ResizeObserver === "undefined"
92
+ ? null
93
+ : new ResizeObserver(updateScrollControls);
94
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(scrollArea);
95
+ const mutationObserver = new MutationObserver(updateScrollControls);
96
+ mutationObserver.observe(scrollArea, { childList: true, subtree: true });
97
+ return () => {
98
+ window.cancelAnimationFrame(animationFrame);
99
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
100
+ mutationObserver.disconnect();
101
+ };
102
+ }, [children, updateScrollControls]);
103
+ const handleScroll = React.useCallback((event) => {
104
+ onScroll === null || onScroll === void 0 ? void 0 : onScroll(event);
105
+ updateScrollControls();
106
+ }, [onScroll, updateScrollControls]);
107
+ const handleAutoScroll = React.useCallback((direction) => {
108
+ const scrollArea = scrollAreaRef.current;
109
+ if (!scrollArea)
110
+ return;
111
+ const firstItem = scrollArea.querySelector('[data-slot="popover-menu-item"]');
112
+ const scrollStep = (firstItem === null || firstItem === void 0 ? void 0 : firstItem.offsetHeight) || 32;
113
+ scrollArea.scrollTop += direction === "up" ? -scrollStep : scrollStep;
114
+ updateScrollControls();
115
+ }, [updateScrollControls]);
116
+ return (_jsxs("div", { "data-slot": "popover-menu-scroll-viewport", className: cn(maxHeight, "flex min-h-0 flex-col overflow-hidden"), children: [canScrollUp ? (_jsx(PopoverMenuScrollButton, { direction: "up", onAutoScroll: handleAutoScroll })) : null, _jsx("div", { ref: scrollAreaRef, className: cn("-mx-1 min-h-0 flex-1 space-y-1 overflow-y-auto overflow-x-hidden px-1", className), onScroll: handleScroll, ...props, children: children }), canScrollDown ? (_jsx(PopoverMenuScrollButton, { direction: "down", onAutoScroll: handleAutoScroll })) : null] }));
57
117
  }
58
118
  function PopoverMenuView({ children, className, direction = "forward", viewKey = "default", ...props }) {
59
119
  const [activeViewKey, setActiveViewKey] = React.useState(viewKey);
package/dist/popover.d.ts CHANGED
@@ -5,12 +5,15 @@ import { type TooltipProps } from "./tooltip";
5
5
  import { Typography } from "./typography";
6
6
  type PopoverRootProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>;
7
7
  type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>;
8
+ type PopoverAnchorProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Anchor>;
8
9
  export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "modal" | "onOpenChange" | "open">, Omit<PopoverContentProps, "children" | "className" | "title"> {
9
10
  trigger: React.ReactElement;
11
+ /** Positions the popover from a specific element while retaining `trigger` for interaction and focus. */
12
+ virtualAnchorRef?: PopoverAnchorProps["virtualRef"];
10
13
  children: React.ReactNode;
11
14
  /** Visible popover heading. Also labels the popover for assistive technology. */
12
15
  title?: React.ReactNode;
13
- surface?: "panel" | "menu";
16
+ surface?: "panel" | "menu" | "message";
14
17
  /** Controls whether Tab stays within the popover or closes it and continues in the page Tab order. */
15
18
  tabBehavior?: PopoverTabBehavior;
16
19
  contentClassName?: string;
@@ -19,7 +22,7 @@ export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "mo
19
22
  triggerTooltipSideOffset?: TooltipProps["sideOffset"];
20
23
  triggerTooltipClassName?: TooltipProps["contentClassName"];
21
24
  }
22
- declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, title, surface, tabBehavior, contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align, sideOffset, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
25
+ declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, virtualAnchorRef, children, title, surface, tabBehavior, contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align, sideOffset, style, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
23
26
  export interface PopoverTitleProps extends Omit<React.ComponentPropsWithoutRef<typeof Typography>, "as" | "asChild" | "variant" | "color"> {
24
27
  }
25
28
  declare function PopoverTitle({ className, children, ...props }: PopoverTitleProps): import("react/jsx-runtime").JSX.Element;
package/dist/popover.js CHANGED
@@ -2,11 +2,11 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "./lib/utils";
5
- import { PopoverContent, PopoverRoot, PopoverTrigger, } from "./popover-primitive";
5
+ import { PopoverAnchor, PopoverContent, PopoverRoot, PopoverTrigger, } from "./popover-primitive";
6
6
  import { usePopoverTabExit, } from "./popover-focus";
7
7
  import { Tooltip } from "./tooltip";
8
8
  import { Typography } from "./typography";
9
- function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, title, surface = "panel", tabBehavior = "contain", contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align = "center", sideOffset = 4, ...contentProps }) {
9
+ function Popover({ defaultOpen, modal, onOpenChange, open, trigger, virtualAnchorRef, children, title, surface = "panel", tabBehavior = "contain", contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align = "center", sideOffset = 4, style, ...contentProps }) {
10
10
  const generatedTitleId = React.useId();
11
11
  const titleId = title ? `popover-title-${generatedTitleId}` : undefined;
12
12
  const [internalOpen, setInternalOpen] = React.useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
@@ -79,7 +79,7 @@ function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, ti
79
79
  contentOnCloseAutoFocus === null || contentOnCloseAutoFocus === void 0 ? void 0 : contentOnCloseAutoFocus(event);
80
80
  handleCloseAutoFocus(event);
81
81
  }, [contentOnCloseAutoFocus, handleCloseAutoFocus]);
82
- return (_jsxs(PopoverRoot, { modal: modal, onOpenChange: setOpen, open: currentOpen, children: [triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsxs(PopoverContent, { ref: contentRef, align: align, sideOffset: sideOffset, "aria-labelledby": titleId !== null && titleId !== void 0 ? titleId : contentProps["aria-labelledby"], className: cn(surface === "menu" && "overflow-hidden p-1", contentClassName), ...contentProps, onCloseAutoFocus: handleContentCloseAutoFocus, onKeyDown: handleContentKeyDown, onKeyDownCapture: handleContentKeyDownCapture, children: [title ? (_jsx(PopoverTitle, { id: titleId, className: cn(surface === "menu"
82
+ return (_jsxs(PopoverRoot, { modal: modal, onOpenChange: setOpen, open: currentOpen, children: [virtualAnchorRef ? (_jsx(PopoverAnchor, { virtualRef: virtualAnchorRef })) : null, triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsxs(PopoverContent, { ref: contentRef, align: align, sideOffset: sideOffset, "aria-labelledby": titleId !== null && titleId !== void 0 ? titleId : contentProps["aria-labelledby"], className: cn(surface === "menu" && "overflow-hidden p-1", surface === "message" && "overflow-hidden p-0", contentClassName), style: style, ...contentProps, onCloseAutoFocus: handleContentCloseAutoFocus, onKeyDown: handleContentKeyDown, onKeyDownCapture: handleContentKeyDownCapture, children: [title ? (_jsx(PopoverTitle, { id: titleId, className: cn(surface === "menu"
83
83
  ? "px-2 py-1.5"
84
84
  : "-mx-4 -mt-4 mb-4 border-b px-4 py-3"), children: title })) : null, children] })] }));
85
85
  }
@@ -25,6 +25,7 @@ export interface RichTextEditorSendAction {
25
25
  disabled?: boolean;
26
26
  }
27
27
  export interface RichTextEditorProps {
28
+ variant?: "default" | "message";
28
29
  placeholder?: string;
29
30
  editorAriaLabel?: string;
30
31
  header?: React.ReactNode;
@@ -53,4 +54,4 @@ export interface RichTextEditorProps {
53
54
  export declare function formatFileSize(bytes: number): string;
54
55
  export declare function sanitizeRichTextHtml(html: string): string;
55
56
  export declare function replaceTemplateVariables(content: string, context: TemplateContext): string;
56
- export declare function RichTextEditor({ placeholder, editorAriaLabel, header, isExpanded, onExpandToggle, onSend, className, maxFileSize, maxFileSizeLabel, acceptedFileTypes, acceptedFileTypesLabel, isSending, showTemplates, templates, templateContext, templateCategoryLabels, templateCategoryOrder, templateCreateHref, templateCreateLabel, onFileRejected, colorPalette, sendLabel, sendingLabel, sendActions, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare function RichTextEditor({ variant, placeholder, editorAriaLabel, header, isExpanded, onExpandToggle, onSend, className, maxFileSize, maxFileSizeLabel, acceptedFileTypes, acceptedFileTypesLabel, isSending, showTemplates, templates, templateContext, templateCategoryLabels, templateCategoryOrder, templateCreateHref, templateCreateLabel, onFileRejected, colorPalette, sendLabel, sendingLabel, sendActions, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -11,6 +11,7 @@ import { TextStyle } from "@tiptap/extension-text-style";
11
11
  import { Color } from "@tiptap/extension-color";
12
12
  import Highlight from "@tiptap/extension-highlight";
13
13
  import Underline from "@tiptap/extension-underline";
14
+ import { Baseline, Reply } from "lucide-react";
14
15
  import { BoldIcon, ChevronDownIcon, HighlighterIcon, ItalicIcon, LinkIcon, ListIcon, ListOrderedIcon, MaximizeIcon, PaletteIcon, PaperclipIcon, PlusIcon, SendIcon, StrikethroughIcon, UnderlineIcon, XIcon, } from "./animated-icons";
15
16
  import { Button } from "./button";
16
17
  import { DropdownButton } from "./dropdown-button";
@@ -19,6 +20,7 @@ import { inputControlPaddingClassName } from "./input-control";
19
20
  import { Popover } from "./popover";
20
21
  import { PopoverMenuItem, PopoverMenuSection } from "./popover-menu";
21
22
  import { Tooltip } from "./tooltip";
23
+ import { Typography } from "./typography";
22
24
  import { cn } from "./lib/utils";
23
25
  const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
24
26
  const DEFAULT_MAX_FILE_SIZE_LABEL = "10MB";
@@ -125,7 +127,8 @@ function ToolbarIconButton({ active, label, onClick, children, }) {
125
127
  function getTemplateCategory(template) {
126
128
  return template.category || DEFAULT_TEMPLATE_CATEGORY;
127
129
  }
128
- export function RichTextEditor({ placeholder = "Type message here...", editorAriaLabel = "Message body", header, isExpanded = false, onExpandToggle, onSend, className, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxFileSizeLabel = DEFAULT_MAX_FILE_SIZE_LABEL, acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES, acceptedFileTypesLabel = DEFAULT_ACCEPTED_FILE_TYPES_LABEL, isSending = false, showTemplates = true, templates = [], templateContext = {}, templateCategoryLabels = {}, templateCategoryOrder, templateCreateHref, templateCreateLabel = "Create new template", onFileRejected, colorPalette = DEFAULT_COLOR_PALETTE, sendLabel = "Send", sendingLabel = "Sending...", sendActions, }) {
130
+ export function RichTextEditor({ variant = "default", placeholder = "Type message here...", editorAriaLabel = "Message body", header, isExpanded = false, onExpandToggle, onSend, className, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxFileSizeLabel = DEFAULT_MAX_FILE_SIZE_LABEL, acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES, acceptedFileTypesLabel = DEFAULT_ACCEPTED_FILE_TYPES_LABEL, isSending = false, showTemplates = true, templates = [], templateContext = {}, templateCategoryLabels = {}, templateCategoryOrder, templateCreateHref, templateCreateLabel = "Create new template", onFileRejected, colorPalette = DEFAULT_COLOR_PALETTE, sendLabel = "Send", sendingLabel = "Sending...", sendActions, }) {
131
+ var _a;
129
132
  const [isEmpty, setIsEmpty] = React.useState(true);
130
133
  const [attachments, setAttachments] = React.useState([]);
131
134
  const [isDragging, setIsDragging] = React.useState(false);
@@ -133,6 +136,7 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
133
136
  const [linkUrl, setLinkUrl] = React.useState("");
134
137
  const [linkMenuPosition, setLinkMenuPosition] = React.useState(null);
135
138
  const [templatePopoverOpen, setTemplatePopoverOpen] = React.useState(false);
139
+ const [isFormattingOpen, setIsFormattingOpen] = React.useState(false);
136
140
  const fileInputRef = React.useRef(null);
137
141
  const dropZoneRef = React.useRef(null);
138
142
  const linkInputRef = React.useRef(null);
@@ -192,7 +196,9 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
192
196
  role: "textbox",
193
197
  "aria-multiline": "true",
194
198
  "aria-label": editorAriaLabel,
195
- class: cn("prose prose-sm max-w-none focus:outline-none", "min-h-[120px]", inputControlPaddingClassName),
199
+ class: cn("prose prose-sm max-w-none focus:outline-none", variant === "message"
200
+ ? "min-h-[48px] px-[10px] py-[8px] text-sm"
201
+ : cn("min-h-[120px]", inputControlPaddingClassName)),
196
202
  },
197
203
  },
198
204
  onUpdate: ({ editor }) => {
@@ -352,6 +358,25 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
352
358
  }, children: action.label }, action.id))) }) })) : (_jsxs(Button, { type: "button", className: "w-full", disabled: sendDisabled, onClick: () => {
353
359
  void handleSend();
354
360
  }, children: [_jsx(SendIcon, { size: 16 }), isSending ? sendingLabel : sendLabel] }));
361
+ if (variant === "message") {
362
+ return (_jsxs("div", { "data-slot": "message-composer", ref: dropZoneRef, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("box-border flex min-h-[164px] flex-col overflow-hidden rounded-[8px] border border-input bg-background transition-all focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50", !isExpanded && attachments.length === 0 && "h-[164px]", isDragging && "border-2 border-primary bg-primary/5", className), children: [header ? (_jsxs("div", { "data-slot": "message-composer-header", className: "flex min-h-[48px] items-center justify-between px-[10px] py-[8px] text-muted-foreground", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-[8px]", children: [_jsx(Reply, { "aria-hidden": "true", className: "size-[16px] shrink-0" }), _jsx(Typography, { as: "span", variant: "caption", className: "min-w-0 truncate font-medium text-muted-foreground", "data-slot": "message-composer-header-label", children: header })] }), onExpandToggle ? (_jsx(Tooltip, { content: isExpanded ? "Collapse editor" : "Expand editor", side: "top", children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[28px] w-[28px] shrink-0", onClick: onExpandToggle, "aria-label": isExpanded ? "Collapse editor" : "Expand editor", children: _jsx(MaximizeIcon, { size: 16 }) }) })) : null] })) : null, _jsx("div", { "data-slot": "message-composer-editor", className: cn("min-h-[64px]", isExpanded && "min-h-[200px]"), children: _jsx(EditorContent, { editor: editor, className: cn("min-h-[64px] text-sm", isExpanded && "min-h-[200px]", "[&_.ProseMirror]:min-h-[64px] [&_.ProseMirror]:outline-none", isExpanded && "[&_.ProseMirror]:min-h-[200px]", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]") }) }), attachments.length > 0 ? (_jsx("div", { className: "space-y-[8px] px-[10px] pb-[8px]", children: attachments.map((attachment, index) => (_jsxs("div", { className: "flex w-full items-center gap-[8px] rounded-md border bg-muted px-[12px] py-[8px] text-sm", children: [_jsx(PaperclipIcon, { size: 14, className: "shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: attachment.name }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatFileSize(attachment.size) }), _jsx("button", { type: "button", onClick: () => removeAttachment(index), className: "shrink-0 text-muted-foreground hover:text-foreground", "aria-label": `Remove ${attachment.name}`, children: _jsx(XIcon, { size: 14 }) })] }, `${attachment.name}-${attachment.size}-${index}`))) })) : null, _jsx("input", { ref: fileInputRef, type: "file", accept: acceptedFileTypes, multiple: true, onChange: handleFileSelect, className: "hidden" }), linkPopoverOpen &&
363
+ linkMenuPosition &&
364
+ typeof document !== "undefined" &&
365
+ createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-[99]", onClick: () => {
366
+ setLinkPopoverOpen(false);
367
+ setLinkUrl("");
368
+ setLinkMenuPosition(null);
369
+ editor === null || editor === void 0 ? void 0 : editor.chain().focus().run();
370
+ } }), _jsx("div", { ref: linkMenuRef, className: "fixed z-[100] w-80 animate-in fade-in-0 zoom-in-95 rounded-md border bg-popover p-3 shadow-md", style: {
371
+ top: linkMenuPosition.top,
372
+ left: linkMenuPosition.left,
373
+ }, children: _jsxs("div", { className: "flex items-end gap-2", children: [_jsx(Input, { ref: linkInputRef, label: "URL", id: "message-link-url", type: "url", placeholder: "https://example.com", value: linkUrl, onChange: (event) => setLinkUrl(event.target.value), onKeyDown: handleLinkKeyDown, inputClassName: "h-8 text-sm", autoFocus: true }), _jsx(Button, { type: "button", className: "h-8 px-3", onClick: handleLinkSubmit, disabled: !linkUrl.trim(), children: "Add" })] }) })] }), document.body), isFormattingOpen ? (_jsxs("div", { className: "flex flex-wrap items-center gap-[4px] border-t border-border px-[10px] py-[8px]", children: [_jsx(ToolbarIconButton, { label: "Bold", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bold"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBold().run(), children: _jsx(BoldIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Italic", active: editor === null || editor === void 0 ? void 0 : editor.isActive("italic"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleItalic().run(), children: _jsx(ItalicIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Underline", active: editor === null || editor === void 0 ? void 0 : editor.isActive("underline"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleUnderline().run(), children: _jsx(UnderlineIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Bullet list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bulletList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBulletList().run(), children: _jsx(ListIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Ordered list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("orderedList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrderedIcon, { size: 14 }) })] })) : null, _jsxs("div", { "data-slot": "message-composer-toolbar", className: "flex min-h-[52px] items-center justify-between px-[10px] py-[8px]", children: [_jsxs("div", { className: "flex items-center gap-[8px]", children: [_jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[28px] w-[28px]", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 16 }) }) }), _jsxs("div", { className: "inline-flex h-[32px] overflow-hidden rounded-control border border-input bg-background", children: [_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[32px] w-[32px] rounded-none border-r border-input", onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBold().run(), "aria-pressed": (_a = editor === null || editor === void 0 ? void 0 : editor.isActive("bold")) !== null && _a !== void 0 ? _a : false, "aria-label": "Bold", children: _jsx(BoldIcon, { size: 16 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[32px] w-[32px] rounded-none border-r border-input", onClick: openLinkMenu, "aria-label": "Add link", children: _jsx(LinkIcon, { size: 16 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[32px] w-[32px] rounded-none", onClick: () => setIsFormattingOpen((open) => !open), "aria-expanded": isFormattingOpen, "aria-label": "Formatting options", children: _jsx(Baseline, { "aria-hidden": "true", className: "size-[16px]" }) })] })] }), _jsxs("div", { className: "flex items-center gap-[8px]", children: [showTemplates && (templates.length > 0 || templateCreateHref) ? (_jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "outline", size: "sm", children: ["Templates", _jsx(ChevronDownIcon, { size: 16 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
374
+ var _a;
375
+ return (_jsxs("div", { children: [_jsx("div", { className: "sticky top-0 bg-muted px-3 py-2 text-sm font-semibold text-foreground", children: (_a = templateCategoryLabels[category]) !== null && _a !== void 0 ? _a : category }), templates.map((template) => (_jsxs("button", { type: "button", className: "w-full border-b border-border/50 px-3 py-2 text-left text-sm transition-colors last:border-0 hover:bg-muted", onClick: () => applyTemplate(template), children: [_jsx("div", { className: "truncate font-medium", children: template.name }), template.description ? (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: template.description })) : null] }, template.id)))] }, category));
376
+ }) }), templateCreateHref ? (_jsx("div", { className: "border-t p-2", children: _jsxs("a", { href: templateCreateHref, target: "_blank", rel: "noopener noreferrer", className: "flex w-full items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm text-interactive transition-colors hover:bg-muted", children: [_jsx(PlusIcon, { size: 16 }), templateCreateLabel] }) })) : null] })) : null, _jsx(Button, { type: "button", size: "sm", disabled: isSending || !onSend, "aria-label": isSending ? sendingLabel : sendLabel, onClick: () => {
377
+ void handleSend();
378
+ }, children: isSending ? sendingLabel : sendLabel })] })] })] }));
379
+ }
355
380
  return (_jsxs("div", { ref: dropZoneRef, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("overflow-hidden rounded-control border bg-background transition-all focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50", isExpanded && "shadow-md", isDragging && "border-2 border-primary bg-primary/5", className), children: [header && (_jsxs("div", { className: cn("flex items-center justify-between border-b bg-muted dark:bg-muted/30", inputControlPaddingClassName), children: [_jsx("div", { className: "flex-1", children: header }), _jsx("div", { className: "flex items-center gap-2", children: onExpandToggle && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-6 w-6", onClick: onExpandToggle, "aria-label": "Expand editor", children: _jsx(MaximizeIcon, { size: 14 }) })) })] })), _jsxs("div", { className: cn("bg-background transition-all", isExpanded ? "min-h-[200px]" : "min-h-[120px]"), children: [_jsx(EditorContent, { editor: editor, className: cn("text-sm", "[&_.ProseMirror]:min-h-[120px] [&_.ProseMirror]:outline-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0", "[&_.ProseMirror_ul]:my-2 [&_.ProseMirror_ul]:list-disc [&_.ProseMirror_ul]:pl-6", "[&_.ProseMirror_ol]:my-2 [&_.ProseMirror_ol]:list-decimal [&_.ProseMirror_ol]:pl-6", "[&_.ProseMirror_li]:my-1", "[&_.ProseMirror_ul_ul]:list-[circle] [&_.ProseMirror_ul_ul_ul]:list-[square]") }), linkPopoverOpen &&
356
381
  linkMenuPosition &&
357
382
  typeof document !== "undefined" &&
@@ -387,7 +412,7 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
387
412
  else {
388
413
  editor === null || editor === void 0 ? void 0 : editor.chain().focus().setHighlight({ color: color.value }).run();
389
414
  }
390
- }, children: color.value === "" && (_jsx("span", { className: "text-xs text-muted-foreground", children: "A" })) }, `bg-${color.name}`))) }) }), _jsx(ToolbarIconButton, { label: "Bullet list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bulletList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBulletList().run(), children: _jsx(ListIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Ordered list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("orderedList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrderedIcon, { size: 14 }) }), _jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 14 }) }) }), _jsx(ToolbarIconButton, { label: "Add link", active: editor === null || editor === void 0 ? void 0 : editor.isActive("link"), onClick: openLinkMenu, children: _jsx(LinkIcon, { size: 14 }) }), showTemplates && templates.length > 0 && (_jsx(_Fragment, { children: _jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "ghost", className: "h-8 gap-1 text-muted-foreground", children: ["Template", _jsx(ChevronDownIcon, { size: 14 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
415
+ }, children: color.value === "" && (_jsx("span", { className: "text-xs text-muted-foreground", children: "A" })) }, `bg-${color.name}`))) }) }), _jsx(ToolbarIconButton, { label: "Bullet list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bulletList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBulletList().run(), children: _jsx(ListIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Ordered list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("orderedList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrderedIcon, { size: 14 }) }), _jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 14 }) }) }), _jsx(ToolbarIconButton, { label: "Add link", active: editor === null || editor === void 0 ? void 0 : editor.isActive("link"), onClick: openLinkMenu, children: _jsx(LinkIcon, { size: 14 }) }), showTemplates && (templates.length > 0 || templateCreateHref) && (_jsx(_Fragment, { children: _jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "ghost", className: "h-8 gap-1 text-muted-foreground", children: ["Template", _jsx(ChevronDownIcon, { size: 14 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
391
416
  var _a;
392
417
  return (_jsxs("div", { children: [_jsx("div", { className: "sticky top-0 bg-muted px-3 py-2 text-sm font-semibold text-foreground", children: (_a = templateCategoryLabels[category]) !== null && _a !== void 0 ? _a : category }), templates.map((template) => (_jsxs("button", { type: "button", className: "w-full border-b border-border/50 px-3 py-2 text-left text-sm transition-colors last:border-0 hover:bg-muted", onClick: () => applyTemplate(template), children: [_jsx("div", { className: "truncate font-medium", children: template.name }), template.description && (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: template.description }))] }, template.id)))] }, category));
393
418
  }) }), templateCreateHref && (_jsx("div", { className: "border-t p-2", children: _jsxs("a", { href: templateCreateHref, target: "_blank", rel: "noopener noreferrer", className: "flex w-full items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm text-interactive transition-colors hover:bg-muted", children: [_jsx(PlusIcon, { size: 16 }), templateCreateLabel] }) }))] }) }))] }) }), _jsx("div", { className: "bg-muted px-3 pb-2 pt-1 dark:bg-background", children: sendAction })] }));
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- export type RoutingTimelineItemKind = "assigned" | "reassigned" | "returned" | "routed" | "system";
2
+ export type RoutingTimelineItemKind = "assigned" | "reassigned" | "returned" | "routed" | "message-received" | "message-sent" | "system";
3
3
  export interface RoutingTimelineItem {
4
4
  id: string;
5
5
  kind?: RoutingTimelineItemKind;
@@ -8,15 +8,20 @@ export interface RoutingTimelineItem {
8
8
  actor?: React.ReactNode;
9
9
  remark?: React.ReactNode;
10
10
  description?: React.ReactNode;
11
+ detailLabel?: React.ReactNode;
11
12
  from?: React.ReactNode;
12
13
  to?: React.ReactNode;
13
14
  fromLabel?: React.ReactNode;
14
15
  toLabel?: React.ReactNode;
15
16
  badgeLabel?: React.ReactNode;
17
+ badge?: React.ReactNode;
18
+ icon?: React.ReactNode;
16
19
  }
17
20
  export interface RoutingTimelineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
18
21
  items: RoutingTimelineItem[];
19
22
  emptyMessage?: React.ReactNode;
23
+ /** Reduces typography, icon, padding, and event spacing for constrained panels. */
24
+ density?: "default" | "compact";
20
25
  }
21
- declare function RoutingTimeline({ items, emptyMessage, className, ...props }: RoutingTimelineProps): import("react/jsx-runtime").JSX.Element;
26
+ declare function RoutingTimeline({ items, emptyMessage, density, className, ...props }: RoutingTimelineProps): import("react/jsx-runtime").JSX.Element;
22
27
  export { RoutingTimeline };