@neasg/design-system 0.4.133 → 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.
- package/dist/assignee-popover.d.ts +28 -0
- package/dist/assignee-popover.js +49 -0
- package/dist/changelog-action-panel.d.ts +14 -2
- package/dist/changelog-action-panel.js +18 -3
- package/dist/checklist.d.ts +4 -1
- package/dist/checklist.js +10 -8
- package/dist/file-upload-status-row.js +2 -2
- package/dist/filter-toggle.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/layout.js +4 -4
- package/dist/notification.d.ts +4 -0
- package/dist/notification.js +10 -3
- package/dist/routing-action-panel.d.ts +4 -11
- package/dist/routing-action-panel.js +6 -11
- package/dist/styles.css +0 -27
- package/package.json +1 -1
- package/dist/routing-timeline.d.ts +0 -27
- package/dist/routing-timeline.js +0 -96
|
@@ -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:
|
|
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
|
-
|
|
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 };
|
package/dist/checklist.d.ts
CHANGED
|
@@ -48,10 +48,13 @@ 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;
|
package/dist/checklist.js
CHANGED
|
@@ -19,21 +19,23 @@ export function ChecklistSelection({ title, description, typeOptions, subtypeOpt
|
|
|
19
19
|
const Icon = loading ? LoaderCircle : ClipboardList;
|
|
20
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] });
|
|
21
21
|
}
|
|
22
|
-
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;
|
|
23
24
|
const name = React.useId();
|
|
24
|
-
const [
|
|
25
|
+
const [internalRemarksOpen, setInternalRemarksOpen] = React.useState(null);
|
|
25
26
|
const [evidenceOpen, setEvidenceOpen] = React.useState(null);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
};
|
|
31
33
|
const showEvidence = evidenceOpen !== null && evidenceOpen !== void 0 ? evidenceOpen : evidenceCount > 0;
|
|
32
34
|
const remarksVisible = disabled ? Boolean(remarks.trim()) : showRemarks;
|
|
33
35
|
const evidenceVisible = disabled ? evidenceCount > 0 : showEvidence;
|
|
34
36
|
const remarksToggleLabel = showRemarks ? "Hide remarks" : "Show remarks";
|
|
35
37
|
const evidenceToggleLabel = showEvidence ? "Hide photos" : "Show photos";
|
|
36
|
-
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:
|
|
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] });
|
|
37
39
|
}
|
|
38
40
|
export function ChecklistAttachments({ items, onRemove }) {
|
|
39
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)) });
|
|
@@ -37,9 +37,9 @@ export function FileUploadStatusRow({ file, href, onCancel, onRemove, disabled =
|
|
|
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-
|
|
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: [href ? (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: "min-w-0 truncate 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
|
|
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();
|
package/dist/filter-toggle.js
CHANGED
|
@@ -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-
|
|
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";
|
|
@@ -176,3 +174,5 @@ export { PopoverList, type PopoverListProps } from "./popover";
|
|
|
176
174
|
export { StatusBanner, type StatusBannerProps } from "./status-banner";
|
|
177
175
|
export { FileUploadList } from "./file-upload-list";
|
|
178
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";
|
|
@@ -94,3 +93,4 @@ export { TableAddRow } from "./table-add-row";
|
|
|
94
93
|
export { PopoverList } from "./popover";
|
|
95
94
|
export { StatusBanner } from "./status-banner";
|
|
96
95
|
export { FileUploadList } from "./file-upload-list";
|
|
96
|
+
export { AssigneePopover } from "./assignee-popover";
|
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-
|
|
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();
|
package/dist/notification.d.ts
CHANGED
|
@@ -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. */
|
package/dist/notification.js
CHANGED
|
@@ -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 &&
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
36
|
-
*
|
|
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({
|
|
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 {
|
|
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
|
|
13
|
-
*
|
|
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({
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", ...panelProps, children: _jsxs(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", footerVariant: "separated", footerClassName: "max-h-[50%] overflow-y-auto overscroll-contain", 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,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 };
|
package/dist/routing-timeline.js
DELETED
|
@@ -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 };
|