@neasg/design-system 0.4.132 → 0.4.137

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.
@@ -0,0 +1,28 @@
1
+ export interface AssigneeOption {
2
+ value: string;
3
+ label: string;
4
+ role?: string;
5
+ /** Optional menu group, such as "Licensing officers" or "Supervisors". */
6
+ group?: string;
7
+ avatarSrc?: string;
8
+ initials?: string;
9
+ keywords?: string[];
10
+ disabled?: boolean;
11
+ }
12
+ export interface AssigneePopoverProps {
13
+ options: AssigneeOption[];
14
+ value: string;
15
+ onValueChange: (value: string) => void;
16
+ /** Value of the current user in options; enables the self-assignment shortcut. */
17
+ currentUserValue?: string;
18
+ disabled?: boolean;
19
+ placeholder?: string;
20
+ highlightQuery?: string;
21
+ triggerAriaLabel?: string;
22
+ menuLabel?: string;
23
+ selfOptionLabel?: string;
24
+ groupLabel?: string;
25
+ searchPlaceholder?: string;
26
+ emptyMessage?: string;
27
+ }
28
+ export declare function AssigneePopover({ options, value, onValueChange, currentUserValue, disabled, placeholder, highlightQuery, triggerAriaLabel, menuLabel, selfOptionLabel, groupLabel, searchPlaceholder, emptyMessage, }: AssigneePopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { UserPlus } from "lucide-react";
5
+ import { AvatarProfile } from "./avatar";
6
+ import { Button } from "./button";
7
+ import { Popover } from "./popover";
8
+ import { PopoverMenuDivider, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuItem, PopoverMenuScrollArea, PopoverMenuSearch, PopoverMenuSection } from "./popover-menu";
9
+ import { SearchHighlight } from "./use-search-highlight";
10
+ import { Typography } from "./typography";
11
+ function Profile({ option, label = option.label, query = "" }) {
12
+ var _a;
13
+ return _jsx(AvatarProfile, { size: "xs", src: option.avatarSrc, alt: option.label, fallback: (_a = option.initials) !== null && _a !== void 0 ? _a : option.label.trim().split(/\s+/).slice(0, 2).map((word) => { var _a; return (_a = word[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase(); }).join(""), name: _jsx(SearchHighlight, { text: label, query: query }), role: option.role, rolePlacement: "below", className: "min-w-0 max-w-full" });
14
+ }
15
+ export function AssigneePopover({ options, value, onValueChange, currentUserValue, disabled = false, placeholder = "Unassigned", highlightQuery, triggerAriaLabel, menuLabel = "Assign to", selfOptionLabel = "Assign to myself", groupLabel = "People", searchPlaceholder = "Search people...", emptyMessage = "No matching people", }) {
16
+ var _a;
17
+ const [open, setOpen] = React.useState(false);
18
+ const [search, setSearch] = React.useState("");
19
+ const selected = (_a = options.find((option) => option.value === value)) !== null && _a !== void 0 ? _a : { value, label: value || placeholder };
20
+ const currentUser = options.find((option) => option.value === currentUserValue);
21
+ const query = search.trim().toLowerCase();
22
+ const filtered = options.filter((option) => {
23
+ var _a;
24
+ return [option.label, option.role, ...((_a = option.keywords) !== null && _a !== void 0 ? _a : [])]
25
+ .some((text) => text === null || text === void 0 ? void 0 : text.toLowerCase().includes(query));
26
+ });
27
+ const groups = filtered.reduce((result, option) => {
28
+ var _a;
29
+ const label = (_a = option.group) !== null && _a !== void 0 ? _a : groupLabel;
30
+ const group = result.find((item) => item.label === label);
31
+ if (group)
32
+ group.options.push(option);
33
+ else
34
+ result.push({ label, options: [option] });
35
+ return result;
36
+ }, []);
37
+ const select = (option) => {
38
+ if (disabled || option.disabled)
39
+ return;
40
+ onValueChange(option.value);
41
+ setOpen(false);
42
+ setSearch("");
43
+ };
44
+ return _jsxs(Popover, { open: open && !disabled, onOpenChange: (nextOpen) => {
45
+ setOpen(nextOpen);
46
+ if (!nextOpen)
47
+ setSearch("");
48
+ }, surface: "menu", align: "start", contentClassName: "w-72", trigger: _jsx(Button, { type: "button", variant: "ghost", size: "default", disabled: disabled, className: "flex h-auto min-w-0 max-w-full justify-start whitespace-normal text-left leading-none", "aria-label": triggerAriaLabel !== null && triggerAriaLabel !== void 0 ? triggerAriaLabel : `Change assignee: ${selected.label}`, onClick: (event) => event.stopPropagation(), children: _jsx(Profile, { option: selected, query: highlightQuery }) }), children: [_jsxs(PopoverMenuGroup, { children: [_jsx(PopoverMenuGroupLabel, { children: menuLabel }), currentUser ? _jsx(PopoverMenuSection, { children: _jsxs(PopoverMenuItem, { active: value === currentUser.value, disabled: currentUser.disabled, onClick: () => select(currentUser), children: [_jsx(UserPlus, { "aria-hidden": "true", className: "size-4" }), _jsx("span", { children: selfOptionLabel })] }) }) : null] }), _jsx(PopoverMenuDivider, {}), _jsx(PopoverMenuSearch, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: searchPlaceholder, "aria-label": searchPlaceholder, className: "h-control border-0 bg-transparent px-2 py-0 text-sm shadow-none", autoFocus: true }), _jsxs(PopoverMenuScrollArea, { children: [groups.map((group) => _jsxs(PopoverMenuGroup, { children: [_jsx(PopoverMenuGroupLabel, { children: group.label }), _jsx(PopoverMenuSection, { children: group.options.map((option) => _jsx(PopoverMenuItem, { active: value === option.value, disabled: option.disabled, onClick: () => select(option), children: _jsx(Profile, { option: option, query: search }) }, option.value)) })] }, group.label)), filtered.length === 0 ? _jsx(Typography, { as: "p", variant: "bodySm", className: "px-2 py-3 text-muted-foreground", role: "status", children: emptyMessage }) : null] })] });
49
+ }
@@ -1,17 +1,29 @@
1
1
  import * as React from "react";
2
2
  import { type DashboardActionPanelProps } from "./layout";
3
3
  import { type NotificationItemData } from "./notification";
4
+ export interface ChangelogActionPanelFilter {
5
+ value: string;
6
+ label: React.ReactNode;
7
+ }
8
+ export interface ChangelogActionPanelNotification extends NotificationItemData {
9
+ category?: string;
10
+ }
4
11
  export interface ChangelogActionPanelProps extends Omit<DashboardActionPanelProps, "children" | "headerSpacing" | "title"> {
5
12
  title?: React.ReactNode;
6
- notifications: NotificationItemData[];
13
+ notifications: ChangelogActionPanelNotification[];
14
+ filters?: ChangelogActionPanelFilter[];
15
+ allFilterLabel?: React.ReactNode;
16
+ filterGroupLabel?: string;
7
17
  isLoading?: boolean;
8
18
  loadingLabel?: string;
9
19
  emptyMessage?: React.ReactNode;
10
20
  emptyDescription?: React.ReactNode;
21
+ filteredEmptyMessage?: React.ReactNode;
22
+ filteredEmptyDescription?: React.ReactNode;
11
23
  }
12
24
  /**
13
25
  * Shared change-log panel. Consumers map their domain events to notification
14
26
  * items; this component owns the complete list, loading, and empty-state UI.
15
27
  */
16
- declare function ChangelogActionPanel({ title, subtitle, notifications, isLoading, loadingLabel, emptyMessage, emptyDescription, ...panelProps }: ChangelogActionPanelProps): import("react/jsx-runtime").JSX.Element;
28
+ declare function ChangelogActionPanel({ title, subtitle, notifications, filters, allFilterLabel, filterGroupLabel, isLoading, loadingLabel, emptyMessage, emptyDescription, filteredEmptyMessage, filteredEmptyDescription, headerChildren, ...panelProps }: ChangelogActionPanelProps): import("react/jsx-runtime").JSX.Element;
17
29
  export { ChangelogActionPanel };
@@ -1,14 +1,29 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from "react";
3
4
  import { History } from "lucide-react";
4
5
  import { DashboardActionPanel, DashboardActionPanelEmptyState, DashboardRightPanelBody, } from "./layout";
6
+ import { FilterToggle } from "./filter-toggle";
5
7
  import { NotificationList, } from "./notification";
6
8
  import { Spinner } from "./spinner";
7
9
  /**
8
10
  * Shared change-log panel. Consumers map their domain events to notification
9
11
  * items; this component owns the complete list, loading, and empty-state UI.
10
12
  */
11
- function ChangelogActionPanel({ title = "Change log", subtitle = "Review all activity recorded here.", notifications, isLoading = false, loadingLabel = "Loading change log", emptyMessage = "No change log entries yet", emptyDescription = "Changes will be recorded here.", ...panelProps }) {
12
- return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", title: title, subtitle: subtitle, ...panelProps, children: _jsx(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", children: isLoading ? (_jsx("div", { "data-slot": "changelog-action-panel-loading", className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "md", label: loadingLabel }) })) : notifications.length === 0 ? (_jsx(DashboardActionPanelEmptyState, { icon: History, message: emptyMessage, description: emptyDescription })) : (_jsx(NotificationList, { density: "compact", notifications: notifications, descriptionLines: 3, emptyState: null, variant: "bordered" })) }) }));
13
+ function ChangelogActionPanel({ title = "Change log", subtitle = "Review all activity recorded here.", notifications, filters = [], allFilterLabel = "All", filterGroupLabel = "Filter change log", isLoading = false, loadingLabel = "Loading change log", emptyMessage = "No change log entries yet", emptyDescription = "Changes will be recorded here.", filteredEmptyMessage = "No activity in this category", filteredEmptyDescription = "Try another change log filter.", headerChildren, ...panelProps }) {
14
+ const [activeFilter, setActiveFilter] = React.useState("all");
15
+ const resolvedActiveFilter = activeFilter === "all" ||
16
+ filters.some((filter) => filter.value === activeFilter)
17
+ ? activeFilter
18
+ : "all";
19
+ const visibleNotifications = resolvedActiveFilter === "all"
20
+ ? notifications
21
+ : notifications.filter((notification) => notification.category === resolvedActiveFilter);
22
+ const filterControls = filters.length > 0 ? (_jsxs("div", { role: "group", "aria-label": filterGroupLabel, "data-slot": "changelog-action-panel-filters", className: "flex flex-wrap items-center gap-[var(--space-1)] pt-[var(--space-2)]", children: [_jsx(FilterToggle, { selected: resolvedActiveFilter === "all", "aria-pressed": resolvedActiveFilter === "all", onClick: () => setActiveFilter("all"), children: allFilterLabel }), filters.map((filter) => (_jsx(FilterToggle, { selected: resolvedActiveFilter === filter.value, "aria-pressed": resolvedActiveFilter === filter.value, onClick: () => setActiveFilter(filter.value), children: filter.label }, filter.value)))] })) : null;
23
+ return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", title: title, subtitle: subtitle, headerChildren: headerChildren || filterControls ? (_jsxs(_Fragment, { children: [headerChildren, filterControls] })) : undefined, ...panelProps, children: _jsx(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", children: isLoading ? (_jsx("div", { "data-slot": "changelog-action-panel-loading", className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "md", label: loadingLabel }) })) : visibleNotifications.length === 0 ? (_jsx(DashboardActionPanelEmptyState, { icon: History, message: notifications.length === 0
24
+ ? emptyMessage
25
+ : filteredEmptyMessage, description: notifications.length === 0
26
+ ? emptyDescription
27
+ : filteredEmptyDescription })) : (_jsx(NotificationList, { density: "compact", notifications: visibleNotifications, descriptionLines: 3, emptyState: null, variant: "bordered" })) }) }));
13
28
  }
14
29
  export { ChangelogActionPanel };
@@ -48,15 +48,21 @@ export interface ChecklistQuestionProps {
48
48
  /** Count and limit characters instead of words when supplied. */
49
49
  remarksMaxLength?: number;
50
50
  remarksRequired?: boolean;
51
+ remarksDescription?: React.ReactNode;
52
+ remarksOpen?: boolean;
53
+ onRemarksOpenChange?: (open: boolean) => void;
51
54
  evidence?: React.ReactNode;
52
55
  evidenceCount?: number;
53
56
  }
54
- export declare function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit, remarksMaxLength, remarksRequired, evidence, evidenceCount }: ChecklistQuestionProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit, remarksMaxLength, remarksRequired, remarksDescription, remarksOpen, onRemarksOpenChange, evidence, evidenceCount }: ChecklistQuestionProps): import("react/jsx-runtime").JSX.Element;
55
58
  export interface ChecklistAttachmentsProps {
56
59
  items: {
57
60
  key: string;
58
61
  name: string;
59
62
  url: string;
63
+ type?: string;
64
+ size?: number | string;
65
+ sizeBytes?: number;
60
66
  }[];
61
67
  onRemove?: (key: string) => void;
62
68
  }
package/dist/checklist.js CHANGED
@@ -2,8 +2,9 @@
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 { Camera, ChevronDown, ClipboardList, LoaderCircle, MessageSquareText, Trash2 } from "lucide-react";
5
+ import { Camera, ChevronDown, ClipboardList, LoaderCircle, MessageSquareText } from "lucide-react";
6
6
  import { Button } from "./button";
7
+ import { FileUploadStatusRow } from "./file-upload-status-row";
7
8
  import { Select } from "./select";
8
9
  import { Textarea } from "./textarea";
9
10
  import { Tooltip } from "./tooltip";
@@ -16,18 +17,26 @@ export function ChecklistSection({ title, progress, action, children, defaultOpe
16
17
  }
17
18
  export function ChecklistSelection({ title, description, typeOptions, subtypeOptions, type, subtype, onTypeChange, onSubtypeChange, disabled, loading, action }) {
18
19
  const Icon = loading ? LoaderCircle : ClipboardList;
19
- return _jsxs("div", { "data-slot": "checklist-selection", "aria-busy": loading, className: "flex flex-col items-center gap-4 rounded-lg bg-muted/30 p-6 text-center", children: [_jsx(Icon, { "aria-hidden": "true", className: cn("size-8", loading && "animate-spin") }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), _jsxs("div", { className: "grid w-full max-w-md grid-cols-1 gap-[var(--layout-page-control-gap)] sm:grid-cols-2", children: [_jsx(Select, { label: "Type", hideLabel: true, placeholder: "Type", options: typeOptions, value: type, onValueChange: onTypeChange, disabled: disabled || loading }), _jsx(Select, { label: "Sub-Type", hideLabel: true, placeholder: "Sub-Type", options: subtypeOptions, value: subtype, onValueChange: onSubtypeChange, disabled: disabled || loading || !type })] }), action] });
20
+ return _jsxs("div", { "data-slot": "checklist-selection", "aria-busy": loading, className: "flex flex-col items-center gap-4 rounded-lg bg-muted/30 p-6 text-center", children: [_jsx(Icon, { "aria-hidden": "true", className: cn("size-8", loading && "animate-spin") }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), _jsxs("div", { className: "grid w-full max-w-md grid-cols-1 gap-[var(--space-2)] sm:grid-cols-2", children: [_jsx(Select, { label: "Type", hideLabel: true, placeholder: "Type", options: typeOptions, value: type, onValueChange: onTypeChange, disabled: disabled || loading }), _jsx(Select, { label: "Sub-Type", hideLabel: true, placeholder: "Sub-Type", options: subtypeOptions, value: subtype, onValueChange: onSubtypeChange, disabled: disabled || loading || !type })] }), action] });
20
21
  }
21
- export function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit = 500, remarksMaxLength, remarksRequired = false, evidence, evidenceCount = 0 }) {
22
+ export function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit = 500, remarksMaxLength, remarksRequired = false, remarksDescription, remarksOpen, onRemarksOpenChange, evidence, evidenceCount = 0 }) {
23
+ var _a;
22
24
  const name = React.useId();
23
- const [showRemarks, setShowRemarks] = React.useState(false);
24
- const [showEvidence, setShowEvidence] = React.useState(false);
25
+ const [internalRemarksOpen, setInternalRemarksOpen] = React.useState(null);
26
+ const [evidenceOpen, setEvidenceOpen] = React.useState(null);
27
+ const showRemarks = (_a = remarksOpen !== null && remarksOpen !== void 0 ? remarksOpen : internalRemarksOpen) !== null && _a !== void 0 ? _a : Boolean(remarks.trim());
28
+ const setRemarksOpen = (open) => {
29
+ if (remarksOpen === undefined)
30
+ setInternalRemarksOpen(open);
31
+ onRemarksOpenChange === null || onRemarksOpenChange === void 0 ? void 0 : onRemarksOpenChange(open);
32
+ };
33
+ const showEvidence = evidenceOpen !== null && evidenceOpen !== void 0 ? evidenceOpen : evidenceCount > 0;
25
34
  const remarksVisible = disabled ? Boolean(remarks.trim()) : showRemarks;
26
35
  const evidenceVisible = disabled ? evidenceCount > 0 : showEvidence;
27
36
  const remarksToggleLabel = showRemarks ? "Hide remarks" : "Show remarks";
28
37
  const evidenceToggleLabel = showEvidence ? "Hide photos" : "Show photos";
29
- return _jsxs("div", { "data-slot": "checklist-question", className: "space-y-4 rounded-xl border border-border bg-card p-4 shadow-xs", children: [_jsx("p", { id: `${name}-label`, className: "text-sm font-medium", children: label }), _jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx("fieldset", { disabled: disabled, "aria-labelledby": `${name}-label`, className: cn("flex min-w-0 border-0 p-0", disabled && "opacity-50"), children: options.map((option, index) => _jsxs("label", { className: cn("relative cursor-pointer border border-input px-2.5 py-2 text-sm font-medium focus-within:z-10 focus-within:ring-2 focus-within:ring-ring", index > 0 && "-ml-px", index === 0 && "rounded-l-md", index === options.length - 1 && "rounded-r-md", disabled && "cursor-not-allowed", value === option.value ? option.tone === "negative" ? "bg-destructive/20" : option.tone === "positive" ? "bg-primary/20" : "bg-muted" : "bg-background"), children: [_jsx("input", { className: "absolute inset-0 size-full cursor-inherit opacity-0", type: "radio", name: name, value: option.value, checked: value === option.value, onChange: () => onValueChange(option.value) }), option.label] }, option.value)) }), !disabled ? _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tooltip, { content: remarksToggleLabel, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": remarksToggleLabel, "aria-expanded": showRemarks, "aria-controls": `${name}-remarks`, onClick: () => setShowRemarks((open) => !open), children: _jsx(MessageSquareText, { "aria-hidden": "true", size: 16 }) }) }), evidence ? _jsx(Tooltip, { content: `${evidenceToggleLabel}${evidenceCount > 0 ? ` (${evidenceCount})` : ""}`, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": evidenceToggleLabel, "aria-expanded": showEvidence, "aria-controls": `${name}-evidence`, onClick: () => setShowEvidence((open) => !open), children: _jsx(Camera, { "aria-hidden": "true", size: 16 }) }) }) : null] }) : null] }), evidenceVisible && evidence ? _jsx("div", { id: `${name}-evidence`, children: evidence }) : null, remarksVisible ? _jsx("div", { id: `${name}-remarks`, children: disabled ? _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Remarks" }), _jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: remarks })] }) : _jsx(Textarea, { label: "Remarks", hideLabel: true, placeholder: "Type...", value: remarks, "aria-required": remarksRequired, rows: 5, onChange: (event) => onRemarksChange(event.target.value), maxWords: remarksMaxLength === undefined ? remarksLimit : undefined, maxLength: remarksMaxLength, showCount: remarksMaxLength !== undefined }) }) : null] });
38
+ return _jsxs("div", { "data-slot": "checklist-question", className: "space-y-4 rounded-xl border border-border bg-card p-4 shadow-xs", children: [_jsx("p", { id: `${name}-label`, className: "text-sm font-medium", children: label }), _jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx("fieldset", { disabled: disabled, "aria-labelledby": `${name}-label`, className: cn("flex min-w-0 border-0 p-0", disabled && "opacity-50"), children: options.map((option, index) => _jsxs("label", { className: cn("relative cursor-pointer border border-input px-2.5 py-2 text-sm font-medium focus-within:z-10 focus-within:ring-2 focus-within:ring-ring", index > 0 && "-ml-px", index === 0 && "rounded-l-md", index === options.length - 1 && "rounded-r-md", disabled && "cursor-not-allowed", value === option.value ? option.tone === "negative" ? "bg-destructive/20" : option.tone === "positive" ? "bg-primary/20" : "bg-muted" : "bg-background"), children: [_jsx("input", { className: "absolute inset-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed", type: "radio", name: name, value: option.value, checked: value === option.value, onChange: () => onValueChange(option.value) }), option.label] }, option.value)) }), !disabled ? _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tooltip, { content: remarksToggleLabel, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": remarksToggleLabel, "aria-expanded": showRemarks, "aria-controls": `${name}-remarks`, onClick: () => setRemarksOpen(!showRemarks), children: _jsx(MessageSquareText, { "aria-hidden": "true", size: 16 }) }) }), evidence ? _jsx(Tooltip, { content: `${evidenceToggleLabel}${evidenceCount > 0 ? ` (${evidenceCount})` : ""}`, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": evidenceToggleLabel, "aria-expanded": showEvidence, "aria-controls": `${name}-evidence`, onClick: () => setEvidenceOpen(!showEvidence), children: _jsx(Camera, { "aria-hidden": "true", size: 16 }) }) }) : null] }) : null] }), evidenceVisible && evidence ? _jsx("div", { id: `${name}-evidence`, children: evidence }) : null, remarksVisible ? _jsx("div", { id: `${name}-remarks`, children: disabled ? _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Remarks" }), _jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: remarks })] }) : _jsx(Textarea, { label: "Remarks", description: remarksDescription, placeholder: "Type...", value: remarks, "aria-required": remarksRequired, rows: 5, onChange: (event) => { setRemarksOpen(true); onRemarksChange(event.target.value); }, maxWords: remarksMaxLength === undefined ? remarksLimit : undefined, maxLength: remarksMaxLength, showCount: remarksMaxLength !== undefined }) }) : null] });
30
39
  }
31
40
  export function ChecklistAttachments({ items, onRemove }) {
32
- return _jsx("div", { className: "flex flex-wrap gap-2", children: items.map((item) => _jsxs("div", { className: "relative", children: [_jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: _jsx("img", { src: item.url, alt: item.name, className: "size-20 rounded-md border border-border object-cover" }) }), onRemove ? _jsx(Button, { variant: "outline", size: "compactIcon", "aria-label": `Remove ${item.name}`, onClick: () => onRemove(item.key), children: _jsx(Trash2, { "aria-hidden": "true", size: 16 }) }) : null] }, item.key)) });
41
+ return _jsx("ul", { className: "space-y-2", children: items.map((item) => _jsx(FileUploadStatusRow, { file: { name: item.name, type: item.type, size: item.sizeBytes || item.size, status: "complete" }, href: item.url, onRemove: onRemove ? () => onRemove(item.key) : undefined }, item.key)) });
33
42
  }
package/dist/dialog.d.ts CHANGED
@@ -9,10 +9,10 @@ export interface DialogAction extends Omit<ButtonProps, "children"> {
9
9
  }
10
10
  type DialogRenderable = React.ReactNode | ((helpers: DialogRenderHelpers) => React.ReactNode);
11
11
  export interface DialogProps {
12
- variant?: "default" | "confirmation" | "routingConfirmation";
12
+ variant?: "default" | "confirmation";
13
13
  /** Standard dialogs are 512px wide; larger sizes are for complex editors. */
14
14
  size?: "standard" | "large" | "extraLarge" | "full";
15
- /** Optional minimum height for dialogs whose content changes between steps. */
15
+ /** Minimum height; defaults to 11.25rem (180px at the default root font size). */
16
16
  minHeight?: React.CSSProperties["minHeight"];
17
17
  open?: boolean;
18
18
  defaultOpen?: boolean;
package/dist/dialog.js CHANGED
@@ -32,7 +32,7 @@ function renderDialogAction({ action, close, defaultVariant, }) {
32
32
  }
33
33
  }, children: label }));
34
34
  }
35
- function Dialog({ variant = "default", size = "standard", minHeight, open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
35
+ function Dialog({ variant = "default", size = "standard", minHeight = "11.25rem", open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
36
36
  const [isOpen, setIsOpen] = useControllableOpen({
37
37
  open,
38
38
  defaultOpen,
@@ -45,15 +45,12 @@ function Dialog({ variant = "default", size = "standard", minHeight, open, defau
45
45
  const customFooter = resolveDialogRenderable(footer, helpers);
46
46
  const hasFooter = Boolean(customFooter || primaryAction || secondaryAction);
47
47
  const isCompactConfirmation = variant === "confirmation";
48
- const isRoutingConfirmation = variant === "routingConfirmation";
49
48
  return (_jsxs(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, modal: modal, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogSurface, { ...(!description ? { "aria-describedby": undefined } : {}), style: minHeight !== undefined ? { minHeight } : undefined, className: cn(isCompactConfirmation &&
50
- "gap-0 overflow-hidden rounded-xl p-0", isRoutingConfirmation &&
51
- "gap-0 rounded-overlay p-[var(--space-8)]", minHeight !== undefined && "flex flex-col", "w-[calc(100vw-32px)]", { standard: "max-w-[512px]", large: "max-w-3xl", extraLarge: "max-w-5xl", full: "max-w-[96rem]" }[size], contentClassName), hideCloseButton: hideCloseButton || isCompactConfirmation || isRoutingConfirmation, onPointerDownOutside: (event) => {
49
+ "gap-0 overflow-hidden rounded-xl p-0", minHeight !== undefined && "flex flex-col", "w-[calc(100vw-32px)]", { standard: "max-w-[512px]", large: "max-w-3xl", extraLarge: "max-w-5xl", full: "max-w-[96rem]" }[size], contentClassName), hideCloseButton: hideCloseButton || isCompactConfirmation, onPointerDownOutside: (event) => {
52
50
  if (!closeOnOutsideClick) {
53
51
  event.preventDefault();
54
52
  }
55
- }, children: [_jsxs("div", { className: cn(isCompactConfirmation && "flex items-start gap-4 p-4"), children: [isCompactConfirmation ? (_jsx("div", { className: "rounded-lg bg-muted p-2", children: _jsx(CircleAlert, { "aria-hidden": "true", className: "size-6" }) })) : null, _jsxs(DialogHeader, { className: cn(isRoutingConfirmation && "gap-3 space-y-0"), children: [_jsx(DialogTitle, { className: cn(isRoutingConfirmation && "text-2xl leading-8"), children: title }), description ? (_jsx(DialogDescription, { className: cn(isRoutingConfirmation &&
56
- "text-base leading-6 text-foreground"), children: description })) : null] })] }), body ? (_jsx("div", { className: cn("grid gap-4", minHeight !== undefined && "flex-1", isRoutingConfirmation && "mt-3 gap-[var(--space-8)]", bodyClassName), children: body })) : null, hasFooter ? (_jsx(DialogFooter, { className: cn(isCompactConfirmation && "border-t bg-muted/30 p-4", isRoutingConfirmation && "mt-[var(--space-8)] gap-3", minHeight !== undefined && "mt-auto"), children: customFooter !== null && customFooter !== void 0 ? customFooter : (_jsxs(_Fragment, { children: [secondaryAction
53
+ }, children: [_jsxs("div", { className: cn(isCompactConfirmation && "flex items-start gap-4 p-4"), children: [isCompactConfirmation ? (_jsx("div", { className: "rounded-lg bg-muted p-2", children: _jsx(CircleAlert, { "aria-hidden": "true", className: "size-6" }) })) : null, _jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), description ? (_jsx(DialogDescription, { children: description })) : null] })] }), body ? (_jsx("div", { className: cn("grid gap-4", minHeight !== undefined && "flex-1", bodyClassName), children: body })) : null, hasFooter ? (_jsx(DialogFooter, { className: cn(isCompactConfirmation && "border-t bg-muted/30 p-4", minHeight !== undefined && "mt-auto"), children: customFooter !== null && customFooter !== void 0 ? customFooter : (_jsxs(_Fragment, { children: [secondaryAction
57
54
  ? renderDialogAction({
58
55
  action: secondaryAction,
59
56
  close: helpers.close,
@@ -145,7 +145,7 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
145
145
  insertRow: (nextRow, index = rowIndex + 1) => handleInsertRow(nextRow, index),
146
146
  removeRow: () => handleDeleteRow(rowIndex),
147
147
  })
148
- : null, isEditing ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "text-muted-foreground hover:bg-destructive/10 hover:text-destructive focus-visible:text-destructive", onClick: () => handleDeleteRow(rowIndex), "aria-label": `Delete row ${rowIndex + 1}`, children: _jsx(TrashIcon, { size: 16 }) })) : null] }) })) : null] }, `row-${rowIndex}`)))) })] }) }), isEditing ? (_jsx(TableAddRow, { onClick: handleAddRow, children: addRowLabel })) : null] }));
148
+ : null, isEditing ? (_jsx(Button, { type: "button", variant: "destructiveGhost", size: "compactIcon", onClick: () => handleDeleteRow(rowIndex), "aria-label": `Delete row ${rowIndex + 1}`, children: _jsx(TrashIcon, { size: 16 }) })) : null] }) })) : null] }, `row-${rowIndex}`)))) })] }) }), isEditing ? (_jsx(TableAddRow, { onClick: handleAddRow, children: addRowLabel })) : null] }));
149
149
  }
150
150
  EditableTable.displayName = "EditableTable";
151
151
  export { EditableTable };
@@ -0,0 +1,15 @@
1
+ export interface UploadedFile {
2
+ id: string;
3
+ name: string;
4
+ type?: string;
5
+ size?: number | string;
6
+ href?: string;
7
+ }
8
+ export interface FileUploadListProps {
9
+ files: UploadedFile[];
10
+ onRemove?: (id: string) => void;
11
+ disabled?: boolean;
12
+ label?: string;
13
+ }
14
+ /** Saved files, using the same rows as the uploader. */
15
+ export declare function FileUploadList({ files, onRemove, disabled, label }: FileUploadListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { FileUploadStatusRow } from "./file-upload-status-row";
4
+ /** Saved files, using the same rows as the uploader. */
5
+ export function FileUploadList({ files, onRemove, disabled, label = "Uploaded files" }) {
6
+ return _jsx("ul", { "aria-label": label, className: "space-y-2", children: files.map(file => _jsx(FileUploadStatusRow, { file: { ...file, status: "complete" }, href: file.href, disabled: disabled, onRemove: onRemove ? () => onRemove(file.id) : undefined }, file.id)) });
7
+ }
@@ -1,5 +1,5 @@
1
1
  import type { FileUploadStatus } from "./use-file-upload";
2
- export declare function FileUploadStatusRow({ file, onCancel, onRemove, disabled, }: {
2
+ export declare function FileUploadStatusRow({ file, href, onCancel, onRemove, disabled, }: {
3
3
  file: {
4
4
  name: string;
5
5
  type?: string;
@@ -8,6 +8,8 @@ export declare function FileUploadStatusRow({ file, onCancel, onRemove, disabled
8
8
  progress?: number;
9
9
  error?: string;
10
10
  };
11
+ /** Optional destination for an uploaded file name. */
12
+ href?: string;
11
13
  onCancel?: () => void;
12
14
  onRemove?: () => void;
13
15
  disabled?: boolean;
@@ -27,7 +27,7 @@ function getStatusIcon(status, type) {
27
27
  const Icon = (type === null || type === void 0 ? void 0 : type.startsWith("image/")) ? ImageIcon : type === "application/pdf" ? FileText : File;
28
28
  return (_jsx(Icon, { "aria-hidden": "true", className: "size-5 text-muted-foreground" }));
29
29
  }
30
- export function FileUploadStatusRow({ file, onCancel, onRemove, disabled = false, }) {
30
+ export function FileUploadStatusRow({ file, href, onCancel, onRemove, disabled = false, }) {
31
31
  var _a;
32
32
  const isUploading = file.status === "uploading";
33
33
  const isPending = file.status === "pending";
@@ -37,9 +37,9 @@ export function FileUploadStatusRow({ file, onCancel, onRemove, disabled = false
37
37
  const canCancel = (isUploading || isPending) && onCancel;
38
38
  const canRemove = !isUploading && onRemove;
39
39
  const action = canCancel ? "cancel" : canRemove ? "remove" : null;
40
- return (_jsxs("li", { className: cn("rounded-control border bg-background px-2.5 py-2", isError && "border-destructive/80 bg-destructive/5"), children: [_jsxs("div", { className: cn("grid min-h-7 items-start gap-x-2", action
40
+ return (_jsxs("li", { className: cn("rounded-control border bg-background px-2.5 py-2", isError && "border-destructive/80 bg-destructive/5"), children: [_jsxs("div", { className: cn("grid min-h-7 items-center gap-x-2", action
41
41
  ? "grid-cols-[1.5rem_minmax(0,1fr)_auto_1.75rem]"
42
- : "grid-cols-[1.5rem_minmax(0,1fr)_auto]"), children: [_jsx("span", { className: "flex h-7 w-6 shrink-0 items-center justify-center", children: getStatusIcon(file.status, file.type) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1 py-1", children: [_jsx(Typography, { as: "p", variant: "label", className: cn("truncate", isComplete && "text-[hsl(var(--status-success-foreground))]"), title: file.name, children: file.name }), isError ? (_jsx(Typography, { as: "p", variant: "caption", className: "break-words text-xs leading-4 text-destructive", children: (_a = file.error) !== null && _a !== void 0 ? _a : "Upload failed" })) : null] }), sizeLabel ? (_jsx(Typography, { as: "span", variant: "bodySm", className: "col-start-3 mt-1.5 whitespace-nowrap text-xs leading-4 text-muted-foreground", children: sizeLabel })) : null, action ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "col-start-4 h-7 w-7 shrink-0", disabled: disabled, onClick: (event) => {
42
+ : "grid-cols-[1.5rem_minmax(0,1fr)_auto]"), children: [_jsx("span", { className: "flex h-7 w-6 shrink-0 items-center justify-center", children: getStatusIcon(file.status, file.type) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1 py-1", children: [href ? (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: "min-w-0 truncate leading-5 underline-offset-4 hover:underline", children: _jsx(Typography, { as: "span", variant: "label", className: cn("truncate", isComplete && "text-[hsl(var(--status-success-foreground))]"), title: file.name, children: file.name }) })) : (_jsx(Typography, { as: "p", variant: "label", className: cn("truncate", isComplete && "text-[hsl(var(--status-success-foreground))]"), title: file.name, children: file.name })), isError ? (_jsx(Typography, { as: "p", variant: "caption", className: "break-words text-xs leading-4 text-destructive", children: (_a = file.error) !== null && _a !== void 0 ? _a : "Upload failed" })) : null] }), sizeLabel ? (_jsx(Typography, { as: "span", variant: "bodySm", className: "col-start-3 whitespace-nowrap text-xs leading-4 text-muted-foreground", children: sizeLabel })) : null, action ? (_jsx(Button, { type: "button", variant: action === "remove" ? "destructiveGhost" : "ghost", size: "compactIcon", className: "col-start-4 h-7 w-7 shrink-0", disabled: disabled, onClick: (event) => {
43
43
  event.stopPropagation();
44
44
  if (action === "cancel") {
45
45
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
@@ -10,7 +10,7 @@ const filterToggleVariants = cva("inline-flex shrink-0 cursor-pointer select-non
10
10
  },
11
11
  selected: {
12
12
  true: "border-transparent bg-highlight text-highlight-foreground hover:bg-highlight/80 focus-visible:bg-highlight/80",
13
- false: "border-transparent bg-muted text-muted-foreground hover:bg-muted/80",
13
+ false: "border-transparent bg-muted text-instructional hover:bg-muted/80",
14
14
  },
15
15
  },
16
16
  defaultVariants: {
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export type { AlertStackProps } from "./alert-stack";
11
11
  export { ActivityCard, ActivityCardActor, ActivityCardContent, } from "./activity-card";
12
12
  export type { ActivityCardActorProps, ActivityCardContentProps, ActivityCardDensity, ActivityCardIconTone, ActivityCardProps, } from "./activity-card";
13
13
  export { ChangelogActionPanel } from "./changelog-action-panel";
14
- export type { ChangelogActionPanelProps } from "./changelog-action-panel";
14
+ export type { ChangelogActionPanelFilter, ChangelogActionPanelNotification, ChangelogActionPanelProps, } from "./changelog-action-panel";
15
15
  export { Badge, badgeVariants } from "./badge";
16
16
  export { Card, CardGrid, CardSplitGrid, CardList, CardListItem, } from "./card";
17
17
  export type { CardGridProps, CardListItemProps, CardListProps, CardProps, CardSplitGridProps, } from "./card";
@@ -22,8 +22,6 @@ export { MessageActionPanel } from "./message-action-panel";
22
22
  export type { MessageActionPanelProps } from "./message-action-panel";
23
23
  export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
24
24
  export type { NotificationAction, NotificationFilter, NotificationIcon, NotificationItemData, NotificationItemProps, NotificationKind, NotificationListProps, NotificationPopoverProps, NotificationTimestamp, NotificationTriggerProps, NotificationTone, FormatNotificationTimestampOptions, } from "./notification";
25
- export { RoutingTimeline } from "./routing-timeline";
26
- export type { RoutingTimelineItem, RoutingTimelineItemKind, RoutingTimelineProps, } from "./routing-timeline";
27
25
  export { RoutingActionPanel } from "./routing-action-panel";
28
26
  export type { RoutingActionPanelProps } from "./routing-action-panel";
29
27
  export { RichTextEditor, formatFileSize, replaceTemplateVariables, sanitizeRichTextHtml, } from "./rich-text-editor";
@@ -174,3 +172,7 @@ export type { ChecklistAttachmentsProps, ChecklistPanelProps, ChecklistSectionPr
174
172
  export { TableAddRow, type TableAddRowProps } from "./table-add-row";
175
173
  export { PopoverList, type PopoverListProps } from "./popover";
176
174
  export { StatusBanner, type StatusBannerProps } from "./status-banner";
175
+ export { FileUploadList } from "./file-upload-list";
176
+ export type { FileUploadListProps, UploadedFile } from "./file-upload-list";
177
+ export { AssigneePopover } from "./assignee-popover";
178
+ export type { AssigneePopoverProps, AssigneeOption } from "./assignee-popover";
package/dist/index.js CHANGED
@@ -11,7 +11,6 @@ export { Card, CardGrid, CardSplitGrid, CardList, CardListItem, } from "./card";
11
11
  export { MessageAttachmentRow, MessageItem, MessageNewDivider, MessageThread, formatMessageCardTimestamp, formatMessageDate, } from "./message-item";
12
12
  export { MessageActionPanel } from "./message-action-panel";
13
13
  export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
14
- export { RoutingTimeline } from "./routing-timeline";
15
14
  export { RoutingActionPanel } from "./routing-action-panel";
16
15
  export { RichTextEditor, formatFileSize, replaceTemplateVariables, sanitizeRichTextHtml, } from "./rich-text-editor";
17
16
  export { Calendar, CalendarDayButton } from "./calendar";
@@ -93,3 +92,5 @@ export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelect
93
92
  export { TableAddRow } from "./table-add-row";
94
93
  export { PopoverList } from "./popover";
95
94
  export { StatusBanner } from "./status-banner";
95
+ export { FileUploadList } from "./file-upload-list";
96
+ export { AssigneePopover } from "./assignee-popover";
@@ -7,11 +7,13 @@ export interface LabelValueGridProps extends React.HTMLAttributes<HTMLDivElement
7
7
  items: LabelValueGridItem[];
8
8
  columns?: 1 | 2 | 3 | 4 | 5 | 6;
9
9
  density?: "default" | "compact";
10
+ /** Controls whether every grid row shares one height or sizes to its content. */
11
+ rowSizing?: "equal" | "natural";
10
12
  itemClassName?: string;
11
13
  minColumnWidth?: number;
12
14
  hideIfEmpty?: boolean;
13
15
  loading?: boolean;
14
16
  loadingItems?: number;
15
17
  }
16
- declare function LabelValueGrid({ items, columns, density, className, itemClassName, minColumnWidth, hideIfEmpty, loading, loadingItems, style, ...props }: LabelValueGridProps): import("react/jsx-runtime").JSX.Element;
18
+ declare function LabelValueGrid({ items, columns, density, rowSizing, className, itemClassName, minColumnWidth, hideIfEmpty, loading, loadingItems, style, ...props }: LabelValueGridProps): import("react/jsx-runtime").JSX.Element;
17
19
  export { LabelValueGrid };
@@ -11,7 +11,7 @@ function getAvailableWidth(element) {
11
11
  var _a, _b, _c, _d, _e, _f;
12
12
  return Math.max(element.getBoundingClientRect().width, element.clientWidth, element.offsetWidth, (_b = (_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) !== null && _b !== void 0 ? _b : 0, (_d = (_c = element.parentElement) === null || _c === void 0 ? void 0 : _c.clientWidth) !== null && _d !== void 0 ? _d : 0, (_f = (_e = element.parentElement) === null || _e === void 0 ? void 0 : _e.offsetWidth) !== null && _f !== void 0 ? _f : 0);
13
13
  }
14
- function LabelValueGrid({ items, columns = 4, density = "default", className, itemClassName, minColumnWidth, hideIfEmpty = false, loading = false, loadingItems = 4, style, ...props }) {
14
+ function LabelValueGrid({ items, columns = 4, density = "default", rowSizing = "equal", className, itemClassName, minColumnWidth, hideIfEmpty = false, loading = false, loadingItems = 4, style, ...props }) {
15
15
  const resolvedMinColumnWidth = minColumnWidth !== null && minColumnWidth !== void 0 ? minColumnWidth : (density === "compact" ? 140 : 192);
16
16
  const resolvedItems = React.useMemo(() => {
17
17
  if (!loading)
@@ -66,7 +66,7 @@ function LabelValueGrid({ items, columns = 4, density = "default", className, it
66
66
  }, [columns, density, resolvedMinColumnWidth]);
67
67
  return (_jsx("div", { ref: containerRef, className: cn("grid items-start", density === "compact"
68
68
  ? "auto-rows-auto gap-x-4 gap-y-3"
69
- : resolvedColumns === 1
69
+ : resolvedColumns === 1 || rowSizing === "natural"
70
70
  ? "auto-rows-auto gap-x-6 gap-y-6"
71
71
  : "auto-rows-fr gap-x-6 gap-y-6", className), style: {
72
72
  ...style,
package/dist/layout.js CHANGED
@@ -406,7 +406,7 @@ function DashboardRightPanelHeader({ className, spacing = "default", title, subt
406
406
  return (_jsx("div", { className: cn(spacing === "comfortable"
407
407
  ? "shrink-0 p-[16px]"
408
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"
409
+ ? "text-instructional"
410
410
  : undefined, children: subtitle })) : null, children] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }) }));
411
411
  }
412
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] })));
@@ -424,11 +424,11 @@ function DashboardRightPanelBody({ className, contentClassName, footer, footerVa
424
424
  const topInsetClassName = topInset === "none" ? "pt-0" : undefined;
425
425
  if (footer) {
426
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 })] }));
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", tabIndex: 0, className: cn("scrollbar-none h-full min-h-0 overflow-y-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring", 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
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 })] }));
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", tabIndex: 0, className: cn("scrollbar-none h-full min-h-0 space-y-[var(--layout-right-panel-gap)] overflow-y-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring", 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 })] }));
430
430
  }
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 })] }));
431
+ return (_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scrollRef, "data-slot": "dashboard-right-panel-scroll-area", tabIndex: 0, className: cn("scrollbar-none h-full min-h-0 space-y-[var(--layout-right-panel-gap)] overflow-y-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring", spacingClassName, topInsetClassName, className), ...props, children: children }), _jsx(ScrollHint, { visible: canScrollDown })] }));
432
432
  }
433
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 }) {
434
434
  const { isCollapsed, toggle, collapse } = useSidebar();
@@ -25,6 +25,10 @@ export interface NotificationItemData {
25
25
  id: string;
26
26
  title: React.ReactNode;
27
27
  description?: React.ReactNode;
28
+ /** Optional secondary content rendered separately from the event summary. */
29
+ detail?: React.ReactNode;
30
+ /** Label for secondary content, for example `Remarks`. */
31
+ detailLabel?: React.ReactNode;
28
32
  /** Person or system responsible for the event. */
29
33
  actor?: React.ReactNode;
30
34
  /** Optional status or category displayed in an activity-card header. */
@@ -128,6 +128,9 @@ function NotificationItem({ notification, density = "default", variant = "row",
128
128
  const hasActions = actions.length > 0;
129
129
  const isInteractive = Boolean(!hasActions && (notification.href || onClick || onSelect));
130
130
  const hasHoverSurface = isInteractive || hasActions;
131
+ const hasDetail = notification.detail !== undefined &&
132
+ notification.detail !== null &&
133
+ notification.detail !== "";
131
134
  const timestamp = notification.timestamp;
132
135
  const now = useNotificationTimestampNow(timestamp);
133
136
  const formattedTimestamp = timestamp !== undefined
@@ -147,8 +150,12 @@ function NotificationItem({ notification, density = "default", variant = "row",
147
150
  const hasDescription = notification.description !== undefined &&
148
151
  notification.description !== null &&
149
152
  notification.description !== "";
150
- const actorBelongsInHeader = notification.actor !== undefined && !hasDescription && !hasActions;
153
+ const actorBelongsInHeader = notification.actor !== undefined &&
154
+ !hasDescription &&
155
+ !hasDetail &&
156
+ !hasActions;
151
157
  const hasCardContent = hasDescription ||
158
+ hasDetail ||
152
159
  (notification.actor !== undefined && !actorBelongsInHeader) ||
153
160
  hasActions;
154
161
  const activityElement = notification.href && !hasActions
@@ -159,7 +166,7 @@ function NotificationItem({ notification, density = "default", variant = "row",
159
166
  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
167
  const cardTrailing = notification.badge || unreadIndicator ? (_jsxs("span", { className: "flex items-center gap-[var(--space-2)]", children: [notification.badge, unreadIndicator] })) : null;
161
168
  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) => {
169
+ "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-instructional", descriptionLineClasses[descriptionLines]), children: notification.description })) : null, hasDetail ? (_jsxs("div", { "data-slot": "notification-item-detail", className: "text-left", children: [notification.detailLabel !== undefined ? (_jsx(Typography, { as: "div", variant: "caption", className: "font-semibold text-foreground", children: notification.detailLabel })) : null, _jsx(Typography, { as: "div", variant: "bodySm", className: "whitespace-pre-wrap text-instructional [overflow-wrap:anywhere]", children: notification.detail })] })) : 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
170
  var _a, _b;
164
171
  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
172
  var _a;
@@ -168,7 +175,7 @@ function NotificationItem({ notification, density = "default", variant = "row",
168
175
  }, children: action.label }, (_b = action.id) !== null && _b !== void 0 ? _b : index));
169
176
  }) })) : null] })) : null }));
170
177
  }
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) => {
178
+ 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-instructional", 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-instructional", descriptionLineClasses[descriptionLines]), children: notification.description })) : null, hasDetail ? (_jsxs("span", { "data-slot": "notification-item-detail", className: "mt-2 block text-left", children: [notification.detailLabel !== undefined ? (_jsx(Typography, { as: "span", variant: "caption", className: "block font-semibold text-foreground", children: notification.detailLabel })) : null, _jsx(Typography, { as: "span", variant: "caption", className: "block whitespace-pre-wrap text-instructional [overflow-wrap:anywhere]", children: notification.detail })] })) : 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) => {
172
179
  var _a, _b;
173
180
  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) => {
174
181
  var _a;
@@ -1,13 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type DashboardActionPanelProps } from "./layout";
3
- import { type RoutingTimelineItem } from "./routing-timeline";
4
3
  import { type SelectOption } from "./select";
5
4
  export interface RoutingActionPanelProps extends Omit<DashboardActionPanelProps, "children"> {
6
- items: RoutingTimelineItem[];
7
- isLoading?: boolean;
8
- loadingLabel?: string;
9
- emptyMessage?: React.ReactNode;
10
- emptyDescription?: React.ReactNode;
11
5
  routeOptions: SelectOption[];
12
6
  routeValue?: string;
13
7
  routePlaceholder?: string;
@@ -16,7 +10,7 @@ export interface RoutingActionPanelProps extends Omit<DashboardActionPanelProps,
16
10
  onRouteValueChange?: (value: string) => void;
17
11
  /** Workflow-specific fields rendered before the route target. */
18
12
  supplementaryFields?: React.ReactNode;
19
- /** Read-only review context in the scrollable body, above routing activity. */
13
+ /** Read-only review context in the scrollable body, above the routing form. */
20
14
  context?: React.ReactNode;
21
15
  remarks: string;
22
16
  remarksDescription?: React.ReactNode;
@@ -32,9 +26,8 @@ export interface RoutingActionPanelProps extends Omit<DashboardActionPanelProps,
32
26
  onSubmit: () => void;
33
27
  }
34
28
  /**
35
- * Shared routing surface for constrained dashboard popovers and right panels.
36
- * Consumers provide workflow data and actions; the component owns the complete
37
- * visual composition so different records render routing consistently.
29
+ * Shared action-only routing surface for constrained dashboard popovers and
30
+ * right panels. Historical routing events belong in the change log.
38
31
  */
39
- declare function RoutingActionPanel({ items, isLoading, loadingLabel, emptyMessage, emptyDescription, routeOptions, routeValue, routePlaceholder, routeDescription, routeDisabled, onRouteValueChange, supplementaryFields, context, remarks, remarksDescription, remarksPlaceholder, remarksRequired, remarksRows, validationMessages, onRemarksChange, submitLabel, submitDisabled, submitLoading, onSubmit, ...panelProps }: RoutingActionPanelProps): import("react/jsx-runtime").JSX.Element;
32
+ declare function RoutingActionPanel({ routeOptions, routeValue, routePlaceholder, routeDescription, routeDisabled, onRouteValueChange, supplementaryFields, context, remarks, remarksDescription, remarksPlaceholder, remarksRequired, remarksRows, validationMessages, onRemarksChange, submitLabel, submitDisabled, submitLoading, onSubmit, ...panelProps }: RoutingActionPanelProps): import("react/jsx-runtime").JSX.Element;
40
33
  export { RoutingActionPanel };
@@ -2,20 +2,15 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Route } from "lucide-react";
4
4
  import { Button } from "./button";
5
- import { EmptyState } from "./empty-state";
6
- import { DashboardActionPanel, DashboardActionPanelEmptyState, DashboardRightPanelBody, } from "./layout";
7
- import { RoutingTimeline, } from "./routing-timeline";
5
+ import { DashboardActionPanel, DashboardRightPanelBody, } from "./layout";
8
6
  import { Select } from "./select";
9
- import { Spinner } from "./spinner";
10
7
  import { Textarea } from "./textarea";
11
8
  /**
12
- * Shared routing surface for constrained dashboard popovers and right panels.
13
- * Consumers provide workflow data and actions; the component owns the complete
14
- * visual composition so different records render routing consistently.
9
+ * Shared action-only routing surface for constrained dashboard popovers and
10
+ * right panels. Historical routing events belong in the change log.
15
11
  */
16
- function RoutingActionPanel({ items, isLoading = false, loadingLabel = "Loading routing activity", emptyMessage = "No routing activity yet", emptyDescription = "Workflow routes will appear here.", routeOptions, routeValue, routePlaceholder = "Select Officer Role", routeDescription, routeDisabled = false, onRouteValueChange, supplementaryFields, context, remarks, remarksDescription, remarksPlaceholder = "Type remarks", remarksRequired = false, remarksRows = 2, validationMessages = [], onRemarksChange, submitLabel = "Route", submitDisabled = false, submitLoading = false, onSubmit, ...panelProps }) {
17
- const RoutingEmptyState = context ? EmptyState : DashboardActionPanelEmptyState;
18
- const footer = (_jsxs("div", { "data-slot": "routing-action-panel-form", className: "grid gap-[var(--space-4)]", children: [supplementaryFields, _jsx(Select, { label: "Route To", options: routeOptions, value: routeValue, placeholder: routePlaceholder, disabled: routeDisabled || submitLoading, description: routeDescription, onValueChange: onRouteValueChange }), _jsx(Textarea, { label: "Remarks", value: remarks, onChange: (event) => onRemarksChange(event.target.value), placeholder: remarksPlaceholder, description: remarksDescription, disabled: routeDisabled || submitLoading, required: remarksRequired, rows: remarksRows }), validationMessages.length > 0 ? (_jsxs("details", { className: "text-sm text-muted-foreground", children: [_jsxs("summary", { className: "cursor-pointer font-medium text-foreground", children: [validationMessages.length, " required ", validationMessages.length === 1 ? "item" : "items", " remaining \u2014 view details"] }), _jsx("ul", { className: "mt-2 max-h-40 list-disc space-y-1 overflow-y-auto pl-5", children: validationMessages.map((message, index) => _jsx("li", { children: message }, index)) })] })) : null, _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", size: "default", disabled: routeDisabled || submitDisabled || validationMessages.length > 0, loading: submitLoading, loadingText: submitLabel, onClick: onSubmit, children: [_jsx(Route, { "aria-hidden": "true", className: "size-4" }), submitLabel] }) })] }));
19
- return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", ...panelProps, children: _jsxs(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", footerVariant: "separated", footer: footer, children: [context ? (_jsx("div", { "data-slot": "routing-action-panel-context", className: "mb-[var(--space-4)] grid min-w-0 gap-[var(--space-3)] whitespace-pre-wrap [overflow-wrap:anywhere]", children: context })) : null, isLoading ? (_jsx("div", { "data-slot": "routing-action-panel-loading", className: context ? "flex items-center justify-center py-[var(--space-4)]" : "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "md", label: loadingLabel }) })) : items.length === 0 ? (_jsx(RoutingEmptyState, { icon: Route, message: emptyMessage, description: emptyDescription })) : (_jsx(RoutingTimeline, { density: "compact", items: items }))] }) }));
12
+ function RoutingActionPanel({ routeOptions, routeValue, routePlaceholder = "Select Officer Role", routeDescription, routeDisabled = false, onRouteValueChange, supplementaryFields, context, remarks, remarksDescription, remarksPlaceholder = "Type remarks", remarksRequired = false, remarksRows = 2, validationMessages = [], onRemarksChange, submitLabel = "Route", submitDisabled = false, submitLoading = false, onSubmit, ...panelProps }) {
13
+ const form = (_jsxs("div", { "data-slot": "routing-action-panel-form", className: "grid gap-[var(--space-4)]", children: [supplementaryFields, _jsx(Select, { label: "Route To", options: routeOptions, value: routeValue, placeholder: routePlaceholder, disabled: routeDisabled || submitLoading, description: routeDescription, onValueChange: onRouteValueChange }), _jsx(Textarea, { label: "Remarks", value: remarks, onChange: (event) => onRemarksChange(event.target.value), placeholder: remarksPlaceholder, description: remarksDescription, disabled: routeDisabled || submitLoading, required: remarksRequired, rows: remarksRows }), validationMessages.length > 0 ? (_jsxs("details", { className: "text-sm text-muted-foreground", children: [_jsxs("summary", { className: "cursor-pointer font-medium text-foreground", children: [validationMessages.length, " required ", validationMessages.length === 1 ? "item" : "items", " remaining \u2014 view details"] }), _jsx("ul", { className: "mt-2 max-h-40 list-disc space-y-1 overflow-y-auto pl-5", children: validationMessages.map((message, index) => _jsx("li", { children: message }, index)) })] })) : null, _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", size: "default", disabled: routeDisabled || submitDisabled || validationMessages.length > 0, loading: submitLoading, loadingText: submitLabel, onClick: onSubmit, children: [_jsx(Route, { "aria-hidden": "true", className: "size-4" }), submitLabel] }) })] }));
14
+ return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", ...panelProps, children: _jsxs(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", children: [context ? (_jsx("div", { "data-slot": "routing-action-panel-context", className: "grid min-w-0 gap-[var(--space-3)] whitespace-pre-wrap [overflow-wrap:anywhere]", children: context })) : null, form] }) }));
20
15
  }
21
16
  export { RoutingActionPanel };
package/dist/styles.css CHANGED
@@ -412,33 +412,6 @@
412
412
  }
413
413
  }
414
414
 
415
- @layer components {
416
- [data-slot="routing-timeline-event-card"] {
417
- container-type: inline-size;
418
- }
419
-
420
- [data-slot="routing-timeline-endpoints"][data-complete-route="true"] {
421
- grid-template-columns: minmax(0, 1fr);
422
- }
423
-
424
- [data-slot="routing-timeline-endpoint-arrow"] {
425
- justify-self: start;
426
- transform: rotate(90deg);
427
- }
428
-
429
- @container (min-width: 22rem) {
430
- [data-slot="routing-timeline-endpoints"][data-complete-route="true"] {
431
- grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
432
- }
433
-
434
- [data-slot="routing-timeline-endpoint-arrow"] {
435
- align-self: center;
436
- justify-self: center;
437
- transform: none;
438
- }
439
- }
440
- }
441
-
442
415
  @layer utilities {
443
416
  .shadow-overlay-strong {
444
417
  box-shadow: var(--overlay-shadow-strong);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neasg/design-system",
3
- "version": "0.4.132",
3
+ "version": "0.4.137",
4
4
  "description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,27 +0,0 @@
1
- import * as React from "react";
2
- export type RoutingTimelineItemKind = "assigned" | "reassigned" | "returned" | "routed" | "message-received" | "message-sent" | "system";
3
- export interface RoutingTimelineItem {
4
- id: string;
5
- kind?: RoutingTimelineItemKind;
6
- title?: React.ReactNode;
7
- timestamp?: React.ReactNode;
8
- actor?: React.ReactNode;
9
- remark?: React.ReactNode;
10
- description?: React.ReactNode;
11
- detailLabel?: React.ReactNode;
12
- from?: React.ReactNode;
13
- to?: React.ReactNode;
14
- fromLabel?: React.ReactNode;
15
- toLabel?: React.ReactNode;
16
- badgeLabel?: React.ReactNode;
17
- badge?: React.ReactNode;
18
- icon?: React.ReactNode;
19
- }
20
- export interface RoutingTimelineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
21
- items: RoutingTimelineItem[];
22
- emptyMessage?: React.ReactNode;
23
- /** Reduces typography, icon, padding, and event spacing for constrained panels. */
24
- density?: "default" | "compact";
25
- }
26
- declare function RoutingTimeline({ items, emptyMessage, density, className, ...props }: RoutingTimelineProps): import("react/jsx-runtime").JSX.Element;
27
- export { RoutingTimeline };
@@ -1,96 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ArrowRight, Mail, Route } from "lucide-react";
3
- import { ActivityCard, ActivityCardContent } from "./activity-card";
4
- import { AvatarProfile } from "./avatar";
5
- import { Badge } from "./badge";
6
- import { EmptyState } from "./empty-state";
7
- import { cn } from "./lib/utils";
8
- import { Typography } from "./typography";
9
- const routingKindConfig = {
10
- assigned: {
11
- title: "Assigned case",
12
- label: "ASSIGNED",
13
- icon: "route",
14
- },
15
- reassigned: {
16
- title: "Reassigned case",
17
- label: "REASSIGNED",
18
- icon: "route",
19
- },
20
- returned: {
21
- title: "Case returned",
22
- label: "RETURNED",
23
- icon: "route",
24
- },
25
- routed: {
26
- title: "Case routed",
27
- label: "ROUTED",
28
- icon: "route",
29
- },
30
- "message-received": {
31
- title: "Received from applicant",
32
- label: "RECEIVED",
33
- detailLabel: "Response from applicant:",
34
- icon: "mail",
35
- },
36
- "message-sent": {
37
- title: "Sent to applicant",
38
- label: "SENT",
39
- detailLabel: "Officer sent a message:",
40
- icon: "mail",
41
- },
42
- system: {
43
- title: "System update",
44
- label: "SYSTEM",
45
- icon: "route",
46
- },
47
- };
48
- function RoutingTimelineHandoffEndpoint({ value, label, }) {
49
- const isTextValue = typeof value === "string" || typeof value === "number";
50
- if (!isTextValue) {
51
- return (_jsx("div", { "data-slot": "routing-timeline-profile", "data-avatar-size": "xs", className: "min-w-0", children: value }));
52
- }
53
- const name = String(value);
54
- const fallback = name
55
- .split(/\s+/)
56
- .filter(Boolean)
57
- .slice(0, 2)
58
- .map((part) => part.charAt(0))
59
- .join("")
60
- .toUpperCase();
61
- return (_jsx("div", { "data-slot": "routing-timeline-profile", "data-avatar-size": "xs", className: "min-w-0", children: _jsx(AvatarProfile, { size: "xs", fallback: fallback || "?", name: name, role: label, rolePlacement: "below" }) }));
62
- }
63
- function RoutingEventIcon({ icon, kind, density, }) {
64
- if (icon)
65
- return _jsx(_Fragment, { children: icon });
66
- return routingKindConfig[kind].icon === "mail" ? (_jsx(Mail, { "aria-hidden": "true", className: density === "compact" ? "size-4" : "size-6" })) : (_jsx(Route, { "aria-hidden": "true", className: density === "compact" ? "size-4" : "size-6" }));
67
- }
68
- function RoutingTimeline({ items, emptyMessage = "No routing activity yet.", density = "default", className, ...props }) {
69
- if (items.length === 0) {
70
- return (_jsx(EmptyState, { "data-slot": "routing-timeline", message: emptyMessage, className: cn("border-0 bg-transparent px-0", density === "compact"
71
- ? "py-[var(--space-4)]"
72
- : "py-[var(--space-6)]", className), ...props }));
73
- }
74
- return (_jsx("div", { "data-slot": "routing-timeline", "data-density": density, className: cn("min-w-0", className), ...props, children: _jsx("ol", { "data-slot": "routing-timeline-list", className: cn(density === "compact"
75
- ? "space-y-[var(--space-4)]"
76
- : "space-y-[32px]"), children: items.map((item) => {
77
- var _a, _b, _c, _d, _e;
78
- const kind = (_a = item.kind) !== null && _a !== void 0 ? _a : "routed";
79
- const config = routingKindConfig[kind];
80
- const hasRouteEndpoints = Boolean(item.from || item.to);
81
- const hasCompleteRoute = Boolean(item.from && item.to);
82
- const title = (_b = item.title) !== null && _b !== void 0 ? _b : config.title;
83
- const remark = (_c = item.remark) !== null && _c !== void 0 ? _c : item.description;
84
- const detailLabel = (_d = item.detailLabel) !== null && _d !== void 0 ? _d : config.detailLabel;
85
- const showDefaultBadge = item.badgeLabel !== null;
86
- return (_jsx("li", { children: _jsx(ActivityCard, { "data-slot": "routing-timeline-event-card", density: density, icon: _jsx(RoutingEventIcon, { icon: item.icon, kind: kind, density: density }), title: title, meta: item.timestamp, trailing: item.badge ? (item.badge) : showDefaultBadge ? (_jsx(Badge, { variant: "outline", bordered: true, className: "max-w-full shrink-0", children: (_e = item.badgeLabel) !== null && _e !== void 0 ? _e : config.label })) : null, children: hasRouteEndpoints || remark ? (_jsxs(ActivityCardContent, { density: density, children: [hasRouteEndpoints ? (_jsxs("div", { "data-slot": "routing-timeline-handoff", "data-complete-route": hasCompleteRoute ? "true" : undefined, className: cn("flex min-w-0 items-center", density === "compact"
87
- ? "gap-[var(--space-3)]"
88
- : "gap-[24px]"), children: [item.from ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.from, label: item.fromLabel })) : null, item.from && item.to ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "sr-only", children: "to" }), _jsx(ArrowRight, { "aria-hidden": "true", "data-slot": "routing-timeline-handoff-arrow", className: cn("shrink-0 text-foreground", density === "compact" ? "size-4" : "size-6") })] })) : null, item.to ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.to, label: item.toLabel })) : null] })) : null, remark ? (_jsxs("div", { "data-slot": "routing-timeline-remark", className: cn("bg-muted", density === "compact"
89
- ? "rounded-control p-[var(--space-2)]"
90
- : "rounded-md p-[12px]"), children: [detailLabel ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "font-semibold text-instructional", children: detailLabel })) : null, _jsx(Typography, { as: "div", variant: "bodySm", className: cn("whitespace-pre-wrap [overflow-wrap:anywhere]", detailLabel &&
91
- (density === "compact"
92
- ? "mt-[var(--space-1)]"
93
- : "mt-[4px]")), children: remark })] })) : null] })) : null }) }, item.id));
94
- }) }) }));
95
- }
96
- export { RoutingTimeline };