@neasg/design-system 0.4.132 → 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.
@@ -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,18 +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: "grid w-full max-w-md grid-cols-1 gap-[var(--layout-page-control-gap)] sm:grid-cols-2", children: [_jsx(Select, { label: "Type", hideLabel: true, placeholder: "Type", options: typeOptions, value: type, onValueChange: onTypeChange, disabled: disabled || loading }), _jsx(Select, { label: "Sub-Type", hideLabel: true, placeholder: "Sub-Type", options: subtypeOptions, value: subtype, onValueChange: onSubtypeChange, disabled: disabled || loading || !type })] }), action] });
20
+ return _jsxs("div", { "data-slot": "checklist-selection", "aria-busy": loading, className: "flex flex-col items-center gap-4 rounded-lg bg-muted/30 p-6 text-center", children: [_jsx(Icon, { "aria-hidden": "true", className: cn("size-8", loading && "animate-spin") }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), _jsxs("div", { className: "grid w-full max-w-md grid-cols-1 gap-[var(--space-2)] sm:grid-cols-2", children: [_jsx(Select, { label: "Type", hideLabel: true, placeholder: "Type", options: typeOptions, value: type, onValueChange: onTypeChange, disabled: disabled || loading }), _jsx(Select, { label: "Sub-Type", hideLabel: true, placeholder: "Sub-Type", options: subtypeOptions, value: subtype, onValueChange: onSubtypeChange, disabled: disabled || loading || !type })] }), action] });
20
21
  }
21
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;
25
32
  const remarksVisible = disabled ? Boolean(remarks.trim()) : showRemarks;
26
33
  const evidenceVisible = disabled ? evidenceCount > 0 : showEvidence;
27
34
  const remarksToggleLabel = showRemarks ? "Hide remarks" : "Show remarks";
28
35
  const evidenceToggleLabel = showEvidence ? "Hide photos" : "Show photos";
29
- return _jsxs("div", { "data-slot": "checklist-question", className: "space-y-4 rounded-xl border border-border bg-card p-4 shadow-xs", children: [_jsx("p", { id: `${name}-label`, className: "text-sm font-medium", children: label }), _jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx("fieldset", { disabled: disabled, "aria-labelledby": `${name}-label`, className: cn("flex min-w-0 border-0 p-0", disabled && "opacity-50"), children: options.map((option, index) => _jsxs("label", { className: cn("relative cursor-pointer border border-input px-2.5 py-2 text-sm font-medium focus-within:z-10 focus-within:ring-2 focus-within:ring-ring", index > 0 && "-ml-px", index === 0 && "rounded-l-md", index === options.length - 1 && "rounded-r-md", disabled && "cursor-not-allowed", value === option.value ? option.tone === "negative" ? "bg-destructive/20" : option.tone === "positive" ? "bg-primary/20" : "bg-muted" : "bg-background"), children: [_jsx("input", { className: "absolute inset-0 size-full cursor-inherit opacity-0", type: "radio", name: name, value: option.value, checked: value === option.value, onChange: () => onValueChange(option.value) }), option.label] }, option.value)) }), !disabled ? _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tooltip, { content: remarksToggleLabel, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": remarksToggleLabel, "aria-expanded": showRemarks, "aria-controls": `${name}-remarks`, onClick: () => setShowRemarks((open) => !open), children: _jsx(MessageSquareText, { "aria-hidden": "true", size: 16 }) }) }), evidence ? _jsx(Tooltip, { content: `${evidenceToggleLabel}${evidenceCount > 0 ? ` (${evidenceCount})` : ""}`, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": evidenceToggleLabel, "aria-expanded": showEvidence, "aria-controls": `${name}-evidence`, onClick: () => setShowEvidence((open) => !open), children: _jsx(Camera, { "aria-hidden": "true", size: 16 }) }) }) : null] }) : null] }), evidenceVisible && evidence ? _jsx("div", { id: `${name}-evidence`, children: evidence }) : null, remarksVisible ? _jsx("div", { id: `${name}-remarks`, children: disabled ? _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: "Remarks" }), _jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: remarks })] }) : _jsx(Textarea, { label: "Remarks", hideLabel: true, placeholder: "Type...", value: remarks, "aria-required": remarksRequired, rows: 5, onChange: (event) => onRemarksChange(event.target.value), maxWords: remarksMaxLength === undefined ? remarksLimit : undefined, maxLength: remarksMaxLength, showCount: remarksMaxLength !== undefined }) }) : null] });
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] });
30
37
  }
31
38
  export function ChecklistAttachments({ items, onRemove }) {
32
- return _jsx("div", { className: "flex flex-wrap gap-2", children: items.map((item) => _jsxs("div", { className: "relative", children: [_jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: _jsx("img", { src: item.url, alt: item.name, className: "size-20 rounded-md border border-border object-cover" }) }), onRemove ? _jsx(Button, { variant: "outline", size: "compactIcon", "aria-label": `Remove ${item.name}`, onClick: () => onRemove(item.key), children: _jsx(Trash2, { "aria-hidden": "true", size: 16 }) }) : null] }, item.key)) });
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)) });
33
40
  }
package/dist/dialog.d.ts CHANGED
@@ -9,10 +9,10 @@ export interface DialogAction extends Omit<ButtonProps, "children"> {
9
9
  }
10
10
  type DialogRenderable = React.ReactNode | ((helpers: DialogRenderHelpers) => React.ReactNode);
11
11
  export interface DialogProps {
12
- variant?: "default" | "confirmation" | "routingConfirmation";
12
+ variant?: "default" | "confirmation";
13
13
  /** Standard dialogs are 512px wide; larger sizes are for complex editors. */
14
14
  size?: "standard" | "large" | "extraLarge" | "full";
15
- /** Optional minimum height for dialogs whose content changes between steps. */
15
+ /** Minimum height; defaults to 11.25rem (180px at the default root font size). */
16
16
  minHeight?: React.CSSProperties["minHeight"];
17
17
  open?: boolean;
18
18
  defaultOpen?: boolean;
package/dist/dialog.js CHANGED
@@ -32,7 +32,7 @@ function renderDialogAction({ action, close, defaultVariant, }) {
32
32
  }
33
33
  }, children: label }));
34
34
  }
35
- function Dialog({ variant = "default", size = "standard", minHeight, open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
35
+ function Dialog({ variant = "default", size = "standard", minHeight = "11.25rem", open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
36
36
  const [isOpen, setIsOpen] = useControllableOpen({
37
37
  open,
38
38
  defaultOpen,
@@ -45,15 +45,12 @@ function Dialog({ variant = "default", size = "standard", minHeight, open, defau
45
45
  const customFooter = resolveDialogRenderable(footer, helpers);
46
46
  const hasFooter = Boolean(customFooter || primaryAction || secondaryAction);
47
47
  const isCompactConfirmation = variant === "confirmation";
48
- const isRoutingConfirmation = variant === "routingConfirmation";
49
48
  return (_jsxs(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, modal: modal, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogSurface, { ...(!description ? { "aria-describedby": undefined } : {}), style: minHeight !== undefined ? { minHeight } : undefined, className: cn(isCompactConfirmation &&
50
- "gap-0 overflow-hidden rounded-xl p-0", isRoutingConfirmation &&
51
- "gap-0 rounded-overlay p-[var(--space-8)]", minHeight !== undefined && "flex flex-col", "w-[calc(100vw-32px)]", { standard: "max-w-[512px]", large: "max-w-3xl", extraLarge: "max-w-5xl", full: "max-w-[96rem]" }[size], contentClassName), hideCloseButton: hideCloseButton || isCompactConfirmation || isRoutingConfirmation, onPointerDownOutside: (event) => {
49
+ "gap-0 overflow-hidden rounded-xl p-0", minHeight !== undefined && "flex flex-col", "w-[calc(100vw-32px)]", { standard: "max-w-[512px]", large: "max-w-3xl", extraLarge: "max-w-5xl", full: "max-w-[96rem]" }[size], contentClassName), hideCloseButton: hideCloseButton || isCompactConfirmation, onPointerDownOutside: (event) => {
52
50
  if (!closeOnOutsideClick) {
53
51
  event.preventDefault();
54
52
  }
55
- }, children: [_jsxs("div", { className: cn(isCompactConfirmation && "flex items-start gap-4 p-4"), children: [isCompactConfirmation ? (_jsx("div", { className: "rounded-lg bg-muted p-2", children: _jsx(CircleAlert, { "aria-hidden": "true", className: "size-6" }) })) : null, _jsxs(DialogHeader, { className: cn(isRoutingConfirmation && "gap-3 space-y-0"), children: [_jsx(DialogTitle, { className: cn(isRoutingConfirmation && "text-2xl leading-8"), children: title }), description ? (_jsx(DialogDescription, { className: cn(isRoutingConfirmation &&
56
- "text-base leading-6 text-foreground"), children: description })) : null] })] }), body ? (_jsx("div", { className: cn("grid gap-4", minHeight !== undefined && "flex-1", isRoutingConfirmation && "mt-3 gap-[var(--space-8)]", bodyClassName), children: body })) : null, hasFooter ? (_jsx(DialogFooter, { className: cn(isCompactConfirmation && "border-t bg-muted/30 p-4", isRoutingConfirmation && "mt-[var(--space-8)] gap-3", minHeight !== undefined && "mt-auto"), children: customFooter !== null && customFooter !== void 0 ? customFooter : (_jsxs(_Fragment, { children: [secondaryAction
53
+ }, children: [_jsxs("div", { className: cn(isCompactConfirmation && "flex items-start gap-4 p-4"), children: [isCompactConfirmation ? (_jsx("div", { className: "rounded-lg bg-muted p-2", children: _jsx(CircleAlert, { "aria-hidden": "true", className: "size-6" }) })) : null, _jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), description ? (_jsx(DialogDescription, { children: description })) : null] })] }), body ? (_jsx("div", { className: cn("grid gap-4", minHeight !== undefined && "flex-1", bodyClassName), children: body })) : null, hasFooter ? (_jsx(DialogFooter, { className: cn(isCompactConfirmation && "border-t bg-muted/30 p-4", minHeight !== undefined && "mt-auto"), children: customFooter !== null && customFooter !== void 0 ? customFooter : (_jsxs(_Fragment, { children: [secondaryAction
57
54
  ? renderDialogAction({
58
55
  action: secondaryAction,
59
56
  close: helpers.close,
@@ -145,7 +145,7 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
145
145
  insertRow: (nextRow, index = rowIndex + 1) => handleInsertRow(nextRow, index),
146
146
  removeRow: () => handleDeleteRow(rowIndex),
147
147
  })
148
- : null, isEditing ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "text-muted-foreground hover:bg-destructive/10 hover:text-destructive focus-visible:text-destructive", onClick: () => handleDeleteRow(rowIndex), "aria-label": `Delete row ${rowIndex + 1}`, children: _jsx(TrashIcon, { size: 16 }) })) : null] }) })) : null] }, `row-${rowIndex}`)))) })] }) }), isEditing ? (_jsx(TableAddRow, { onClick: handleAddRow, children: addRowLabel })) : null] }));
148
+ : null, isEditing ? (_jsx(Button, { type: "button", variant: "destructiveGhost", size: "compactIcon", onClick: () => handleDeleteRow(rowIndex), "aria-label": `Delete row ${rowIndex + 1}`, children: _jsx(TrashIcon, { size: 16 }) })) : null] }) })) : null] }, `row-${rowIndex}`)))) })] }) }), isEditing ? (_jsx(TableAddRow, { onClick: handleAddRow, children: addRowLabel })) : null] }));
149
149
  }
150
150
  EditableTable.displayName = "EditableTable";
151
151
  export { EditableTable };
@@ -0,0 +1,15 @@
1
+ export interface UploadedFile {
2
+ id: string;
3
+ name: string;
4
+ type?: string;
5
+ size?: number | string;
6
+ href?: string;
7
+ }
8
+ export interface FileUploadListProps {
9
+ files: UploadedFile[];
10
+ onRemove?: (id: string) => void;
11
+ disabled?: boolean;
12
+ label?: string;
13
+ }
14
+ /** Saved files, using the same rows as the uploader. */
15
+ export declare function FileUploadList({ files, onRemove, disabled, label }: FileUploadListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { FileUploadStatusRow } from "./file-upload-status-row";
4
+ /** Saved files, using the same rows as the uploader. */
5
+ export function FileUploadList({ files, onRemove, disabled, label = "Uploaded files" }) {
6
+ return _jsx("ul", { "aria-label": label, className: "space-y-2", children: files.map(file => _jsx(FileUploadStatusRow, { file: { ...file, status: "complete" }, href: file.href, disabled: disabled, onRemove: onRemove ? () => onRemove(file.id) : undefined }, file.id)) });
7
+ }
@@ -1,5 +1,5 @@
1
1
  import type { FileUploadStatus } from "./use-file-upload";
2
- export declare function FileUploadStatusRow({ file, onCancel, onRemove, disabled, }: {
2
+ export declare function FileUploadStatusRow({ file, href, onCancel, onRemove, disabled, }: {
3
3
  file: {
4
4
  name: string;
5
5
  type?: string;
@@ -8,6 +8,8 @@ export declare function FileUploadStatusRow({ file, onCancel, onRemove, disabled
8
8
  progress?: number;
9
9
  error?: string;
10
10
  };
11
+ /** Optional destination for an uploaded file name. */
12
+ href?: string;
11
13
  onCancel?: () => void;
12
14
  onRemove?: () => void;
13
15
  disabled?: boolean;
@@ -27,7 +27,7 @@ function getStatusIcon(status, type) {
27
27
  const Icon = (type === null || type === void 0 ? void 0 : type.startsWith("image/")) ? ImageIcon : type === "application/pdf" ? FileText : File;
28
28
  return (_jsx(Icon, { "aria-hidden": "true", className: "size-5 text-muted-foreground" }));
29
29
  }
30
- export function FileUploadStatusRow({ file, onCancel, onRemove, disabled = false, }) {
30
+ export function FileUploadStatusRow({ file, href, onCancel, onRemove, disabled = false, }) {
31
31
  var _a;
32
32
  const isUploading = file.status === "uploading";
33
33
  const isPending = file.status === "pending";
@@ -39,7 +39,7 @@ export function FileUploadStatusRow({ file, onCancel, onRemove, disabled = false
39
39
  const action = canCancel ? "cancel" : canRemove ? "remove" : null;
40
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
41
  ? "grid-cols-[1.5rem_minmax(0,1fr)_auto_1.75rem]"
42
- : "grid-cols-[1.5rem_minmax(0,1fr)_auto]"), children: [_jsx("span", { className: "flex h-7 w-6 shrink-0 items-center justify-center", children: getStatusIcon(file.status, file.type) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1 py-1", children: [_jsx(Typography, { as: "p", variant: "label", className: cn("truncate", isComplete && "text-[hsl(var(--status-success-foreground))]"), title: file.name, children: file.name }), isError ? (_jsx(Typography, { as: "p", variant: "caption", className: "break-words text-xs leading-4 text-destructive", children: (_a = file.error) !== null && _a !== void 0 ? _a : "Upload failed" })) : null] }), sizeLabel ? (_jsx(Typography, { as: "span", variant: "bodySm", className: "col-start-3 mt-1.5 whitespace-nowrap text-xs leading-4 text-muted-foreground", children: sizeLabel })) : null, action ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "col-start-4 h-7 w-7 shrink-0", disabled: disabled, onClick: (event) => {
42
+ : "grid-cols-[1.5rem_minmax(0,1fr)_auto]"), children: [_jsx("span", { className: "flex h-7 w-6 shrink-0 items-center justify-center", children: getStatusIcon(file.status, file.type) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1 py-1", children: [href ? (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: "min-w-0 truncate 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) => {
43
43
  event.stopPropagation();
44
44
  if (action === "cancel") {
45
45
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
package/dist/index.d.ts CHANGED
@@ -174,3 +174,5 @@ export type { ChecklistAttachmentsProps, ChecklistPanelProps, ChecklistSectionPr
174
174
  export { TableAddRow, type TableAddRowProps } from "./table-add-row";
175
175
  export { PopoverList, type PopoverListProps } from "./popover";
176
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
@@ -93,3 +93,4 @@ export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelect
93
93
  export { TableAddRow } from "./table-add-row";
94
94
  export { PopoverList } from "./popover";
95
95
  export { StatusBanner } from "./status-banner";
96
+ export { FileUploadList } from "./file-upload-list";
@@ -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,
@@ -16,6 +16,6 @@ import { Textarea } from "./textarea";
16
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
17
  const RoutingEmptyState = context ? EmptyState : DashboardActionPanelEmptyState;
18
18
  const footer = (_jsxs("div", { "data-slot": "routing-action-panel-form", className: "grid gap-[var(--space-4)]", children: [supplementaryFields, _jsx(Select, { label: "Route To", options: routeOptions, value: routeValue, placeholder: routePlaceholder, disabled: routeDisabled || submitLoading, description: routeDescription, onValueChange: onRouteValueChange }), _jsx(Textarea, { label: "Remarks", value: remarks, onChange: (event) => onRemarksChange(event.target.value), placeholder: remarksPlaceholder, description: remarksDescription, disabled: routeDisabled || submitLoading, required: remarksRequired, rows: remarksRows }), validationMessages.length > 0 ? (_jsxs("details", { className: "text-sm text-muted-foreground", children: [_jsxs("summary", { className: "cursor-pointer font-medium text-foreground", children: [validationMessages.length, " required ", validationMessages.length === 1 ? "item" : "items", " remaining \u2014 view details"] }), _jsx("ul", { className: "mt-2 max-h-40 list-disc space-y-1 overflow-y-auto pl-5", children: validationMessages.map((message, index) => _jsx("li", { children: message }, index)) })] })) : null, _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", size: "default", disabled: routeDisabled || submitDisabled || validationMessages.length > 0, loading: submitLoading, loadingText: submitLabel, onClick: onSubmit, children: [_jsx(Route, { "aria-hidden": "true", className: "size-4" }), submitLabel] }) })] }));
19
- return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", ...panelProps, children: _jsxs(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", footerVariant: "separated", footer: footer, children: [context ? (_jsx("div", { "data-slot": "routing-action-panel-context", className: "mb-[var(--space-4)] grid min-w-0 gap-[var(--space-3)] whitespace-pre-wrap [overflow-wrap:anywhere]", children: context })) : null, isLoading ? (_jsx("div", { "data-slot": "routing-action-panel-loading", className: context ? "flex items-center justify-center py-[var(--space-4)]" : "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "md", label: loadingLabel }) })) : items.length === 0 ? (_jsx(RoutingEmptyState, { icon: Route, message: emptyMessage, description: emptyDescription })) : (_jsx(RoutingTimeline, { density: "compact", items: items }))] }) }));
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 }))] }) }));
20
20
  }
21
21
  export { RoutingActionPanel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neasg/design-system",
3
- "version": "0.4.132",
3
+ "version": "0.4.133",
4
4
  "description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
5
5
  "repository": {
6
6
  "type": "git",