@neasg/design-system 0.4.121 → 0.4.133

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/alert.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
+ import { CircleAlert } from "lucide-react";
3
4
  import { cva } from "class-variance-authority";
4
5
  import { Button } from "./button";
5
6
  import { Checkbox } from "./checkbox";
@@ -20,7 +21,8 @@ const alertVariants = cva("relative flex w-full items-start gap-2 rounded-surfac
20
21
  });
21
22
  const AlertAction = React.forwardRef(({ className, type = "button", ...props }, ref) => (_jsx(Button, { ref: ref, type: type, variant: "link", size: "compact", className: cn("h-auto min-h-6 px-0 text-inherit hover:text-inherit", className), ...props })));
22
23
  AlertAction.displayName = "AlertAction";
23
- const Alert = React.forwardRef(({ variant, title, description, icon: Icon, action, className, children, role = "alert", ...props }, ref) => {
24
+ const Alert = React.forwardRef(({ variant, title, description, icon, action, className, children, role = "alert", ...props }, ref) => {
25
+ const Icon = icon !== null && icon !== void 0 ? icon : (variant === "destructive" ? CircleAlert : undefined);
24
26
  const body = description !== null && description !== void 0 ? description : children;
25
27
  const hasStackedContent = Boolean(title);
26
28
  return (_jsxs("div", { ref: ref, role: role, className: cn(alertVariants({ variant }), !hasStackedContent && "items-center", className), ...props, children: [Icon ? (_jsx(Icon, { className: cn("h-4 w-4 shrink-0", hasStackedContent && "mt-0.5") })) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [title ? (_jsx(Typography, { as: "p", variant: "bodySm", color: "inherit", className: "font-bold", children: title })) : null, body ? (_jsx(Typography, { as: "div", variant: "caption", color: "inherit", className: "opacity-95", children: body })) : null] }), action ? (_jsx("div", { className: cn("shrink-0 text-inherit [&_a]:text-inherit [&_button]:text-inherit [&_button:hover]:text-inherit", hasStackedContent ? "self-start" : "self-center"), children: action })) : null] }));
package/dist/button.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "link" | "default" | "warning" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
4
+ variant?: "link" | "default" | "warning" | "destructive" | "outline" | "secondary" | "ghost" | "destructiveGhost" | null | undefined;
5
5
  size?: "default" | "sm" | "compact" | "comfortable" | "icon" | "compactIcon" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -12,6 +12,8 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
12
12
  asChild?: boolean;
13
13
  /** Allow the label to wrap within its container and the button to grow in height. */
14
14
  wrap?: boolean;
15
+ /** Align the button content within its available width. */
16
+ contentAlign?: "center" | "start";
15
17
  /**
16
18
  * Shows a loading indicator, disables interaction, and sets `aria-busy`.
17
19
  * When used with `asChild`, the component also prevents click handling and sets `aria-disabled`.
package/dist/button.js CHANGED
@@ -12,6 +12,7 @@ const buttonVariants = cva("inline-flex cursor-pointer items-center justify-cent
12
12
  outline: "border border-[hsl(var(--button-outline-border))] bg-[hsl(var(--button-outline-background))] text-foreground hover:bg-[hsl(var(--button-outline-hover-background))] hover:text-accent-foreground",
13
13
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
14
  ghost: "hover:bg-accent hover:text-accent-foreground",
15
+ destructiveGhost: "text-destructive hover:bg-destructive/10 hover:text-destructive focus-visible:ring-destructive",
15
16
  link: "text-interactive underline-offset-4 hover:text-interactive hover:underline",
16
17
  },
17
18
  size: {
@@ -102,7 +103,7 @@ function attachAnimatedIconRefs(node, refs) {
102
103
  }
103
104
  return node;
104
105
  }
105
- const Button = React.forwardRef(({ className, variant, size, asChild = false, wrap = false, loading = false, loadingText, loadingIndicator, disabled, children, onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, tabIndex, ...props }, ref) => {
106
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, wrap = false, contentAlign = "center", loading = false, loadingText, loadingIndicator, disabled, children, onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, tabIndex, ...props }, ref) => {
106
107
  const Comp = asChild ? Slot : "button";
107
108
  const isDisabled = disabled || loading;
108
109
  const loadingLabel = loadingText !== null && loadingText !== void 0 ? loadingText : children;
@@ -112,7 +113,7 @@ const Button = React.forwardRef(({ className, variant, size, asChild = false, wr
112
113
  hasTextLikeContent(children)) {
113
114
  throw new Error('NEA Button `size="icon"` and `size="compactIcon"` only accepts icon components or SVG content. Do not pass text or emoji.');
114
115
  }
115
- const content = loading ? (loadingLabel ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [loadingIndicator !== null && loadingIndicator !== void 0 ? loadingIndicator : _jsx(DefaultLoadingIndicator, {}), _jsx("span", { children: attachAnimatedIconRefs(loadingLabel, animatedIconRefs) })] })) : (loadingIndicator !== null && loadingIndicator !== void 0 ? loadingIndicator : _jsx(DefaultLoadingIndicator, {}))) : (attachAnimatedIconRefs(children, animatedIconRefs));
116
+ const content = loading ? (loadingLabel ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [loadingIndicator !== null && loadingIndicator !== void 0 ? loadingIndicator : _jsx(DefaultLoadingIndicator, {}), _jsx("span", { className: "inline-flex items-center gap-2", children: attachAnimatedIconRefs(loadingLabel, animatedIconRefs) })] })) : (loadingIndicator !== null && loadingIndicator !== void 0 ? loadingIndicator : _jsx(DefaultLoadingIndicator, {}))) : (attachAnimatedIconRefs(children, animatedIconRefs));
116
117
  const startAnimatedIcons = () => {
117
118
  animatedIconRefs.current.forEach((icon) => { var _a; return (_a = icon.startAnimation) === null || _a === void 0 ? void 0 : _a.call(icon); });
118
119
  };
@@ -149,7 +150,7 @@ const Button = React.forwardRef(({ className, variant, size, asChild = false, wr
149
150
  };
150
151
  return (_jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size }), wrap && "h-auto max-w-full whitespace-normal break-words py-1.5", wrap && (size === "sm" || size === "compact" || size === "compactIcon"
151
152
  ? "min-h-8"
152
- : size === "comfortable" ? "min-h-[46px]" : "min-h-control"), className), ref: ref, "aria-busy": loading || undefined, "aria-disabled": isDisabled || undefined, "data-loading": loading ? "" : undefined, "data-disabled": isDisabled ? "" : undefined, onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleFocus, onBlur: handleBlur, tabIndex: isDisabled && asChild ? -1 : tabIndex, disabled: !asChild ? isDisabled : undefined, ...props, children: content }));
153
+ : size === "comfortable" ? "min-h-[46px]" : "min-h-control"), contentAlign === "start" && "justify-start text-left", className), ref: ref, "aria-busy": loading || undefined, "aria-disabled": isDisabled || undefined, "data-loading": loading ? "" : undefined, "data-disabled": isDisabled ? "" : undefined, onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleFocus, onBlur: handleBlur, tabIndex: isDisabled && asChild ? -1 : tabIndex, disabled: !asChild ? isDisabled : undefined, ...props, children: content }));
153
154
  });
154
155
  Button.displayName = "Button";
155
156
  export { Button, buttonVariants };
package/dist/checkbox.js CHANGED
@@ -24,9 +24,9 @@ const Checkbox = React.forwardRef(({ className, indeterminate = false, size = "m
24
24
  innerRef.current.indeterminate = indeterminate;
25
25
  }
26
26
  }, [indeterminate]);
27
- const box = (_jsxs("span", { className: cn("relative inline-block shrink-0 rounded-sm border bg-background align-middle text-primary-foreground transition-colors", sizeClasses[size], (checked || indeterminate) && !disabled
27
+ const box = (_jsxs("span", { className: cn("relative inline-flex shrink-0 items-center justify-center rounded-sm border bg-background align-middle text-primary-foreground transition-colors", sizeClasses[size], (checked || indeterminate) && !disabled
28
28
  ? "border-primary bg-primary"
29
- : "border-input", disabled && "cursor-not-allowed opacity-50", label && alignToStart && startAlignedOffsetClasses[size], !label && className), children: [_jsx("input", { ref: innerRef, type: "checkbox", id: inputId, checked: checked, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed", ...props }), indeterminate ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) })) : checked ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none absolute inset-0 m-auto h-3 w-3", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : null] }));
29
+ : "border-input", disabled && "cursor-not-allowed opacity-50", label && alignToStart && startAlignedOffsetClasses[size], !label && className), children: [_jsx("input", { ref: innerRef, type: "checkbox", id: inputId, checked: checked, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer appearance-none opacity-0 disabled:cursor-not-allowed", ...props }), indeterminate ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", className: "pointer-events-none h-3 w-3", "aria-hidden": "true", children: _jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) })) : checked ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none h-3 w-3", "aria-hidden": "true", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : null] }));
30
30
  if (!label)
31
31
  return box;
32
32
  return (_jsxs("label", { htmlFor: inputId, className: cn("inline-flex cursor-pointer gap-2 text-sm", alignToStart ? "items-start" : "items-center", disabled && "cursor-not-allowed opacity-50", className), children: [box, _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5 select-none", children: [_jsx(Typography, { as: "span", variant: "bodySm", children: label }), description ? (_jsx(Typography, { as: "p", variant: "caption", color: "muted", children: description })) : null] })] }));
@@ -57,6 +57,9 @@ export interface ChecklistAttachmentsProps {
57
57
  key: string;
58
58
  name: string;
59
59
  url: string;
60
+ type?: string;
61
+ size?: number | string;
62
+ sizeBytes?: number;
60
63
  }[];
61
64
  onRemove?: (key: string) => void;
62
65
  }
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,16 +17,24 @@ 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: "flex flex-wrap items-center justify-center gap-4", children: [_jsx(Select, { className: "w-auto", triggerClassName: "w-auto min-w-0", label: "Type", hideLabel: true, placeholder: "Type", options: typeOptions, value: type, onValueChange: onTypeChange, disabled: disabled || loading }), _jsx(Select, { className: "w-auto", triggerClassName: "w-auto min-w-0", 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
22
  export function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit = 500, remarksMaxLength, remarksRequired = false, evidence, evidenceCount = 0 }) {
22
23
  const name = React.useId();
23
- const [showRemarks, setShowRemarks] = React.useState(false);
24
- const [showEvidence, setShowEvidence] = React.useState(false);
24
+ const [remarksOpen, setRemarksOpen] = React.useState(null);
25
+ const [evidenceOpen, setEvidenceOpen] = React.useState(null);
26
+ React.useEffect(() => {
27
+ if (remarksRequired)
28
+ setRemarksOpen(true);
29
+ }, [remarksRequired]);
30
+ const showRemarks = remarksOpen !== null && remarksOpen !== void 0 ? remarksOpen : (remarksRequired || Boolean(remarks.trim()));
31
+ const showEvidence = evidenceOpen !== null && evidenceOpen !== void 0 ? evidenceOpen : evidenceCount > 0;
32
+ const remarksVisible = disabled ? Boolean(remarks.trim()) : showRemarks;
33
+ const evidenceVisible = disabled ? evidenceCount > 0 : showEvidence;
25
34
  const remarksToggleLabel = showRemarks ? "Hide remarks" : "Show remarks";
26
35
  const evidenceToggleLabel = showEvidence ? "Hide photos" : "Show photos";
27
- 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)) }), _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] })] }), showEvidence && evidence ? _jsx("div", { id: `${name}-evidence`, children: evidence }) : null, showRemarks ? _jsx("div", { id: `${name}-remarks`, children: _jsx(Textarea, { label: "Remarks", hideLabel: true, placeholder: "Type...", value: remarks, disabled: disabled, "aria-required": remarksRequired, rows: 5, onChange: (event) => onRemarksChange(event.target.value), maxWords: remarksMaxLength === undefined ? remarksLimit : undefined, maxLength: remarksMaxLength, showCount: remarksMaxLength !== undefined }) }) : null] });
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: "Required when No or N/A is selected.", 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] });
28
37
  }
29
38
  export function ChecklistAttachments({ items, onRemove }) {
30
- 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)) });
39
+ 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)) });
31
40
  }
package/dist/dialog.d.ts CHANGED
@@ -9,9 +9,11 @@ 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";
13
- /** Standard dialogs are 32rem wide; larger sizes are for complex editors. */
12
+ variant?: "default" | "confirmation";
13
+ /** Standard dialogs are 512px wide; larger sizes are for complex editors. */
14
14
  size?: "standard" | "large" | "extraLarge" | "full";
15
+ /** Minimum height; defaults to 11.25rem (180px at the default root font size). */
16
+ minHeight?: React.CSSProperties["minHeight"];
15
17
  open?: boolean;
16
18
  defaultOpen?: boolean;
17
19
  onOpenChange?: (open: boolean) => void;
@@ -29,5 +31,5 @@ export interface DialogProps {
29
31
  /** Whether a pointer interaction outside the dialog closes it. */
30
32
  closeOnOutsideClick?: boolean;
31
33
  }
32
- declare function Dialog({ variant, size, open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton, closeOnOutsideClick, }: DialogProps): import("react/jsx-runtime").JSX.Element;
34
+ declare function Dialog({ variant, size, minHeight, open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton, closeOnOutsideClick, }: DialogProps): import("react/jsx-runtime").JSX.Element;
33
35
  export { Dialog };
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", 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", open, defaultOpen, onO
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
- return (_jsxs(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, modal: modal, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogSurface, { ...(!description ? { "aria-describedby": undefined } : {}), className: cn(isCompactConfirmation &&
50
- "gap-0 overflow-hidden rounded-xl p-0", isRoutingConfirmation &&
51
- "gap-0 rounded-overlay p-[var(--space-8)]", "w-[calc(100vw-2rem)]", { standard: "max-w-lg", large: "max-w-3xl", extraLarge: "max-w-5xl", full: "max-w-[96rem]" }[size], contentClassName), hideCloseButton: hideCloseButton || isCompactConfirmation || isRoutingConfirmation, onPointerDownOutside: (event) => {
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 &&
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", 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"), 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,
@@ -12,6 +12,7 @@ const splitButtonDividerClassNames = {
12
12
  outline: "before:bg-[hsl(var(--button-outline-border))]",
13
13
  secondary: "before:bg-secondary-foreground/20",
14
14
  ghost: "before:bg-border",
15
+ destructiveGhost: "before:bg-destructive/30",
15
16
  link: "before:bg-interactive/25",
16
17
  };
17
18
  function DropdownButton({ label, leadingIcon, children, className, contentClassName, dropdownAriaLabel, menuWidth = "trigger", align = "end", alignOffset, avoidCollisions, collisionPadding, defaultOpen, disabled, hideWhenDetached, loading, loadingIndicator, loadingText, modal, onClick, onOpenChange, open, side, sideOffset = 4, split: splitProp, sticky, type = "button", variant, size, ...buttonProps }) {
@@ -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,7 +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: "grid grid-cols-[1.5rem_minmax(0,1fr)_auto_1.75rem] min-h-7 items-start gap-x-2", 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) => {
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
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 mt-1.5 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) => {
41
43
  event.stopPropagation();
42
44
  if (action === "cancel") {
43
45
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
@@ -17,6 +17,8 @@ export interface FileUploadStatusToastProps {
17
17
  className?: string;
18
18
  onCancel?: (file: FileUploadStatusToastFile) => void;
19
19
  onRemove?: (file: FileUploadStatusToastFile) => void;
20
+ /** Show remove actions when onRemove is provided. Does not hide cancel. */
21
+ showRemoveButton?: boolean;
20
22
  onDismiss?: () => void;
21
23
  }
22
24
  export interface ShowFileUploadStatusToastOptions extends FileUploadStatusToastProps {
@@ -24,5 +26,5 @@ export interface ShowFileUploadStatusToastOptions extends FileUploadStatusToastP
24
26
  duration?: number;
25
27
  dismissible?: boolean;
26
28
  }
27
- export declare function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, onDismiss, }: FileUploadStatusToastProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, showRemoveButton, onDismiss, }: FileUploadStatusToastProps): import("react/jsx-runtime").JSX.Element;
28
30
  export declare function showFileUploadStatusToast({ id, duration, dismissible, onDismiss, ...props }: ShowFileUploadStatusToastOptions): string | number;
@@ -61,13 +61,13 @@ function getDefaultDescription(files) {
61
61
  ].filter(Boolean);
62
62
  return parts.length > 0 ? parts.join(" / ") : undefined;
63
63
  }
64
- export function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, onDismiss, }) {
64
+ export function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, showRemoveButton = true, onDismiss, }) {
65
65
  const normalizedFiles = normalizeFiles({ files, uploadProgress });
66
66
  const resolvedTitle = title !== null && title !== void 0 ? title : getDefaultTitle(normalizedFiles);
67
67
  const resolvedDescription = description !== null && description !== void 0 ? description : getDefaultDescription(normalizedFiles);
68
68
  return (_jsxs("section", { className: cn("w-[min(40rem,calc(100vw-2rem))] overflow-hidden rounded-overlay border border-border bg-background text-foreground shadow-overlay", className), "aria-label": "File upload status", children: [_jsxs("div", { className: "flex items-start gap-3 border-b px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Typography, { as: "p", variant: "label", className: "truncate", children: resolvedTitle }), resolvedDescription ? (_jsx(Typography, { as: "p", variant: "caption", className: "mt-0.5 text-xs leading-4", children: resolvedDescription })) : null] }), onDismiss ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-7 w-7 shrink-0", onClick: onDismiss, "aria-label": "Dismiss file upload status", children: _jsx(X, { "aria-hidden": "true" }) })) : null] }), _jsx("ul", { className: "max-h-80 space-y-2 overflow-y-auto p-2.5", children: normalizedFiles.map((file, index) => {
69
69
  var _a;
70
- return (_jsx(FileUploadStatusRow, { file: file, onCancel: onCancel ? () => onCancel(file) : undefined, onRemove: onRemove && (file.status === "complete" || file.status === "error")
70
+ return (_jsx(FileUploadStatusRow, { file: file, onCancel: onCancel ? () => onCancel(file) : undefined, onRemove: showRemoveButton && onRemove && (file.status === "complete" || file.status === "error")
71
71
  ? () => onRemove(file)
72
72
  : undefined }, `${file.name}-${(_a = file.size) !== null && _a !== void 0 ? _a : "unknown"}-${index}`));
73
73
  }) })] }));
@@ -6,7 +6,9 @@ export interface FileUploadProps {
6
6
  onFilesChange: (files: File[]) => void;
7
7
  value?: File[];
8
8
  disabled?: boolean;
9
+ /** Show the remove action on file rows. */
10
+ showRemoveButton?: boolean;
9
11
  className?: string;
10
12
  uploadProgress?: Map<string, FileUploadProgress>;
11
13
  }
12
- export declare function FileUpload({ accept, maxSize, maxFiles, onFilesChange, value, disabled, className, uploadProgress, }: FileUploadProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function FileUpload({ accept, maxSize, maxFiles, onFilesChange, value, disabled, showRemoveButton, className, uploadProgress, }: FileUploadProps): import("react/jsx-runtime").JSX.Element;
@@ -15,7 +15,7 @@ function formatFileSize(bytes) {
15
15
  const i = Math.floor(Math.log(bytes) / Math.log(k));
16
16
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
17
17
  }
18
- export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles = 10, onFilesChange, value = [], disabled = false, className, uploadProgress, }) {
18
+ export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles = 10, onFilesChange, value = [], disabled = false, showRemoveButton = true, className, uploadProgress, }) {
19
19
  const [isDragOver, setIsDragOver] = useState(false);
20
20
  const [error, setError] = useState(null);
21
21
  const inputRef = React.useRef(null);
@@ -87,6 +87,6 @@ export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles =
87
87
  status: progress === null || progress === void 0 ? void 0 : progress.status,
88
88
  progress: progress === null || progress === void 0 ? void 0 : progress.progress,
89
89
  error: progress === null || progress === void 0 ? void 0 : progress.error,
90
- }, disabled: disabled, onRemove: () => removeFile(index) }, `${file.name}-${file.size}-${index}`));
90
+ }, disabled: disabled, onRemove: showRemoveButton ? () => removeFile(index) : undefined }, `${file.name}-${file.size}-${index}`));
91
91
  }) })) : null] }));
92
92
  }
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type { AvatarProps, AvatarSize, AvatarProfileProps, AvatarRootProps, } fr
3
3
  export { AvatarProfilePopover, AvatarProfilePopoverContent, AvatarProfilePopoverHeader, AvatarProfilePopoverSection, AvatarProfilePopoverFooter, AvatarProfilePopoverFooterAction, } from "./avatar-profile-popover";
4
4
  export type { AvatarProfilePopoverProps, AvatarProfilePopoverContentProps, AvatarProfilePopoverHeaderProps, AvatarProfilePopoverSectionProps, AvatarProfilePopoverFooterProps, AvatarProfilePopoverFooterActionProps, } from "./avatar-profile-popover";
5
5
  export * from "./animated-icons";
6
+ export { Inline } from "./inline";
6
7
  export { Alert, AlertAction, AlertConsent, alertVariants, } from "./alert";
7
8
  export type { AlertActionProps, AlertConsentProps, AlertProps, } from "./alert";
8
9
  export { AlertStack } from "./alert-stack";
@@ -173,3 +174,5 @@ export type { ChecklistAttachmentsProps, ChecklistPanelProps, ChecklistSectionPr
173
174
  export { TableAddRow, type TableAddRowProps } from "./table-add-row";
174
175
  export { PopoverList, type PopoverListProps } from "./popover";
175
176
  export { StatusBanner, type StatusBannerProps } from "./status-banner";
177
+ export { FileUploadList } from "./file-upload-list";
178
+ export type { FileUploadListProps, UploadedFile } from "./file-upload-list";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Avatar, AvatarProfile, AvatarRoot, AvatarImage, } from "./avatar";
2
2
  export { AvatarProfilePopover, AvatarProfilePopoverContent, AvatarProfilePopoverHeader, AvatarProfilePopoverSection, AvatarProfilePopoverFooter, AvatarProfilePopoverFooterAction, } from "./avatar-profile-popover";
3
3
  export * from "./animated-icons";
4
+ export { Inline } from "./inline";
4
5
  export { Alert, AlertAction, AlertConsent, alertVariants, } from "./alert";
5
6
  export { AlertStack } from "./alert-stack";
6
7
  export { ActivityCard, ActivityCardActor, ActivityCardContent, } from "./activity-card";
@@ -92,3 +93,4 @@ export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelect
92
93
  export { TableAddRow } from "./table-add-row";
93
94
  export { PopoverList } from "./popover";
94
95
  export { StatusBanner } from "./status-banner";
96
+ export { FileUploadList } from "./file-upload-list";
@@ -0,0 +1,3 @@
1
+ import * as React from "react";
2
+ /** A compact horizontal group with design-system spacing. */
3
+ export declare function Inline({ children, ...props }: Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style">): import("react/jsx-runtime").JSX.Element;
package/dist/inline.js ADDED
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** A compact horizontal group with design-system spacing. */
3
+ export function Inline({ children, ...props }) {
4
+ return _jsx("div", { ...props, "data-slot": "inline", className: "flex items-center gap-[var(--space-2)]", children: children });
5
+ }
@@ -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,
@@ -122,7 +122,7 @@ export function replaceTemplateVariables(content, context) {
122
122
  return result;
123
123
  }
124
124
  function ToolbarIconButton({ active, label, onClick, children, }) {
125
- return (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: cn("h-8 w-8", active && "bg-muted"), onClick: onClick, "aria-label": label, title: label, children: children }));
125
+ return (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: cn(active && "bg-muted"), onClick: onClick, "aria-label": label, title: label, children: children }));
126
126
  }
127
127
  function getTemplateCategory(template) {
128
128
  return template.category || DEFAULT_TEMPLATE_CATEGORY;
@@ -359,7 +359,7 @@ export function RichTextEditor({ variant = "default", placeholder = "Type messag
359
359
  void handleSend();
360
360
  }, children: [_jsx(SendIcon, { size: 16 }), isSending ? sendingLabel : sendLabel] }));
361
361
  if (variant === "message") {
362
- return (_jsxs("div", { "data-slot": "message-composer", ref: dropZoneRef, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("box-border flex min-h-[164px] flex-col overflow-hidden rounded-[8px] border border-input bg-background transition-all focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50", !isExpanded && attachments.length === 0 && "h-[164px]", isDragging && "border-2 border-primary bg-primary/5", className), children: [header ? (_jsxs("div", { "data-slot": "message-composer-header", className: "flex min-h-[48px] items-center justify-between px-[10px] py-[8px] text-muted-foreground", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-[8px]", children: [_jsx(Reply, { "aria-hidden": "true", className: "size-[16px] shrink-0" }), _jsx(Typography, { as: "span", variant: "caption", className: "min-w-0 truncate font-medium text-muted-foreground", "data-slot": "message-composer-header-label", children: header })] }), onExpandToggle ? (_jsx(Tooltip, { content: isExpanded ? "Collapse editor" : "Expand editor", side: "top", children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[28px] w-[28px] shrink-0", onClick: onExpandToggle, "aria-label": isExpanded ? "Collapse editor" : "Expand editor", children: _jsx(MaximizeIcon, { size: 16 }) }) })) : null] })) : null, _jsx("div", { "data-slot": "message-composer-editor", className: cn("min-h-[64px]", isExpanded && "min-h-[200px]"), children: _jsx(EditorContent, { editor: editor, className: cn("min-h-[64px] text-sm", isExpanded && "min-h-[200px]", "[&_.ProseMirror]:min-h-[64px] [&_.ProseMirror]:outline-none", isExpanded && "[&_.ProseMirror]:min-h-[200px]", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]") }) }), attachments.length > 0 ? (_jsx("div", { className: "space-y-[8px] px-[10px] pb-[8px]", children: attachments.map((attachment, index) => (_jsxs("div", { className: "flex w-full items-center gap-[8px] rounded-md border bg-muted px-[12px] py-[8px] text-sm", children: [_jsx(PaperclipIcon, { size: 14, className: "shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: attachment.name }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatFileSize(attachment.size) }), _jsx("button", { type: "button", onClick: () => removeAttachment(index), className: "shrink-0 text-muted-foreground hover:text-foreground", "aria-label": `Remove ${attachment.name}`, children: _jsx(XIcon, { size: 14 }) })] }, `${attachment.name}-${attachment.size}-${index}`))) })) : null, _jsx("input", { ref: fileInputRef, type: "file", accept: acceptedFileTypes, multiple: true, onChange: handleFileSelect, className: "hidden" }), linkPopoverOpen &&
362
+ return (_jsxs("div", { "data-slot": "message-composer", ref: dropZoneRef, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("box-border flex min-h-[148px] flex-col overflow-hidden rounded-[8px] border border-input bg-background transition-all focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50", !isExpanded && attachments.length === 0 && "h-[148px]", isDragging && "border-2 border-primary bg-primary/5", className), children: [header ? (_jsx("div", { "data-slot": "message-composer-header", className: "flex min-h-[32px] items-center px-[10px] py-[4px] text-muted-foreground", children: _jsxs("div", { className: "flex min-w-0 items-center gap-[8px]", children: [_jsx(Reply, { "aria-hidden": "true", className: "size-[16px] shrink-0" }), _jsx(Typography, { as: "span", variant: "caption", className: "min-w-0 truncate font-medium text-muted-foreground", "data-slot": "message-composer-header-label", children: header })] }) })) : null, _jsx("div", { "data-slot": "message-composer-editor", className: cn("min-h-[64px]", isExpanded && "min-h-[200px]"), children: _jsx(EditorContent, { editor: editor, className: cn("min-h-[64px] text-sm", isExpanded && "min-h-[200px]", "[&_.ProseMirror]:min-h-[64px] [&_.ProseMirror]:outline-none", isExpanded && "[&_.ProseMirror]:min-h-[200px]", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]") }) }), attachments.length > 0 ? (_jsx("div", { className: "space-y-[8px] px-[10px] pb-[8px]", children: attachments.map((attachment, index) => (_jsxs("div", { className: "flex w-full items-center gap-[8px] rounded-md border bg-muted px-[12px] py-[8px] text-sm", children: [_jsx(PaperclipIcon, { size: 14, className: "shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: attachment.name }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatFileSize(attachment.size) }), _jsx("button", { type: "button", onClick: () => removeAttachment(index), className: "shrink-0 text-muted-foreground hover:text-foreground", "aria-label": `Remove ${attachment.name}`, children: _jsx(XIcon, { size: 14 }) })] }, `${attachment.name}-${attachment.size}-${index}`))) })) : null, _jsx("input", { ref: fileInputRef, type: "file", accept: acceptedFileTypes, multiple: true, onChange: handleFileSelect, className: "hidden" }), linkPopoverOpen &&
363
363
  linkMenuPosition &&
364
364
  typeof document !== "undefined" &&
365
365
  createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-[99]", onClick: () => {
@@ -370,7 +370,7 @@ export function RichTextEditor({ variant = "default", placeholder = "Type messag
370
370
  } }), _jsx("div", { ref: linkMenuRef, className: "fixed z-[100] w-80 animate-in fade-in-0 zoom-in-95 rounded-md border bg-popover p-3 shadow-md", style: {
371
371
  top: linkMenuPosition.top,
372
372
  left: linkMenuPosition.left,
373
- }, children: _jsxs("div", { className: "flex items-end gap-2", children: [_jsx(Input, { ref: linkInputRef, label: "URL", id: "message-link-url", type: "url", placeholder: "https://example.com", value: linkUrl, onChange: (event) => setLinkUrl(event.target.value), onKeyDown: handleLinkKeyDown, inputClassName: "h-8 text-sm", autoFocus: true }), _jsx(Button, { type: "button", className: "h-8 px-3", onClick: handleLinkSubmit, disabled: !linkUrl.trim(), children: "Add" })] }) })] }), document.body), isFormattingOpen ? (_jsxs("div", { className: "flex flex-wrap items-center gap-[4px] border-t border-border px-[10px] py-[8px]", children: [_jsx(ToolbarIconButton, { label: "Bold", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bold"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBold().run(), children: _jsx(BoldIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Italic", active: editor === null || editor === void 0 ? void 0 : editor.isActive("italic"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleItalic().run(), children: _jsx(ItalicIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Underline", active: editor === null || editor === void 0 ? void 0 : editor.isActive("underline"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleUnderline().run(), children: _jsx(UnderlineIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Bullet list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bulletList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBulletList().run(), children: _jsx(ListIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Ordered list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("orderedList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrderedIcon, { size: 14 }) })] })) : null, _jsxs("div", { "data-slot": "message-composer-toolbar", className: "flex min-h-[52px] items-center justify-between px-[10px] py-[8px]", children: [_jsxs("div", { className: "flex items-center gap-[8px]", children: [_jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[28px] w-[28px]", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 16 }) }) }), _jsxs("div", { className: "inline-flex h-[32px] overflow-hidden rounded-control border border-input bg-background", children: [_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[32px] w-[32px] rounded-none border-r border-input", onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBold().run(), "aria-pressed": (_a = editor === null || editor === void 0 ? void 0 : editor.isActive("bold")) !== null && _a !== void 0 ? _a : false, "aria-label": "Bold", children: _jsx(BoldIcon, { size: 16 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[32px] w-[32px] rounded-none border-r border-input", onClick: openLinkMenu, "aria-label": "Add link", children: _jsx(LinkIcon, { size: 16 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-[32px] w-[32px] rounded-none", onClick: () => setIsFormattingOpen((open) => !open), "aria-expanded": isFormattingOpen, "aria-label": "Formatting options", children: _jsx(Baseline, { "aria-hidden": "true", className: "size-[16px]" }) })] })] }), _jsxs("div", { className: "flex items-center gap-[8px]", children: [showTemplates && (templates.length > 0 || templateCreateHref) ? (_jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "outline", size: "sm", children: ["Templates", _jsx(ChevronDownIcon, { size: 16 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
373
+ }, children: _jsxs("div", { className: "flex items-end gap-2", children: [_jsx(Input, { ref: linkInputRef, label: "URL", id: "message-link-url", type: "url", placeholder: "https://example.com", value: linkUrl, onChange: (event) => setLinkUrl(event.target.value), onKeyDown: handleLinkKeyDown, inputClassName: "h-8 text-sm", autoFocus: true }), _jsx(Button, { type: "button", className: "h-8 px-3", onClick: handleLinkSubmit, disabled: !linkUrl.trim(), children: "Add" })] }) })] }), document.body), isFormattingOpen ? (_jsxs("div", { className: "flex flex-wrap items-center gap-[4px] border-t border-border px-[10px] py-[8px]", children: [_jsx(ToolbarIconButton, { label: "Bold", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bold"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBold().run(), children: _jsx(BoldIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Italic", active: editor === null || editor === void 0 ? void 0 : editor.isActive("italic"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleItalic().run(), children: _jsx(ItalicIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Underline", active: editor === null || editor === void 0 ? void 0 : editor.isActive("underline"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleUnderline().run(), children: _jsx(UnderlineIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Bullet list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bulletList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBulletList().run(), children: _jsx(ListIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Ordered list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("orderedList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrderedIcon, { size: 14 }) })] })) : null, _jsxs("div", { "data-slot": "message-composer-toolbar", className: "flex min-h-[52px] items-center justify-between px-[10px] py-[8px]", children: [_jsxs("div", { className: "flex items-center gap-[8px]", children: [_jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 16 }) }) }), _jsxs("div", { className: "inline-flex h-[32px] overflow-hidden rounded-control border border-input bg-background", children: [_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "rounded-none border-r border-input", onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBold().run(), "aria-pressed": (_a = editor === null || editor === void 0 ? void 0 : editor.isActive("bold")) !== null && _a !== void 0 ? _a : false, "aria-label": "Bold", children: _jsx(BoldIcon, { size: 16 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "rounded-none border-r border-input", onClick: openLinkMenu, "aria-label": "Add link", children: _jsx(LinkIcon, { size: 16 }) }), _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "rounded-none", onClick: () => setIsFormattingOpen((open) => !open), "aria-expanded": isFormattingOpen, "aria-label": "Formatting options", children: _jsx(Baseline, { "aria-hidden": "true", className: "size-[16px]" }) })] })] }), _jsxs("div", { className: "flex items-center gap-[8px]", children: [showTemplates && (templates.length > 0 || templateCreateHref) ? (_jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "outline", size: "sm", children: ["Templates", _jsx(ChevronDownIcon, { size: 16 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
374
374
  var _a;
375
375
  return (_jsxs("div", { children: [_jsx("div", { className: "sticky top-0 bg-muted px-3 py-2 text-sm font-semibold text-foreground", children: (_a = templateCategoryLabels[category]) !== null && _a !== void 0 ? _a : category }), templates.map((template) => (_jsxs("button", { type: "button", className: "w-full border-b border-border/50 px-3 py-2 text-left text-sm transition-colors last:border-0 hover:bg-muted", onClick: () => applyTemplate(template), children: [_jsx("div", { className: "truncate font-medium", children: template.name }), template.description ? (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: template.description })) : null] }, template.id)))] }, category));
376
376
  }) }), templateCreateHref ? (_jsx("div", { className: "border-t p-2", children: _jsxs("a", { href: templateCreateHref, target: "_blank", rel: "noopener noreferrer", className: "flex w-full items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm text-interactive transition-colors hover:bg-muted", children: [_jsx(PlusIcon, { size: 16 }), templateCreateLabel] }) })) : null] })) : null, renderSendAction({ compact: true })] })] })] }));
@@ -16,6 +16,8 @@ export interface RoutingActionPanelProps extends Omit<DashboardActionPanelProps,
16
16
  onRouteValueChange?: (value: string) => void;
17
17
  /** Workflow-specific fields rendered before the route target. */
18
18
  supplementaryFields?: React.ReactNode;
19
+ /** Read-only review context in the scrollable body, above routing activity. */
20
+ context?: React.ReactNode;
19
21
  remarks: string;
20
22
  remarksDescription?: React.ReactNode;
21
23
  remarksPlaceholder?: string;
@@ -34,5 +36,5 @@ export interface RoutingActionPanelProps extends Omit<DashboardActionPanelProps,
34
36
  * Consumers provide workflow data and actions; the component owns the complete
35
37
  * visual composition so different records render routing consistently.
36
38
  */
37
- declare function RoutingActionPanel({ items, isLoading, loadingLabel, emptyMessage, emptyDescription, routeOptions, routeValue, routePlaceholder, routeDescription, routeDisabled, onRouteValueChange, supplementaryFields, remarks, remarksDescription, remarksPlaceholder, remarksRequired, remarksRows, validationMessages, onRemarksChange, submitLabel, submitDisabled, submitLoading, onSubmit, ...panelProps }: RoutingActionPanelProps): import("react/jsx-runtime").JSX.Element;
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;
38
40
  export { RoutingActionPanel };
@@ -2,6 +2,7 @@
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";
5
6
  import { DashboardActionPanel, DashboardActionPanelEmptyState, DashboardRightPanelBody, } from "./layout";
6
7
  import { RoutingTimeline, } from "./routing-timeline";
7
8
  import { Select } from "./select";
@@ -12,8 +13,9 @@ import { Textarea } from "./textarea";
12
13
  * Consumers provide workflow data and actions; the component owns the complete
13
14
  * visual composition so different records render routing consistently.
14
15
  */
15
- 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, remarks, remarksDescription, remarksPlaceholder = "Type remarks", remarksRequired = false, remarksRows = 2, validationMessages = [], onRemarksChange, submitLabel = "Route", submitDisabled = false, submitLoading = false, onSubmit, ...panelProps }) {
16
- 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", disabled: routeDisabled || submitDisabled || validationMessages.length > 0, loading: submitLoading, onClick: onSubmit, children: [_jsx(Route, { "aria-hidden": "true", className: "size-4" }), submitLabel] }) })] }));
17
- return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", ...panelProps, children: _jsx(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", footerVariant: "separated", footer: footer, children: isLoading ? (_jsx("div", { "data-slot": "routing-action-panel-loading", className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "md", label: loadingLabel }) })) : items.length === 0 ? (_jsx(DashboardActionPanelEmptyState, { icon: Route, message: emptyMessage, description: emptyDescription })) : (_jsx(RoutingTimeline, { density: "compact", items: items })) }) }));
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", 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 }))] }) }));
18
20
  }
19
21
  export { RoutingActionPanel };
@@ -87,7 +87,7 @@ function RoutingTimeline({ items, emptyMessage = "No routing activity yet.", den
87
87
  ? "gap-[var(--space-3)]"
88
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
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(detailLabel &&
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
91
  (density === "compact"
92
92
  ? "mt-[var(--space-1)]"
93
93
  : "mt-[4px]")), children: remark })] })) : null] })) : null }) }, item.id));
package/dist/table.d.ts CHANGED
@@ -29,6 +29,7 @@ declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<
29
29
  wrap?: boolean;
30
30
  treatWidthAsMinWidth?: boolean;
31
31
  containOverflow?: boolean;
32
+ preserveButtonPadding?: boolean;
32
33
  } & React.RefAttributes<HTMLTableCellElement>>;
33
34
  declare const TableRowHeader: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & {
34
35
  width?: string;
@@ -93,6 +94,8 @@ export interface TableColumn<Row> {
93
94
  skeleton?: TableSkeletonConfig;
94
95
  headerClassName?: string;
95
96
  cellClassName?: string;
97
+ /** Keeps each design-system button's own horizontal padding in body cells. */
98
+ preserveButtonPadding?: boolean;
96
99
  /** Renders this column's body cells as semantic row headers. */
97
100
  rowHeader?: boolean;
98
101
  /** Pins the column to the left or right edge while the table scrolls horizontally. */
package/dist/table.js CHANGED
@@ -171,7 +171,8 @@ const TableHead = React.forwardRef(({ className, style, width, minWidth, maxWidt
171
171
  treatWidthAsMinWidth,
172
172
  }), ...props })));
173
173
  TableHead.displayName = "TableHead";
174
- const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-[4px] align-middle text-card-foreground [&:has([role=checkbox])]:pr-0 [&:not([data-slot=table-empty-cell])_[data-slot=button]]:px-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
174
+ const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, preserveButtonPadding = false, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-[4px] align-middle text-card-foreground [&:has([role=checkbox])]:pr-0", !preserveButtonPadding &&
175
+ "[&:not([data-slot=table-empty-cell])_[data-slot=button]]:px-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
175
176
  style,
176
177
  width,
177
178
  minWidth,
@@ -742,7 +743,7 @@ function Table({ accessibleLabel, columns, rows, getRowKey, emptyState = (_jsx(T
742
743
  const Cell = column.rowHeader
743
744
  ? TableRowHeader
744
745
  : TableCell;
745
- return (_jsx(Cell, { width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), wrap: column.wrap, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "cell", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.cellClassName), children: value }, column.key));
746
+ return (_jsx(Cell, { width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), wrap: column.wrap, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, preserveButtonPadding: column.preserveButtonPadding, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "cell", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.cellClassName), children: value }, column.key));
746
747
  })] }), isExpanded ? (_jsx(TableRow, { id: expandedContentId, "data-slot": "table-expanded-row", className: "bg-[hsl(var(--table-row-striped-background))] hover:bg-[hsl(var(--table-row-striped-background))]", children: _jsx(TableCell, { colSpan: Math.max(visibleColumns.length +
747
748
  (hasRowIndicatorColumn ? 1 : 0) +
748
749
  (hasExpansionColumn ? 1 : 0), 1), className: "h-auto whitespace-normal p-4", children: rowExpansion === null || rowExpansion === void 0 ? void 0 : rowExpansion.renderExpandedContent(row, rowIndex) }) })) : null] }, rowKey));
@@ -7,7 +7,7 @@ export interface TypographyProps extends React.HTMLAttributes<HTMLElement>, Vari
7
7
  as?: keyof React.JSX.IntrinsicElements;
8
8
  asChild?: boolean;
9
9
  /** Override the built-in text colour. Use "inherit" to inherit from a parent container. */
10
- color?: "inherit" | "foreground" | "muted";
10
+ color?: "inherit" | "foreground" | "muted" | "primary";
11
11
  }
12
12
  declare const Typography: React.ForwardRefExoticComponent<TypographyProps & React.RefAttributes<HTMLElement>>;
13
13
  export { Typography, typographyVariants };
@@ -39,9 +39,11 @@ const Typography = React.forwardRef(({ as, asChild = false, className, variant,
39
39
  ? "text-inherit"
40
40
  : color === "foreground"
41
41
  ? "text-foreground"
42
- : color === "muted"
43
- ? "text-instructional"
44
- : undefined;
42
+ : color === "primary"
43
+ ? "text-primary"
44
+ : color === "muted"
45
+ ? "text-instructional"
46
+ : undefined;
45
47
  return (_jsx(Comp, { ref: ref, className: cn(typographyVariants({ variant: resolvedVariant }), colorClass, className), ...props }));
46
48
  });
47
49
  Typography.displayName = "Typography";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neasg/design-system",
3
- "version": "0.4.121",
3
+ "version": "0.4.133",
4
4
  "description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
5
5
  "repository": {
6
6
  "type": "git",