@neasg/design-system 0.4.35 → 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.
- package/dist/activity-card.d.ts +29 -0
- package/dist/activity-card.js +65 -0
- 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.js +1 -1
- package/dist/button.d.ts +1 -1
- package/dist/button.js +1 -0
- package/dist/calendar.js +1 -1
- package/dist/card.d.ts +1 -1
- package/dist/card.js +1 -1
- package/dist/compound-search-input.d.ts +8 -0
- package/dist/compound-search-input.js +9 -0
- package/dist/date-input.js +3 -3
- package/dist/dialog.d.ts +3 -1
- package/dist/dialog.js +6 -2
- package/dist/draggable-tabs.js +9 -8
- package/dist/index.d.ts +12 -4
- package/dist/index.js +6 -2
- 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-item.d.ts +23 -3
- package/dist/message-item.js +115 -16
- package/dist/notification.d.ts +14 -3
- package/dist/notification.js +32 -7
- package/dist/popover-menu.d.ts +4 -2
- package/dist/popover-menu.js +65 -5
- package/dist/popover.d.ts +5 -2
- package/dist/popover.js +3 -3
- package/dist/rich-text-editor.d.ts +2 -1
- package/dist/rich-text-editor.js +28 -3
- package/dist/routing-timeline.d.ts +7 -2
- package/dist/routing-timeline.js +67 -29
- package/dist/search-input.d.ts +4 -0
- package/dist/search-input.js +2 -2
- 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-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/textarea.d.ts +1 -0
- package/dist/textarea.js +3 -2
- package/dist/theme-switcher.d.ts +3 -1
- package/dist/theme-switcher.js +8 -2
- package/package.json +1 -1
package/dist/layout.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
5
|
-
import { GripVertical, PanelRightClose, PanelRightOpen } from "lucide-react";
|
|
5
|
+
import { ChevronsUpDown, GripVertical, PanelRightClose, PanelRightOpen, } from "lucide-react";
|
|
6
6
|
import { ChevronDownIcon } from "./animated-icons/chevron-down";
|
|
7
7
|
import { ChevronLeftIcon } from "./animated-icons/chevron-left";
|
|
8
8
|
import { ChevronRightIcon } from "./animated-icons/chevron-right";
|
|
@@ -10,6 +10,7 @@ import { Avatar, AvatarProfile } from "./avatar";
|
|
|
10
10
|
import { AvatarProfilePopover, } from "./avatar-profile-popover";
|
|
11
11
|
import { Button } from "./button";
|
|
12
12
|
import { CountBadge } from "./count-badge";
|
|
13
|
+
import { EmptyState } from "./empty-state";
|
|
13
14
|
import { ScrollHint } from "./scroll-hint";
|
|
14
15
|
import { Tabs } from "./tabs";
|
|
15
16
|
import { Tooltip } from "./tooltip";
|
|
@@ -17,6 +18,7 @@ import { Typography } from "./typography";
|
|
|
17
18
|
import { cn } from "./lib/utils";
|
|
18
19
|
import { rightPanelEdgeClassName, rightPanelEdgeClosedClassName, rightPanelEdgeOpenClassName, rightPanelResizeHandleSurfaceClassName, } from "./layout-right-panel-edge";
|
|
19
20
|
const COLLAPSE_BREAKPOINT = 1800;
|
|
21
|
+
const SIDEBAR_CONTENT_CROSSFADE_MS = 100;
|
|
20
22
|
const RIGHT_PANEL_AUTO_COLLAPSE_BREAKPOINT = 1800;
|
|
21
23
|
const RIGHT_PANEL_TRANSITION_MS = 300;
|
|
22
24
|
const RIGHT_PANEL_RESIZE_DEFAULT_WIDTH = 432;
|
|
@@ -81,32 +83,77 @@ function useScrollOverflowHint() {
|
|
|
81
83
|
}
|
|
82
84
|
export function SidebarProvider({ children, defaultCollapsed = false, }) {
|
|
83
85
|
const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed);
|
|
86
|
+
const [isContentCollapsed, setIsContentCollapsed] = React.useState(defaultCollapsed);
|
|
87
|
+
const [isContentVisible, setIsContentVisible] = React.useState(true);
|
|
88
|
+
const isCollapsedRef = React.useRef(defaultCollapsed);
|
|
89
|
+
const isContentCollapsedRef = React.useRef(defaultCollapsed);
|
|
84
90
|
const manualCollapsedRef = React.useRef(defaultCollapsed);
|
|
91
|
+
const collapseTimerRef = React.useRef(null);
|
|
92
|
+
const clearCollapseTimer = React.useCallback(() => {
|
|
93
|
+
if (collapseTimerRef.current === null)
|
|
94
|
+
return;
|
|
95
|
+
clearTimeout(collapseTimerRef.current);
|
|
96
|
+
collapseTimerRef.current = null;
|
|
97
|
+
}, []);
|
|
98
|
+
const updateCollapsedState = React.useCallback((nextCollapsed) => {
|
|
99
|
+
var _a;
|
|
100
|
+
clearCollapseTimer();
|
|
101
|
+
isCollapsedRef.current = nextCollapsed;
|
|
102
|
+
setIsCollapsed(nextCollapsed);
|
|
103
|
+
const reduceMotion = typeof window !== "undefined" &&
|
|
104
|
+
((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)").matches);
|
|
105
|
+
if (reduceMotion) {
|
|
106
|
+
isContentCollapsedRef.current = nextCollapsed;
|
|
107
|
+
setIsContentCollapsed(nextCollapsed);
|
|
108
|
+
setIsContentVisible(true);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (isContentCollapsedRef.current === nextCollapsed) {
|
|
112
|
+
setIsContentVisible(true);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setIsContentVisible(false);
|
|
116
|
+
collapseTimerRef.current = setTimeout(() => {
|
|
117
|
+
collapseTimerRef.current = null;
|
|
118
|
+
if (isCollapsedRef.current === nextCollapsed) {
|
|
119
|
+
isContentCollapsedRef.current = nextCollapsed;
|
|
120
|
+
setIsContentCollapsed(nextCollapsed);
|
|
121
|
+
setIsContentVisible(true);
|
|
122
|
+
}
|
|
123
|
+
}, SIDEBAR_CONTENT_CROSSFADE_MS);
|
|
124
|
+
}, [clearCollapseTimer]);
|
|
85
125
|
const collapse = React.useCallback(() => {
|
|
86
|
-
setIsCollapsed(true);
|
|
87
126
|
manualCollapsedRef.current = true;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
127
|
+
updateCollapsedState(true);
|
|
128
|
+
}, [updateCollapsedState]);
|
|
129
|
+
const toggle = React.useCallback(() => {
|
|
130
|
+
const nextCollapsed = !isCollapsedRef.current;
|
|
131
|
+
manualCollapsedRef.current = nextCollapsed;
|
|
132
|
+
updateCollapsedState(nextCollapsed);
|
|
133
|
+
}, [updateCollapsedState]);
|
|
96
134
|
React.useEffect(() => {
|
|
97
135
|
const handleResize = () => {
|
|
98
136
|
if (window.innerWidth < COLLAPSE_BREAKPOINT) {
|
|
99
|
-
|
|
137
|
+
updateCollapsedState(true);
|
|
100
138
|
}
|
|
101
139
|
else {
|
|
102
|
-
|
|
140
|
+
updateCollapsedState(manualCollapsedRef.current);
|
|
103
141
|
}
|
|
104
142
|
};
|
|
105
143
|
handleResize();
|
|
106
144
|
window.addEventListener("resize", handleResize);
|
|
107
|
-
return () =>
|
|
108
|
-
|
|
109
|
-
|
|
145
|
+
return () => {
|
|
146
|
+
window.removeEventListener("resize", handleResize);
|
|
147
|
+
clearCollapseTimer();
|
|
148
|
+
};
|
|
149
|
+
}, [clearCollapseTimer, updateCollapsedState]);
|
|
150
|
+
return (_jsx(SidebarContext.Provider, { value: {
|
|
151
|
+
isCollapsed,
|
|
152
|
+
isContentCollapsed,
|
|
153
|
+
isContentVisible,
|
|
154
|
+
toggle,
|
|
155
|
+
collapse,
|
|
156
|
+
}, children: children }));
|
|
110
157
|
}
|
|
111
158
|
export function useSidebar() {
|
|
112
159
|
const context = React.useContext(SidebarContext);
|
|
@@ -142,10 +189,10 @@ function DashboardLayout({ className, masthead, prototypeBanner, sidebar, rightP
|
|
|
142
189
|
}, [masthead, prototypeBanner]);
|
|
143
190
|
return (_jsx(SidebarProvider, { defaultCollapsed: defaultSidebarCollapsed, children: _jsxs("div", { className: cn("flex min-h-screen flex-col bg-[hsl(var(--layout-shell-background))]", className), style: layoutStyle, ...props, children: [masthead || prototypeBanner ? (_jsxs("div", { ref: chromeRef, className: "sticky top-0 z-40 shrink-0", children: [masthead, prototypeBanner] })) : null, _jsxs("div", { className: "flex min-h-0 flex-1", children: [sidebar, _jsx(DashboardContent, { className: contentClassName, hasSidebar: Boolean(sidebar), rightPanel: rightPanel, rightPanelClassName: rightPanelClassName, rightPanelFallbackTrigger: rightPanelFallbackTrigger, rightPanelMode: rightPanelMode, rightPanelResizable: rightPanelResizable, rightPanelCollapsible: rightPanelCollapsible, rightPanelCollapsed: rightPanelCollapsed, defaultRightPanelCollapsed: defaultRightPanelCollapsed, onRightPanelCollapsedChange: onRightPanelCollapsedChange, rightPanelDefaultWidth: rightPanelDefaultWidth, rightPanelMinWidth: rightPanelMinWidth, rightPanelMaxWidth: rightPanelMaxWidth, rightPanelAutoCollapseSidebarBreakpoint: rightPanelAutoCollapseSidebarBreakpoint, surfaceClassName: surfaceClassName, children: children })] })] }) }));
|
|
144
191
|
}
|
|
145
|
-
function SidebarShell({ isCollapsed, className, children, ...props }) {
|
|
146
|
-
return (_jsx("aside", { className: cn("fixed left-0 top-[var(--layout-top-offset)] z-30 flex h-[calc(100dvh-var(--layout-top-offset))] shrink-0 flex-col bg-[hsl(var(--layout-sidebar-background))] pb-2 pt-[var(--layout-sidebar-padding-top)] transition-
|
|
147
|
-
? "w-[var(--layout-sidebar-collapsed-width)]
|
|
148
|
-
: "w-[var(--layout-sidebar-width)]
|
|
192
|
+
function SidebarShell({ isCollapsed, isContentCollapsed, isContentVisible, className, children, ...props }) {
|
|
193
|
+
return (_jsx("aside", { className: cn("fixed left-0 top-[var(--layout-top-offset)] z-30 flex h-[calc(100dvh-var(--layout-top-offset))] shrink-0 flex-col bg-[hsl(var(--layout-sidebar-background))] pb-2 pt-[var(--layout-sidebar-padding-top)] transition-[width] duration-300 ease-in-out will-change-[width] motion-reduce:transition-none", isCollapsed
|
|
194
|
+
? "w-[var(--layout-sidebar-collapsed-width)] px-[var(--layout-sidebar-padding-inline)]"
|
|
195
|
+
: "w-[var(--layout-sidebar-width)] px-[var(--layout-sidebar-padding-inline)]", isContentCollapsed ? "overflow-visible" : "overflow-hidden", className), ...props, children: _jsx("div", { "data-slot": "sidebar-transition-content", className: cn("flex min-h-0 flex-1 flex-col transition-opacity duration-100 ease-out motion-reduce:transition-none", isContentVisible ? "opacity-100" : "opacity-0"), children: children }) }));
|
|
149
196
|
}
|
|
150
197
|
function SidebarHeader({ className, children, }) {
|
|
151
198
|
return _jsx("div", { className: cn("shrink-0", className), children: children });
|
|
@@ -242,7 +289,9 @@ function getDashboardSidebarProfileAvatarOverlay(status) {
|
|
|
242
289
|
return (_jsx("span", { className: cn("absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 border-[hsl(var(--layout-sidebar-background))] shadow-sm", getDashboardSidebarProfileStatusClassName(status)) }));
|
|
243
290
|
}
|
|
244
291
|
function DashboardSidebarProfileView({ isCollapsed = false, status = "online", name, role, rolePlacement = "below", size = "sm", ...props }) {
|
|
245
|
-
return (_jsx(AvatarProfile, { ...props, size: size, name: isCollapsed ? undefined : name, role: isCollapsed ? undefined : role, rolePlacement: rolePlacement, className: cn("rounded-control
|
|
292
|
+
return (_jsx(AvatarProfile, { ...props, size: size, name: isCollapsed ? undefined : name, role: isCollapsed ? undefined : role, rolePlacement: rolePlacement, className: cn("w-full items-center rounded-control text-muted-foreground transition-colors hover:bg-card hover:text-foreground", isCollapsed
|
|
293
|
+
? "justify-center p-0"
|
|
294
|
+
: "min-w-0 gap-[8px] p-[10px]"), avatarClassName: "mt-0 h-[32px] w-[32px] rounded-[10px]", fallbackClassName: "rounded-[10px]", contentClassName: "max-w-[120px] gap-[2px]", nameClassName: "font-semibold leading-none", roleClassName: "leading-none text-foreground", topInfoClassName: "leading-none text-foreground", bottomInfoClassName: "leading-none text-foreground", avatarOverlay: getDashboardSidebarProfileAvatarOverlay(status) }));
|
|
246
295
|
}
|
|
247
296
|
function DashboardSidebarProfile({ isCollapsed = false, ...props }) {
|
|
248
297
|
return (_jsx("div", { className: cn(isCollapsed ? "px-0" : "px-1", "pb-4 pt-2"), children: _jsx(DashboardSidebarProfileView, { isCollapsed: isCollapsed, ...props }) }));
|
|
@@ -254,15 +303,13 @@ const DashboardSidebarProfileTrigger = React.forwardRef(({ isCollapsed = false,
|
|
|
254
303
|
: "Open profile menu");
|
|
255
304
|
const resolvedTop = topInfo !== null && topInfo !== void 0 ? topInfo : (rolePlacement === "above" ? role : undefined);
|
|
256
305
|
const resolvedBottom = bottomInfo !== null && bottomInfo !== void 0 ? bottomInfo : (rolePlacement === "below" ? role : undefined);
|
|
257
|
-
const hasSupportingInfo = (resolvedTop !== undefined && resolvedTop !== null) ||
|
|
258
|
-
(resolvedBottom !== undefined && resolvedBottom !== null);
|
|
259
306
|
const hasMeta = name !== undefined ||
|
|
260
307
|
nameTrailing !== undefined ||
|
|
261
308
|
resolvedTop !== undefined ||
|
|
262
309
|
resolvedBottom !== undefined;
|
|
263
|
-
const infoClassName = "min-w-0 truncate text-xs leading-
|
|
264
|
-
const nameNode = name !== undefined && name !== null ? (_jsx("span", { className: "min-w-0 truncate text-sm font-
|
|
265
|
-
return (_jsx("div", { className: cn(isCollapsed ? "px-0" : "px-1", "pb-4 pt-2"), children: _jsx("button", { ...buttonProps, ref: ref, type: "button", "aria-label": profileLabel, "aria-busy": loading ? "true" : undefined, className: cn("group/profile-trigger block h-auto w-full cursor-pointer rounded-control border-0 bg-transparent p-0 text-left text-muted-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0", isCollapsed ? "justify-center" : "justify-start", triggerClassName), children: _jsxs("span", { className: cn("flex min-w-0 max-w-full rounded-control
|
|
310
|
+
const infoClassName = "min-w-0 truncate text-xs leading-none text-foreground";
|
|
311
|
+
const nameNode = name !== undefined && name !== null ? (_jsx("span", { className: "min-w-0 truncate text-sm font-semibold leading-none text-foreground", children: name })) : null;
|
|
312
|
+
return (_jsx("div", { className: cn(isCollapsed ? "px-0" : "px-1", "pb-4 pt-2"), children: _jsx("button", { ...buttonProps, ref: ref, type: "button", "aria-label": profileLabel, "aria-busy": loading ? "true" : undefined, className: cn("group/profile-trigger block h-auto w-full cursor-pointer rounded-control border-0 bg-transparent p-0 text-left text-muted-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0", isCollapsed ? "justify-center" : "justify-start", triggerClassName), children: _jsxs("span", { className: cn("flex w-full min-w-0 max-w-full items-center rounded-control transition-colors hover:bg-card hover:text-foreground", isCollapsed ? "justify-center p-0" : "gap-[8px] p-[10px]"), children: [_jsxs("span", { className: "relative shrink-0", children: [loading ? (_jsx("span", { "aria-hidden": "true", className: "block h-[32px] w-[32px] shrink-0 animate-pulse rounded-[10px] bg-muted" })) : (_jsx(Avatar, { src: src, alt: alt, fallback: fallback, size: size, className: "h-[32px] w-[32px] rounded-[10px]", fallbackClassName: "rounded-[10px]" })), getDashboardSidebarProfileAvatarOverlay(status)] }), !isCollapsed && hasMeta ? (_jsxs("span", { className: "flex min-w-0 max-w-[120px] flex-1 flex-col gap-[2px]", children: [resolvedTop !== undefined && resolvedTop !== null ? (_jsx("span", { className: infoClassName, children: resolvedTop })) : null, nameTrailing !== undefined && nameTrailing !== null ? (_jsxs("span", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [nameNode, nameTrailing] })) : (nameNode), resolvedBottom !== undefined && resolvedBottom !== null ? (_jsx("span", { className: infoClassName, children: resolvedBottom })) : null] })) : null, !isCollapsed ? (_jsx(ChevronsUpDown, { "aria-hidden": "true", className: "ml-auto shrink-0 text-foreground", size: 16 })) : null] }) }) }));
|
|
266
313
|
});
|
|
267
314
|
DashboardSidebarProfileTrigger.displayName = "DashboardSidebarProfileTrigger";
|
|
268
315
|
function DashboardSidebarProfilePopover({ align = "end", alignOffset, avoidCollisions, children, collisionPadding = 8, contentClassName, defaultOpen, modal, onOpenChange, open, side = "right", sideOffset = 4, triggerAriaLabel, triggerClassName, ...profileProps }) {
|
|
@@ -315,44 +362,73 @@ function DashboardSidebarItem({ item, isCollapsed, depth = 0, }) {
|
|
|
315
362
|
}
|
|
316
363
|
return (_jsx(DashboardSidebarLeafItem, { item: item, isCollapsed: isCollapsed, depth: depth }));
|
|
317
364
|
}
|
|
318
|
-
function DashboardSidebar({ className, children, logo, search, navGroups, footerItems, footer, toggleAriaLabel = "Toggle sidebar", ...props }) {
|
|
365
|
+
function DashboardSidebar({ className, children, logo, collapsedToggleIcon, search, navGroups, showNavigationDivider = true, footerItems, footer, toggleAriaLabel = "Toggle sidebar", ...props }) {
|
|
319
366
|
var _a;
|
|
320
|
-
const { isCollapsed, toggle } = useSidebar();
|
|
367
|
+
const { isCollapsed, isContentCollapsed, isContentVisible, toggle, } = useSidebar();
|
|
321
368
|
const hasStructuredSidebar = logo !== undefined ||
|
|
322
369
|
search !== undefined ||
|
|
323
370
|
navGroups !== undefined ||
|
|
324
371
|
footerItems !== undefined ||
|
|
325
372
|
footer !== undefined;
|
|
326
373
|
if (!hasStructuredSidebar) {
|
|
327
|
-
return (_jsx(SidebarShell, { isCollapsed: isCollapsed, className: className, ...props, children: children }));
|
|
374
|
+
return (_jsx(SidebarShell, { isCollapsed: isCollapsed, isContentCollapsed: isContentCollapsed, isContentVisible: isContentVisible, className: className, ...props, children: children }));
|
|
328
375
|
}
|
|
329
|
-
return (_jsxs(SidebarShell, { isCollapsed: isCollapsed, className: className, ...props, children: [_jsxs(SidebarHeader, { className: cn("flex h-14 items-center gap-2",
|
|
376
|
+
return (_jsxs(SidebarShell, { isCollapsed: isCollapsed, isContentCollapsed: isContentCollapsed, isContentVisible: isContentVisible, className: className, ...props, children: [_jsxs(SidebarHeader, { className: cn("flex h-14 items-center gap-2", isContentCollapsed
|
|
377
|
+
? "justify-center px-0"
|
|
378
|
+
: "justify-between px-3"), children: [renderSidebarSlot(logo, isContentCollapsed), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": toggleAriaLabel, title: isContentCollapsed && collapsedToggleIcon
|
|
379
|
+
? toggleAriaLabel
|
|
380
|
+
: undefined, className: cn("shrink-0 rounded-control", isContentCollapsed
|
|
381
|
+
? collapsedSidebarControlClassName
|
|
382
|
+
: "h-7 w-7"), onClick: toggle, children: isContentCollapsed ? (collapsedToggleIcon !== null && collapsedToggleIcon !== void 0 ? collapsedToggleIcon : _jsx(ChevronRightIcon, { size: 16 })) : (_jsx(ChevronLeftIcon, { size: 16 })) })] }), search ? (_jsx(SidebarHeader, { className: cn(isContentCollapsed
|
|
330
383
|
? "flex justify-center px-0 pb-3 pt-2"
|
|
331
|
-
: "px-2 pb-3 pt-2"), children: renderSidebarSlot(search,
|
|
384
|
+
: "px-2 pb-3 pt-2"), children: renderSidebarSlot(search, isContentCollapsed) })) : null, showNavigationDivider &&
|
|
385
|
+
(search || ((_a = navGroups === null || navGroups === void 0 ? void 0 : navGroups.length) !== null && _a !== void 0 ? _a : 0) > 0) ? (_jsx(SidebarHeader, { className: "mx-2 mb-2 border-t border-[hsl(var(--layout-sidebar-border))]" })) : null, (navGroups === null || navGroups === void 0 ? void 0 : navGroups.length) ? (_jsx(SidebarContent, { className: cn(isContentCollapsed
|
|
332
386
|
? "overflow-visible p-[var(--layout-sidebar-content-padding)]"
|
|
333
387
|
: "p-[var(--layout-sidebar-content-padding)]"), children: _jsx("nav", { "aria-label": "Sidebar navigation", children: navGroups.map((group, groupIndex) => {
|
|
334
388
|
var _a;
|
|
335
|
-
return (_jsx(SidebarGroup, { children: _jsxs("div", { className: cn(groupIndex > 0 &&
|
|
336
|
-
|
|
389
|
+
return (_jsx(SidebarGroup, { children: _jsxs("div", { className: cn(groupIndex > 0 &&
|
|
390
|
+
"mt-[var(--layout-sidebar-section-gap)]"), children: [group.label ? (_jsx(SidebarGroupLabel, { "aria-hidden": isContentCollapsed ? true : undefined, className: isContentCollapsed ? "invisible" : undefined, children: group.label })) : null, _jsx(SidebarMenu, { children: group.items.map((item) => (_jsx(SidebarMenuItem, { collapsed: isContentCollapsed, children: _jsx(DashboardSidebarItem, { item: item, isCollapsed: isContentCollapsed }) }, item.id))) })] }) }, `${(_a = group.label) !== null && _a !== void 0 ? _a : "group"}-${groupIndex}`));
|
|
391
|
+
}) }) })) : null, (footerItems === null || footerItems === void 0 ? void 0 : footerItems.length) ? (_jsx(SidebarFooter, { className: cn(isContentCollapsed ? "px-0 py-1" : "px-1.5 py-1"), children: _jsx(SidebarMenu, { children: footerItems.map((item) => (_jsx(SidebarMenuItem, { collapsed: isContentCollapsed, children: _jsx(DashboardSidebarItem, { item: item, isCollapsed: isContentCollapsed }) }, item.id))) }) })) : null, footer ? _jsx(SidebarFooter, { className: "mx-1.5 mt-1.5 border-t border-[hsl(var(--layout-sidebar-border))] pt-1.5" }) : null, footer ? (_jsx(SidebarFooter, { children: renderSidebarSlot(footer, isContentCollapsed) })) : null] }));
|
|
337
392
|
}
|
|
338
393
|
function DashboardRightPanelSection({ className, title, description, titleClassName, descriptionClassName, children, ...props }) {
|
|
339
394
|
return (_jsxs("section", { className: cn("space-y-[var(--layout-right-panel-section-gap)]", className), ...props, children: [title || description ? (_jsxs("div", { className: "space-y-[var(--layout-right-panel-heading-gap)]", children: [title ? (_jsx(Typography, { as: "h3", variant: "body", className: cn("font-semibold", titleClassName), children: title })) : null, description ? (_jsx(Typography, { as: "p", variant: "caption", className: descriptionClassName, children: description })) : null] })) : null, children] }));
|
|
340
395
|
}
|
|
341
|
-
function DashboardRightPanelTabs({ className, listWrapperClassName, panelClassName, ...props }) {
|
|
342
|
-
return (_jsx(Tabs, { className: cn("flex min-h-0 flex-1 flex-col space-y-0", className), listWrapperClassName: cn("scrollbar-none w-full min-w-0 max-w-full shrink-0 self-stretch overflow-x-auto overflow-y-hidden
|
|
396
|
+
function DashboardRightPanelTabs({ className, listWrapperClassName, panelClassName, spacing = "default", size, ...props }) {
|
|
397
|
+
return (_jsx(Tabs, { className: cn("flex min-h-0 flex-1 flex-col space-y-0", className), listWrapperClassName: cn("scrollbar-none w-full min-w-0 max-w-full shrink-0 self-stretch overflow-x-auto overflow-y-hidden", spacing === "spacious"
|
|
398
|
+
? "px-[32px] pb-[24px] pt-[24px]"
|
|
399
|
+
: "pb-[var(--layout-right-panel-tabs-gap)] pl-[var(--layout-right-panel-padding)] pr-[calc(var(--layout-right-panel-padding)+var(--layout-right-panel-action-space,0px))] pt-[var(--layout-right-panel-padding)]", listWrapperClassName), panelClassName: cn("min-h-0 flex-1 flex-col overflow-hidden pt-0 data-[state=active]:flex", panelClassName), size: size !== null && size !== void 0 ? size : (spacing === "spacious" ? "comfortable" : undefined), ...props }));
|
|
343
400
|
}
|
|
344
|
-
|
|
345
|
-
|
|
401
|
+
const DashboardRightPanel = React.forwardRef(({ className, surface = "default", ...props }, ref) => (_jsx("div", { ref: ref, className: cn("flex min-h-0 flex-1 flex-col", surface === "plain"
|
|
402
|
+
? "bg-background"
|
|
403
|
+
: "bg-[hsl(var(--layout-right-panel-background))]", className), ...props })));
|
|
404
|
+
DashboardRightPanel.displayName = "DashboardRightPanel";
|
|
405
|
+
function DashboardRightPanelHeader({ className, spacing = "default", title, subtitle, badge, action, children, ...props }) {
|
|
406
|
+
return (_jsx("div", { className: cn(spacing === "comfortable"
|
|
407
|
+
? "shrink-0 p-[16px]"
|
|
408
|
+
: "shrink-0 pb-[var(--layout-right-panel-padding)] pl-[var(--layout-right-panel-padding)] pr-[calc(var(--layout-right-panel-padding)+var(--layout-right-panel-action-space,0px))] pt-[var(--layout-right-panel-padding)]", className), ...props, children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: cn("min-w-0 flex-1", spacing === "comfortable" && "space-y-[4px]"), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Typography, { as: "h2", variant: "subheading", children: title }), badge] }), subtitle ? (_jsx(Typography, { as: "p", variant: "caption", className: spacing === "comfortable"
|
|
409
|
+
? "text-muted-foreground"
|
|
410
|
+
: undefined, children: subtitle })) : null, children] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }) }));
|
|
346
411
|
}
|
|
347
|
-
|
|
348
|
-
|
|
412
|
+
const DashboardActionPanel = React.forwardRef(({ panelId, headerSpacing, title, subtitle, badge, action, headerChildren, className, children, ...props }, ref) => (_jsxs(DashboardRightPanel, { ref: ref, "data-slot": "dashboard-action-panel", "data-panel-id": panelId, className: cn("h-full min-h-0 w-full", className), ...props, children: [_jsx(DashboardRightPanelHeader, { spacing: headerSpacing, title: title, subtitle: subtitle, badge: badge, action: action, children: headerChildren }), children] })));
|
|
413
|
+
DashboardActionPanel.displayName = "DashboardActionPanel";
|
|
414
|
+
function DashboardActionPanelEmptyState(props) {
|
|
415
|
+
return (_jsx("div", { "data-slot": "dashboard-action-panel-empty-state", className: "absolute inset-0 flex items-center justify-center overflow-y-auto", children: _jsx(EmptyState, { ...props }) }));
|
|
349
416
|
}
|
|
350
|
-
function DashboardRightPanelBody({ className, contentClassName, footer, footerClassName, children, ...props }) {
|
|
417
|
+
function DashboardRightPanelBody({ className, contentClassName, footer, footerVariant = "inline", footerClassName, spacing = "default", topInset = "default", children, ...props }) {
|
|
351
418
|
const { scrollRef, canScrollDown } = useScrollOverflowHint();
|
|
419
|
+
const spacingClassName = spacing === "spacious"
|
|
420
|
+
? "px-[32px] py-[24px]"
|
|
421
|
+
: spacing === "comfortable"
|
|
422
|
+
? "p-[16px]"
|
|
423
|
+
: "p-[var(--layout-right-panel-padding)]";
|
|
424
|
+
const topInsetClassName = topInset === "none" ? "pt-0" : undefined;
|
|
352
425
|
if (footer) {
|
|
353
|
-
|
|
426
|
+
if (footerVariant === "separated") {
|
|
427
|
+
return (_jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col overflow-hidden", className), ...props, children: [_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scrollRef, "data-slot": "dashboard-right-panel-scroll-area", className: cn("scrollbar-none h-full min-h-0 overflow-y-auto", spacingClassName, topInsetClassName, contentClassName), children: children }), _jsx(ScrollHint, { visible: canScrollDown })] }), _jsx("div", { className: cn("shrink-0 border-t border-border bg-muted/50", spacingClassName, footerClassName), children: footer })] }));
|
|
428
|
+
}
|
|
429
|
+
return (_jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col overflow-hidden", spacingClassName, topInsetClassName, className), ...props, children: [_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scrollRef, "data-slot": "dashboard-right-panel-scroll-area", className: cn("scrollbar-none h-full min-h-0 space-y-[var(--layout-right-panel-gap)] overflow-y-auto", contentClassName), children: children }), _jsx(ScrollHint, { visible: canScrollDown })] }), _jsx("div", { className: cn("shrink-0 space-y-[var(--layout-right-panel-footer-gap)] pt-[var(--layout-right-panel-gap)]", footerClassName), children: footer })] }));
|
|
354
430
|
}
|
|
355
|
-
return (_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scrollRef, className: cn("scrollbar-none h-full min-h-0 space-y-[var(--layout-right-panel-gap)] overflow-y-auto
|
|
431
|
+
return (_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scrollRef, "data-slot": "dashboard-right-panel-scroll-area", className: cn("scrollbar-none h-full min-h-0 space-y-[var(--layout-right-panel-gap)] overflow-y-auto", spacingClassName, topInsetClassName, className), ...props, children: children }), _jsx(ScrollHint, { visible: canScrollDown })] }));
|
|
356
432
|
}
|
|
357
433
|
function DashboardContent({ className, hasSidebar = true, rightPanel, rightPanelClassName, rightPanelFallbackTrigger, rightPanelMode = "responsive", rightPanelResizable = false, rightPanelCollapsible = false, rightPanelCollapsed, defaultRightPanelCollapsed = false, onRightPanelCollapsedChange, rightPanelDefaultWidth = RIGHT_PANEL_RESIZE_DEFAULT_WIDTH, rightPanelMinWidth = RIGHT_PANEL_RESIZE_MIN_WIDTH, rightPanelMaxWidth = RIGHT_PANEL_RESIZE_MAX_WIDTH, rightPanelAutoCollapseSidebarBreakpoint = RIGHT_PANEL_AUTO_COLLAPSE_BREAKPOINT, surfaceClassName, children, ...props }) {
|
|
358
434
|
const { isCollapsed, toggle, collapse } = useSidebar();
|
|
@@ -725,4 +801,4 @@ function DashboardContent({ className, hasSidebar = true, rightPanel, rightPanel
|
|
|
725
801
|
? "translate-x-0"
|
|
726
802
|
: "pointer-events-none translate-x-full", rightPanelClassName), children: _jsx("div", { className: "scrollbar-none flex h-full w-full flex-col overflow-y-auto overflow-x-hidden", children: renderedRightPanel }) })] })) : null] }));
|
|
727
803
|
}
|
|
728
|
-
export { DashboardLayout, DashboardSidebar, DashboardSidebarProfile, DashboardSidebarProfilePopover, DashboardContent, DashboardRightPanel, DashboardRightPanelHeader, DashboardRightPanelBody, DashboardRightPanelSection, DashboardRightPanelTabs, };
|
|
804
|
+
export { DashboardLayout, DashboardSidebar, DashboardSidebarProfile, DashboardSidebarProfilePopover, DashboardContent, DashboardActionPanel, DashboardActionPanelEmptyState, DashboardRightPanel, DashboardRightPanelHeader, DashboardRightPanelBody, DashboardRightPanelSection, DashboardRightPanelTabs, };
|
package/dist/lib/utils.js
CHANGED
package/dist/message-item.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export interface MessageItemData {
|
|
|
11
11
|
senderRole: "OFFICER" | "APPLICANT" | "SYSTEM";
|
|
12
12
|
officerName?: string | null;
|
|
13
13
|
officerRole?: string | null;
|
|
14
|
+
avatarUrl?: string | null;
|
|
15
|
+
systemLabel?: string | null;
|
|
16
|
+
senderBadgeLabel?: React.ReactNode;
|
|
14
17
|
applicantEmail?: string | null;
|
|
15
18
|
applicantName?: string | null;
|
|
16
19
|
content: string;
|
|
@@ -24,12 +27,17 @@ export interface MessageItemProps {
|
|
|
24
27
|
caseId?: string;
|
|
25
28
|
showNewBadge?: boolean;
|
|
26
29
|
newMessageLabel?: React.ReactNode;
|
|
30
|
+
/** Controls whether unread state is shown as an inline badge or a divider before the message. */
|
|
31
|
+
newIndicatorPresentation?: "badge" | "divider";
|
|
27
32
|
/** @deprecated Unread messages render a divider. Use `newMessageLabel` to customize its text. */
|
|
28
33
|
newIndicator?: React.ReactNode;
|
|
29
34
|
onMarkAsRead?: (message: MessageItemData) => Promise<boolean | void> | boolean | void;
|
|
30
35
|
onMarkedAsRead?: (message: MessageItemData) => void;
|
|
31
36
|
getAttachmentDownloadUrl?: (attachment: MessageItemAttachment, message: MessageItemData, caseId?: string) => string | null | undefined;
|
|
32
37
|
formatTimestamp?: (date: Date, message: MessageItemData) => React.ReactNode;
|
|
38
|
+
variant?: "default" | "card";
|
|
39
|
+
/** `compact` reduces card insets for constrained correspondence panels. */
|
|
40
|
+
density?: "default" | "compact";
|
|
33
41
|
className?: string;
|
|
34
42
|
contentClassName?: string;
|
|
35
43
|
}
|
|
@@ -37,6 +45,7 @@ export interface MessageAttachmentRowProps extends React.HTMLAttributes<HTMLElem
|
|
|
37
45
|
attachment: MessageItemAttachment;
|
|
38
46
|
downloadUrl?: string | null;
|
|
39
47
|
downloadLabel?: string;
|
|
48
|
+
variant?: "default" | "compact";
|
|
40
49
|
}
|
|
41
50
|
export interface MessageNewDividerProps {
|
|
42
51
|
children?: React.ReactNode;
|
|
@@ -47,16 +56,27 @@ export interface MessageThreadProps {
|
|
|
47
56
|
caseId?: string;
|
|
48
57
|
showNewBadge?: boolean;
|
|
49
58
|
newMessageLabel?: React.ReactNode;
|
|
59
|
+
/** Controls whether unread state is shown as an inline badge or a divider before the first unread message. */
|
|
60
|
+
newIndicatorPresentation?: "badge" | "divider";
|
|
50
61
|
onMarkAsRead?: MessageItemProps["onMarkAsRead"];
|
|
51
62
|
onMarkedAsRead?: MessageItemProps["onMarkedAsRead"];
|
|
52
63
|
getAttachmentDownloadUrl?: MessageItemProps["getAttachmentDownloadUrl"];
|
|
53
64
|
formatTimestamp?: MessageItemProps["formatTimestamp"];
|
|
65
|
+
variant?: MessageItemProps["variant"];
|
|
66
|
+
density?: MessageItemProps["density"];
|
|
67
|
+
groupByDate?: boolean;
|
|
68
|
+
/** Keeps timestamps in each card while omitting redundant date-group headings. */
|
|
69
|
+
showDateHeadings?: boolean;
|
|
70
|
+
formatDateHeading?: (date: Date) => React.ReactNode;
|
|
54
71
|
className?: string;
|
|
55
72
|
itemClassName?: string;
|
|
56
73
|
contentClassName?: string;
|
|
57
74
|
}
|
|
58
75
|
export declare const formatMessageDate: (date: Date) => string;
|
|
76
|
+
export declare const formatMessageTime: (date: Date) => string;
|
|
77
|
+
export declare const formatMessageCardTimestamp: (date: Date, now?: Date) => string;
|
|
78
|
+
export declare const formatMessageDateHeading: (date: Date, now?: Date) => string;
|
|
59
79
|
export declare function MessageNewDivider({ children, className, }: MessageNewDividerProps): import("react/jsx-runtime").JSX.Element;
|
|
60
|
-
export declare function MessageAttachmentRow({ attachment, downloadUrl, downloadLabel, className, ...props }: MessageAttachmentRowProps): import("react/jsx-runtime").JSX.Element;
|
|
61
|
-
export declare function MessageItem({ message, caseId, showNewBadge, newMessageLabel, onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, className, contentClassName, }: MessageItemProps): import("react/jsx-runtime").JSX.Element;
|
|
62
|
-
export declare function MessageThread({ messages, caseId, showNewBadge, newMessageLabel, onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, className, itemClassName, contentClassName, }: MessageThreadProps): import("react/jsx-runtime").JSX.Element;
|
|
80
|
+
export declare function MessageAttachmentRow({ attachment, downloadUrl, downloadLabel, variant, className, ...props }: MessageAttachmentRowProps): import("react/jsx-runtime").JSX.Element;
|
|
81
|
+
export declare function MessageItem({ message, caseId, showNewBadge, newMessageLabel, newIndicatorPresentation, onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, variant, density, className, contentClassName, }: MessageItemProps): import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
export declare function MessageThread({ messages, caseId, showNewBadge, newMessageLabel, newIndicatorPresentation, onMarkAsRead, onMarkedAsRead, getAttachmentDownloadUrl, formatTimestamp, variant, density, groupByDate, showDateHeadings, formatDateHeading: formatDateHeadingProp, className, itemClassName, contentClassName, }: MessageThreadProps): import("react/jsx-runtime").JSX.Element;
|
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
|
}
|