@neasg/design-system 0.4.35 → 0.4.113
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/README.md +9 -0
- package/dist/activity-card.d.ts +31 -0
- package/dist/activity-card.js +65 -0
- package/dist/alert.d.ts +1 -1
- package/dist/autocomplete.d.ts +84 -0
- package/dist/autocomplete.js +267 -0
- package/dist/avatar.d.ts +3 -1
- package/dist/avatar.js +27 -10
- package/dist/badge.d.ts +1 -1
- package/dist/badge.js +1 -1
- package/dist/button.d.ts +2 -2
- package/dist/button.js +3 -1
- package/dist/calendar.js +1 -1
- package/dist/card.d.ts +1 -1
- package/dist/card.js +1 -1
- package/dist/changelog-action-panel.d.ts +17 -0
- package/dist/changelog-action-panel.js +14 -0
- package/dist/checklist.d.ts +63 -0
- package/dist/checklist.js +30 -0
- package/dist/compound-search-input.d.ts +10 -0
- package/dist/compound-search-input.js +19 -0
- package/dist/date-input.js +3 -3
- package/dist/dialog.d.ts +4 -1
- package/dist/dialog.js +7 -2
- package/dist/draggable-tabs.js +9 -8
- package/dist/dropdown-button.js +1 -0
- package/dist/editable-table.js +2 -2
- package/dist/field-counter.d.ts +7 -0
- package/dist/field-counter.js +5 -0
- package/dist/index.d.ts +22 -4
- package/dist/index.js +12 -2
- package/dist/input.d.ts +2 -0
- package/dist/input.js +11 -3
- package/dist/label-value-grid.js +6 -4
- package/dist/layout.d.ts +39 -6
- package/dist/layout.js +118 -42
- package/dist/lib/utils.js +1 -0
- package/dist/message-action-panel.d.ts +12 -0
- package/dist/message-action-panel.js +11 -0
- package/dist/message-item.d.ts +23 -3
- package/dist/message-item.js +115 -16
- package/dist/notification.d.ts +14 -3
- package/dist/notification.js +36 -7
- package/dist/page-section.d.ts +2 -1
- package/dist/page-section.js +2 -2
- package/dist/popover-menu.d.ts +4 -2
- package/dist/popover-menu.js +65 -5
- package/dist/popover.d.ts +10 -2
- package/dist/popover.js +7 -3
- package/dist/rich-text-editor.d.ts +2 -1
- package/dist/rich-text-editor.js +29 -6
- package/dist/routing-action-panel.d.ts +38 -0
- package/dist/routing-action-panel.js +19 -0
- package/dist/routing-timeline.d.ts +7 -2
- package/dist/routing-timeline.js +67 -29
- package/dist/search-input.d.ts +6 -0
- package/dist/search-input.js +6 -4
- package/dist/select-primitive.js +1 -1
- package/dist/select.d.ts +3 -1
- package/dist/select.js +9 -4
- package/dist/status-dot.d.ts +13 -0
- package/dist/status-dot.js +19 -0
- package/dist/styles.css +0 -19
- package/dist/table-add-row.d.ts +8 -0
- package/dist/table-add-row.js +7 -0
- package/dist/table-column-filter.js +7 -5
- package/dist/table.d.ts +9 -1
- package/dist/table.js +25 -11
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +6 -4
- package/dist/tailwind-preset.js +10 -0
- package/dist/textarea.d.ts +5 -0
- package/dist/textarea.js +18 -3
- package/dist/theme-switcher.d.ts +3 -1
- package/dist/theme-switcher.js +8 -2
- package/package.json +1 -1
package/dist/message-item.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { FileText } from "lucide-react";
|
|
4
|
+
import { FileText, Laptop, Paperclip } from "lucide-react";
|
|
5
5
|
import { DownloadIcon } from "./animated-icons";
|
|
6
|
-
import { AvatarProfile } from "./avatar";
|
|
6
|
+
import { Avatar, AvatarProfile } from "./avatar";
|
|
7
7
|
import { Badge } from "./badge";
|
|
8
8
|
import { formatNotificationTimestamp } from "./notification";
|
|
9
9
|
import { Separator } from "./separator";
|
|
@@ -18,6 +18,49 @@ export const formatMessageDate = (date) => {
|
|
|
18
18
|
const pad = (value) => String(value).padStart(2, "0");
|
|
19
19
|
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(twelveHour)}:${pad(d.getMinutes())} ${period}`;
|
|
20
20
|
};
|
|
21
|
+
export const formatMessageTime = (date) => new Intl.DateTimeFormat("en-SG", {
|
|
22
|
+
hour: "numeric",
|
|
23
|
+
minute: "2-digit",
|
|
24
|
+
hour12: true,
|
|
25
|
+
})
|
|
26
|
+
.format(new Date(date))
|
|
27
|
+
.toLowerCase();
|
|
28
|
+
export const formatMessageCardTimestamp = (date, now = new Date()) => {
|
|
29
|
+
const value = new Date(date);
|
|
30
|
+
const nowValue = new Date(now);
|
|
31
|
+
const timeLabel = new Intl.DateTimeFormat("en-SG", {
|
|
32
|
+
hour: "numeric",
|
|
33
|
+
minute: "2-digit",
|
|
34
|
+
hour12: true,
|
|
35
|
+
}).format(value).toUpperCase();
|
|
36
|
+
const isToday = value.getFullYear() === nowValue.getFullYear() &&
|
|
37
|
+
value.getMonth() === nowValue.getMonth() &&
|
|
38
|
+
value.getDate() === nowValue.getDate();
|
|
39
|
+
if (isToday)
|
|
40
|
+
return `Today ${timeLabel}`;
|
|
41
|
+
const elapsedDays = Math.floor((new Date(nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()).getTime() -
|
|
42
|
+
new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime()) /
|
|
43
|
+
(24 * 60 * 60 * 1000));
|
|
44
|
+
if (elapsedDays > 0 && elapsedDays < 7) {
|
|
45
|
+
return `${elapsedDays} ${elapsedDays === 1 ? "day" : "days"} ago ${timeLabel}`;
|
|
46
|
+
}
|
|
47
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
48
|
+
const dateLabel = `${pad(value.getDate())}/${pad(value.getMonth() + 1)}/${value.getFullYear()}`;
|
|
49
|
+
return `${dateLabel} ${timeLabel}`;
|
|
50
|
+
};
|
|
51
|
+
export const formatMessageDateHeading = (date, now = new Date()) => {
|
|
52
|
+
const value = new Date(date);
|
|
53
|
+
const isToday = value.getFullYear() === now.getFullYear() &&
|
|
54
|
+
value.getMonth() === now.getMonth() &&
|
|
55
|
+
value.getDate() === now.getDate();
|
|
56
|
+
if (isToday)
|
|
57
|
+
return "Today";
|
|
58
|
+
return new Intl.DateTimeFormat("en-SG", {
|
|
59
|
+
day: "numeric",
|
|
60
|
+
month: "long",
|
|
61
|
+
year: "numeric",
|
|
62
|
+
}).format(value);
|
|
63
|
+
};
|
|
21
64
|
function getInitials(name) {
|
|
22
65
|
if (!name)
|
|
23
66
|
return "";
|
|
@@ -39,9 +82,18 @@ function toHtmlContent(content) {
|
|
|
39
82
|
.replace(/\n/g, "<br>");
|
|
40
83
|
}
|
|
41
84
|
export function MessageNewDivider({ children = "New Message", className, }) {
|
|
42
|
-
return (_jsxs("div", { className: cn("mb-3 flex items-center gap-3 py-1", className), children: [_jsx(Separator, { "aria-hidden": "true", className: "flex-1" }), _jsx(Typography, { as: "span", variant: "caption", className: "shrink-0 px-2 font-medium text-muted-foreground", children: children }), _jsx(Separator, { "aria-hidden": "true", className: "flex-1" })] }));
|
|
85
|
+
return (_jsxs("div", { "data-slot": "message-new-divider", className: cn("mb-3 flex items-center gap-3 py-1", className), children: [_jsx(Separator, { "aria-hidden": "true", className: "flex-1" }), _jsx(Typography, { as: "span", variant: "caption", className: "shrink-0 px-2 font-medium text-muted-foreground", children: children }), _jsx(Separator, { "aria-hidden": "true", className: "flex-1" })] }));
|
|
43
86
|
}
|
|
44
|
-
export function MessageAttachmentRow({ attachment, downloadUrl, downloadLabel = `Download ${attachment.name}`, className, ...props }) {
|
|
87
|
+
export function MessageAttachmentRow({ attachment, downloadUrl, downloadLabel = `Download ${attachment.name}`, variant = "default", className, ...props }) {
|
|
88
|
+
if (variant === "compact") {
|
|
89
|
+
const compactContent = (_jsxs(_Fragment, { children: [_jsx(Paperclip, { "aria-hidden": "true", className: "h-4 w-4 shrink-0" }), _jsx(Typography, { as: "span", variant: "bodySm", className: "min-w-0 truncate font-normal text-interactive", children: attachment.name }), attachment.size ? (_jsx(Typography, { as: "span", variant: "bodySm", className: "shrink-0 text-muted-foreground", children: attachment.size })) : null] }));
|
|
90
|
+
const compactClassName = cn("grid min-h-[44px] min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-[8px] rounded-[8px] border border-border bg-card px-[12px] py-[11px] text-foreground no-underline", downloadUrl &&
|
|
91
|
+
"cursor-pointer transition-colors hover:border-primary/50 hover:bg-primary/5 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className);
|
|
92
|
+
if (downloadUrl) {
|
|
93
|
+
return (_jsx("a", { className: compactClassName, href: downloadUrl, target: "_blank", rel: "noopener noreferrer", download: attachment.name, "aria-label": downloadLabel, title: downloadLabel, ...props, children: compactContent }));
|
|
94
|
+
}
|
|
95
|
+
return (_jsx("div", { className: compactClassName, "aria-disabled": "true", ...props, children: compactContent }));
|
|
96
|
+
}
|
|
45
97
|
const renderContent = (isInteractive) => (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0 pr-1", children: [_jsx(Typography, { as: "p", variant: "bodySm", className: "line-clamp-2 font-medium", children: attachment.name }), attachment.size ? (_jsx(Typography, { as: "p", variant: "caption", children: attachment.size })) : null] }), _jsx("span", { "aria-hidden": "true", className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-control border border-border bg-background text-muted-foreground transition-colors", isInteractive
|
|
46
98
|
? "group-hover:border-primary/40 group-hover:bg-highlight group-hover:text-highlight-foreground group-focus-visible:border-primary/40 group-focus-visible:bg-highlight group-focus-visible:text-highlight-foreground"
|
|
47
99
|
: "opacity-60"), children: _jsx(DownloadIcon, { size: 14 }) })] }));
|
|
@@ -50,7 +102,7 @@ export function MessageAttachmentRow({ attachment, downloadUrl, downloadLabel =
|
|
|
50
102
|
}
|
|
51
103
|
return (_jsx("div", { className: cn("grid min-w-0 grid-cols-[auto_minmax(0,1fr)_2.75rem] items-center gap-3 rounded-lg border border-border bg-card px-3 py-2 shadow-sm", className), "aria-disabled": "true", ...props, children: renderContent(false) }));
|
|
52
104
|
}
|
|
53
|
-
export function MessageItem({ message, caseId, showNewBadge = false, newMessageLabel = "New Message", onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, className, contentClassName, }) {
|
|
105
|
+
export function MessageItem({ message, caseId, showNewBadge = false, newMessageLabel = "New Message", newIndicatorPresentation, onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, variant = "default", density = "default", className, contentClassName, }) {
|
|
54
106
|
var _a;
|
|
55
107
|
const messageRef = React.useRef(null);
|
|
56
108
|
const [hasBeenViewed, setHasBeenViewed] = React.useState(message.isRead);
|
|
@@ -59,7 +111,10 @@ export function MessageItem({ message, caseId, showNewBadge = false, newMessageL
|
|
|
59
111
|
const isOfficer = message.senderRole === "OFFICER";
|
|
60
112
|
const isApplicant = message.senderRole === "APPLICANT";
|
|
61
113
|
const isUnreadApplicant = isApplicant && !message.isRead;
|
|
62
|
-
const
|
|
114
|
+
const showUnreadIndicator = showNewBadge && isUnreadApplicant;
|
|
115
|
+
const resolvedNewIndicatorPresentation = newIndicatorPresentation !== null && newIndicatorPresentation !== void 0 ? newIndicatorPresentation : (variant === "card" ? "badge" : "divider");
|
|
116
|
+
const showUnreadBadge = showUnreadIndicator && resolvedNewIndicatorPresentation === "badge";
|
|
117
|
+
const showUnreadDivider = showUnreadIndicator && resolvedNewIndicatorPresentation === "divider";
|
|
63
118
|
React.useEffect(() => {
|
|
64
119
|
if (message.isRead ||
|
|
65
120
|
message.senderRole !== "APPLICANT" ||
|
|
@@ -125,7 +180,7 @@ export function MessageItem({ message, caseId, showNewBadge = false, newMessageL
|
|
|
125
180
|
}
|
|
126
181
|
const intervalId = window.setInterval(() => setTimestampNow(new Date()), MESSAGE_TIMESTAMP_REFRESH_MS);
|
|
127
182
|
return () => window.clearInterval(intervalId);
|
|
128
|
-
}, [formatTimestamp]);
|
|
183
|
+
}, [formatTimestamp, variant]);
|
|
129
184
|
const senderName = isOfficer
|
|
130
185
|
? message.officerName || "Officer"
|
|
131
186
|
: isApplicant
|
|
@@ -137,23 +192,67 @@ export function MessageItem({ message, caseId, showNewBadge = false, newMessageL
|
|
|
137
192
|
? message.applicantName || message.applicantEmail || "Applicant"
|
|
138
193
|
: "System";
|
|
139
194
|
const officerRole = isOfficer ? message.officerRole : undefined;
|
|
140
|
-
const senderProfileRole = isOfficer
|
|
195
|
+
const senderProfileRole = isOfficer
|
|
196
|
+
? officerRole
|
|
197
|
+
: variant === "card"
|
|
198
|
+
? isApplicant
|
|
199
|
+
? "Applicant"
|
|
200
|
+
: "System (Auto-generated)"
|
|
201
|
+
: undefined;
|
|
141
202
|
const hasSenderProfileRole = Boolean(senderProfileRole);
|
|
142
203
|
const attachments = (message.attachments || []);
|
|
143
204
|
const messageDate = (_a = message.sentAt) !== null && _a !== void 0 ? _a : message.createdAt;
|
|
144
205
|
const downloadCaseId = caseId !== null && caseId !== void 0 ? caseId : message.caseId;
|
|
145
206
|
const contentHtml = toHtmlContent(message.content);
|
|
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-interactive [&_a]:underline [&_a:hover]:text-interactive", "[&_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);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
207
|
+
const richContentClassName = cn("break-words", variant === "default" && "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-interactive [&_a]:underline [&_a:hover]:text-interactive", "[&_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);
|
|
208
|
+
const cardSenderName = message.senderRole === "SYSTEM"
|
|
209
|
+
? "System (Auto-generated)"
|
|
210
|
+
: senderName;
|
|
211
|
+
const cardSenderRole = isOfficer
|
|
212
|
+
? officerRole
|
|
213
|
+
: isApplicant
|
|
214
|
+
? "Applicant"
|
|
215
|
+
: message.systemLabel || "Automated message";
|
|
216
|
+
const sender = variant === "card" ? (_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-[8px]", children: [isOfficer ? (_jsx(Avatar, { src: message.avatarUrl, alt: cardSenderName, fallback: getInitials(initialsName), size: "sm", className: "shrink-0" })) : message.senderRole === "SYSTEM" ? (_jsx("span", { className: "flex size-[24px] shrink-0 items-center justify-center", children: _jsx(Laptop, { "aria-hidden": "true", className: "size-[24px]" }) })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-[4px]", children: [_jsx(Typography, { as: "h3", variant: "label", className: "min-w-0", children: cardSenderName }), message.senderBadgeLabel ? (_jsx(Badge, { variant: "muted", className: "h-5 rounded-full px-2 py-0 [&_span]:normal-case", children: message.senderBadgeLabel })) : null, showUnreadBadge ? (_jsx(Badge, { variant: "success", className: "h-5 rounded-full px-2 py-0 [&_span]:normal-case", children: newMessageLabel })) : null] }), cardSenderRole ? (_jsx(Typography, { as: "p", variant: "caption", className: "truncate text-muted-foreground", "data-slot": "message-author-role", children: cardSenderRole })) : null] })] })) : (_jsx(AvatarProfile, { fallback: getInitials(initialsName), name: senderName, nameAs: "h3", nameTrailing: variant === "default" && 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]" }));
|
|
217
|
+
const header = variant === "card" ? (_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-[8px]", children: [sender, _jsx("time", { className: "shrink-0 whitespace-nowrap", dateTime: messageDate.toISOString(), children: _jsx(Typography, { as: "span", variant: "caption", className: "text-muted-foreground", children: formatTimestamp
|
|
218
|
+
? formatTimestamp(messageDate, message)
|
|
219
|
+
: formatMessageCardTimestamp(messageDate, timestampNow) }) })] })) : (_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-x-3 gap-y-1", children: [sender, _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
|
|
220
|
+
? formatTimestamp(messageDate, message)
|
|
221
|
+
: formatNotificationTimestamp(messageDate, {
|
|
222
|
+
now: timestampNow,
|
|
223
|
+
}) }) })] }));
|
|
224
|
+
const body = (_jsxs(_Fragment, { children: [_jsx(Typography, { as: "div", variant: "bodySm", color: "inherit", className: richContentClassName, dangerouslySetInnerHTML: { __html: contentHtml } }), attachments.length > 0 ? (_jsx("div", { className: variant === "card" ? "mt-[8px] space-y-[8px]" : "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), variant: variant === "card" ? "compact" : "default" }, `${attachment.name}-${idx}`))) })) : null] }));
|
|
225
|
+
return (_jsxs(_Fragment, { children: [showUnreadDivider && (_jsx(MessageNewDivider, { children: newMessageLabel })), _jsx("article", { "data-slot": variant === "card" ? "message-card" : "message-item", ref: messageRef, className: cn("min-w-0", variant === "card" &&
|
|
226
|
+
"overflow-hidden rounded-surface border border-[hsl(var(--card-border))] bg-card", className), children: variant === "card" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: density === "compact" ? "p-[12px]" : "p-[16px]", children: header }), _jsx("div", { className: cn("border-t border-border", density === "compact" ? "p-[12px]" : "p-[16px]"), children: body })] })) : (_jsxs(_Fragment, { children: [header, _jsx("div", { className: "ml-[calc(2.25rem+var(--space-3))] pt-2", children: body })] })) })] }));
|
|
152
227
|
}
|
|
153
|
-
export function MessageThread({ messages, caseId, showNewBadge = false, newMessageLabel = "New Message", onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, className, itemClassName, contentClassName, }) {
|
|
228
|
+
export function MessageThread({ messages, caseId, showNewBadge = false, newMessageLabel = "New Message", newIndicatorPresentation, onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, variant = "default", density = "default", groupByDate = false, showDateHeadings = true, formatDateHeading: formatDateHeadingProp, className, itemClassName, contentClassName, }) {
|
|
154
229
|
var _a;
|
|
230
|
+
const resolvedNewIndicatorPresentation = newIndicatorPresentation !== null && newIndicatorPresentation !== void 0 ? newIndicatorPresentation : (variant === "card" ? "badge" : "divider");
|
|
155
231
|
const firstUnreadMessageId = showNewBadge
|
|
156
232
|
? (_a = messages.find((message) => message.senderRole === "APPLICANT" && !message.isRead)) === null || _a === void 0 ? void 0 : _a.id
|
|
157
233
|
: undefined;
|
|
158
|
-
|
|
234
|
+
const renderMessage = (message) => (_jsx(MessageItem, { message: message, caseId: caseId, showNewBadge: resolvedNewIndicatorPresentation === "badge" && variant === "card"
|
|
235
|
+
? showNewBadge &&
|
|
236
|
+
message.senderRole === "APPLICANT" &&
|
|
237
|
+
!message.isRead
|
|
238
|
+
: message.id === firstUnreadMessageId, newMessageLabel: newMessageLabel, newIndicatorPresentation: resolvedNewIndicatorPresentation, onMarkAsRead: onMarkAsRead, onMarkedAsRead: onMarkedAsRead, getAttachmentDownloadUrl: getAttachmentDownloadUrl, formatTimestamp: formatTimestamp, variant: variant, density: density, className: itemClassName, contentClassName: contentClassName }, message.id));
|
|
239
|
+
if (groupByDate && showDateHeadings) {
|
|
240
|
+
const groups = messages.reduce((result, message) => {
|
|
241
|
+
var _a;
|
|
242
|
+
const date = new Date((_a = message.sentAt) !== null && _a !== void 0 ? _a : message.createdAt);
|
|
243
|
+
const key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
244
|
+
const group = result[result.length - 1];
|
|
245
|
+
if ((group === null || group === void 0 ? void 0 : group.key) === key) {
|
|
246
|
+
group.messages.push(message);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
result.push({ key, date, messages: [message] });
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}, []);
|
|
253
|
+
return (_jsx("div", { className: cn(variant === "card" ? "space-y-[24px]" : "space-y-5", className), children: groups.map((group) => (_jsxs("section", { className: variant === "card" ? "space-y-[16px]" : "space-y-3", children: [_jsx(Typography, { as: "h3", variant: "label", className: variant === "card" ? "font-semibold" : undefined, children: formatDateHeadingProp
|
|
254
|
+
? formatDateHeadingProp(group.date)
|
|
255
|
+
: formatMessageDateHeading(group.date) }), _jsx("div", { className: variant === "card" ? "space-y-[16px]" : "space-y-3", children: group.messages.map(renderMessage) })] }, group.key))) }));
|
|
256
|
+
}
|
|
257
|
+
return (_jsx("div", { className: cn(variant === "card" ? "space-y-[16px]" : "space-y-5", className), children: messages.map(renderMessage) }));
|
|
159
258
|
}
|
package/dist/notification.d.ts
CHANGED
|
@@ -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, };
|
package/dist/notification.js
CHANGED
|
@@ -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,33 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
|
|
|
141
142
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(notification);
|
|
142
143
|
}
|
|
143
144
|
};
|
|
144
|
-
|
|
145
|
+
if (variant === "card") {
|
|
146
|
+
const activityDensity = (_d = notification.activityDensity) !== null && _d !== void 0 ? _d : density;
|
|
147
|
+
const hasDescription = notification.description !== undefined &&
|
|
148
|
+
notification.description !== null &&
|
|
149
|
+
notification.description !== "";
|
|
150
|
+
const actorBelongsInHeader = notification.actor !== undefined && !hasDescription && !hasActions;
|
|
151
|
+
const hasCardContent = hasDescription ||
|
|
152
|
+
(notification.actor !== undefined && !actorBelongsInHeader) ||
|
|
153
|
+
hasActions;
|
|
154
|
+
const activityElement = notification.href && !hasActions
|
|
155
|
+
? "a"
|
|
156
|
+
: isInteractive
|
|
157
|
+
? "button"
|
|
158
|
+
: "article";
|
|
159
|
+
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;
|
|
160
|
+
const cardTrailing = notification.badge || unreadIndicator ? (_jsxs("span", { className: "flex items-center gap-[var(--space-2)]", children: [notification.badge, unreadIndicator] })) : null;
|
|
161
|
+
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, headerContent: actorBelongsInHeader ? (_jsx(ActivityCardActor, { actor: notification.actor, label: actorLabel })) : undefined, 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 &&
|
|
162
|
+
"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: [hasDescription ? (_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 && !actorBelongsInHeader ? (_jsx(ActivityCardActor, { actor: notification.actor, label: actorLabel })) : null, hasActions ? (_jsx("div", { className: "flex flex-wrap gap-2", children: actions.map((action, index) => {
|
|
163
|
+
var _a, _b;
|
|
164
|
+
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) => {
|
|
165
|
+
var _a;
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
(_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action, notification, event);
|
|
168
|
+
}, children: action.label }, (_b = action.id) !== null && _b !== void 0 ? _b : index));
|
|
169
|
+
}) })) : null] })) : null }));
|
|
170
|
+
}
|
|
171
|
+
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
172
|
var _a, _b;
|
|
146
173
|
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
174
|
var _a;
|
|
@@ -149,7 +176,7 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
|
|
|
149
176
|
(_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action, notification, event);
|
|
150
177
|
}, children: action.label }, (_b = action.id) !== null && _b !== void 0 ? _b : index));
|
|
151
178
|
}) })) : null] })] }));
|
|
152
|
-
const itemClassName = cn("group relative flex w-full min-w-0 items-start gap-3 overflow-hidden
|
|
179
|
+
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
180
|
"cursor-pointer focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", className);
|
|
154
181
|
if (notification.href && !hasActions) {
|
|
155
182
|
return (_jsx("a", { href: notification.href, "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, className: itemClassName, onClick: handleClick, ...props, children: content }));
|
|
@@ -159,11 +186,13 @@ function NotificationItem({ notification, density = "default", labelLines = 2, d
|
|
|
159
186
|
}
|
|
160
187
|
return (_jsx("div", { "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, className: itemClassName, ...props, children: content }));
|
|
161
188
|
}
|
|
162
|
-
function NotificationList({ notifications, density = "default", variant = "bordered", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", emptyState, itemClassName, className, onNotificationClick, ...props }) {
|
|
189
|
+
function NotificationList({ notifications, density = "default", variant = "bordered", labelLines = 2, descriptionLines = 2, unreadLabel = "Unread", actorLabel = "By", emptyState, itemClassName, className, onNotificationClick, ...props }) {
|
|
163
190
|
const hasNotifications = notifications.length > 0;
|
|
164
191
|
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
|
|
166
|
-
?
|
|
192
|
+
return (_jsx("div", { "data-slot": "notification-list", className: cn("min-w-0 text-card-foreground", !hasNotifications && "flex min-h-full flex-col", variant === "cards"
|
|
193
|
+
? "space-y-[var(--space-4)] bg-transparent"
|
|
194
|
+
: "overflow-hidden bg-card", variant === "bordered" && "rounded-surface border border-border/70", className), ...props, children: hasNotifications
|
|
195
|
+
? 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
196
|
: (_jsx("div", { className: "flex min-h-0 flex-1 flex-col justify-center", children: resolvedEmptyState })) }));
|
|
168
197
|
}
|
|
169
198
|
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 }) {
|
package/dist/page-section.d.ts
CHANGED
|
@@ -8,8 +8,9 @@ export interface PageSectionProps extends Omit<React.HTMLAttributes<HTMLElement>
|
|
|
8
8
|
description?: React.ReactNode;
|
|
9
9
|
headerAction?: React.ReactNode;
|
|
10
10
|
titleAdornment?: React.ReactNode;
|
|
11
|
+
titleAdornmentSpacing?: "default" | "comfortable";
|
|
11
12
|
headerClassName?: string;
|
|
12
13
|
contentClassName?: string;
|
|
13
14
|
}
|
|
14
|
-
declare function PageSection({ title, description, headerAction, titleAdornment, className, headerClassName, contentClassName, children, ...props }: PageSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
declare function PageSection({ title, description, headerAction, titleAdornment, titleAdornmentSpacing, className, headerClassName, contentClassName, children, ...props }: PageSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
15
16
|
export { PageSection, PageSectionGroup };
|
package/dist/page-section.js
CHANGED
|
@@ -6,9 +6,9 @@ function PageSectionGroup({ variant = "divided", className, ...props }) {
|
|
|
6
6
|
return (_jsx("div", { className: cn(variant === "divided" && "divide-y divide-border", variant === "cards" &&
|
|
7
7
|
"space-y-4 [&>[data-slot=page-section]]:rounded-surface [&>[data-slot=page-section]]:border [&>[data-slot=page-section]]:border-border/70 [&>[data-slot=page-section]]:bg-card [&>[data-slot=page-section]]:p-5", className), ...props }));
|
|
8
8
|
}
|
|
9
|
-
function PageSection({ title, description, headerAction, titleAdornment, className, headerClassName, contentClassName, children, ...props }) {
|
|
9
|
+
function PageSection({ title, description, headerAction, titleAdornment, titleAdornmentSpacing = "default", className, headerClassName, contentClassName, children, ...props }) {
|
|
10
10
|
const renderedTitle = React.isValidElement(title) ? (title) : (_jsx(Typography, { as: "h3", variant: "subheading", children: title }));
|
|
11
11
|
const renderedDescription = description == null ? null : React.isValidElement(description) ? (description) : (_jsx(Typography, { as: "p", variant: "caption", children: description }));
|
|
12
|
-
return (_jsxs("section", { "data-slot": "page-section", className: cn("py-6", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", headerClassName), children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [renderedTitle, titleAdornment] }), renderedDescription] }), headerAction ? (_jsx("div", { className: "flex flex-wrap items-center justify-end gap-3 sm:shrink-0", children: headerAction })) : null] }), children ? (_jsx("div", { className: cn("pt-4", contentClassName), children: children })) : null] }));
|
|
12
|
+
return (_jsxs("section", { "data-slot": "page-section", className: cn("py-6", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", headerClassName), children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: cn("flex flex-wrap items-center", titleAdornmentSpacing === "comfortable" ? "gap-4" : "gap-1.5"), children: [renderedTitle, titleAdornment] }), renderedDescription] }), headerAction ? (_jsx("div", { className: "flex flex-wrap items-center justify-end gap-3 sm:shrink-0", children: headerAction })) : null] }), children ? (_jsx("div", { className: cn("pt-4", contentClassName), children: children })) : null] }));
|
|
13
13
|
}
|
|
14
14
|
export { PageSection, PageSectionGroup };
|
package/dist/popover-menu.d.ts
CHANGED
|
@@ -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;
|
package/dist/popover-menu.js
CHANGED
|
@@ -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",
|
|
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
|
|
56
|
-
|
|
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,8 +22,13 @@ 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;
|
|
26
29
|
export { Popover, PopoverTitle };
|
|
30
|
+
export interface PopoverListProps {
|
|
31
|
+
items: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
/** Read-only information rows for a popover, without implying menu actions. */
|
|
34
|
+
export declare function PopoverList({ items }: PopoverListProps): 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
|
}
|
|
@@ -87,3 +87,7 @@ function PopoverTitle({ className, children, ...props }) {
|
|
|
87
87
|
return (_jsx(Typography, { as: "p", variant: "label", "data-slot": "popover-title", className: className, ...props, children: children }));
|
|
88
88
|
}
|
|
89
89
|
export { Popover, PopoverTitle };
|
|
90
|
+
/** Read-only information rows for a popover, without implying menu actions. */
|
|
91
|
+
export function PopoverList({ items }) {
|
|
92
|
+
return (_jsx("ul", { "data-slot": "popover-list", className: "max-h-64 space-y-3 overflow-y-auto text-sm text-foreground", children: items.map((item, index) => (_jsxs("li", { className: "flex items-start gap-2", children: [_jsx("span", { "aria-hidden": "true", className: "mt-2 size-1 shrink-0 rounded-full bg-muted-foreground" }), _jsx("span", { className: "min-w-0 break-words", children: item })] }, index))) }));
|
|
93
|
+
}
|
|
@@ -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;
|