@neasg/design-system 0.4.35 → 0.4.113
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/README.md +9 -0
- package/dist/activity-card.d.ts +31 -0
- package/dist/activity-card.js +65 -0
- package/dist/alert.d.ts +1 -1
- package/dist/autocomplete.d.ts +84 -0
- package/dist/autocomplete.js +267 -0
- package/dist/avatar.d.ts +3 -1
- package/dist/avatar.js +27 -10
- package/dist/badge.d.ts +1 -1
- package/dist/badge.js +1 -1
- package/dist/button.d.ts +2 -2
- package/dist/button.js +3 -1
- package/dist/calendar.js +1 -1
- package/dist/card.d.ts +1 -1
- package/dist/card.js +1 -1
- package/dist/changelog-action-panel.d.ts +17 -0
- package/dist/changelog-action-panel.js +14 -0
- package/dist/checklist.d.ts +63 -0
- package/dist/checklist.js +30 -0
- package/dist/compound-search-input.d.ts +10 -0
- package/dist/compound-search-input.js +19 -0
- package/dist/date-input.js +3 -3
- package/dist/dialog.d.ts +4 -1
- package/dist/dialog.js +7 -2
- package/dist/draggable-tabs.js +9 -8
- package/dist/dropdown-button.js +1 -0
- package/dist/editable-table.js +2 -2
- package/dist/field-counter.d.ts +7 -0
- package/dist/field-counter.js +5 -0
- package/dist/index.d.ts +22 -4
- package/dist/index.js +12 -2
- package/dist/input.d.ts +2 -0
- package/dist/input.js +11 -3
- package/dist/label-value-grid.js +6 -4
- package/dist/layout.d.ts +39 -6
- package/dist/layout.js +118 -42
- package/dist/lib/utils.js +1 -0
- package/dist/message-action-panel.d.ts +12 -0
- package/dist/message-action-panel.js +11 -0
- package/dist/message-item.d.ts +23 -3
- package/dist/message-item.js +115 -16
- package/dist/notification.d.ts +14 -3
- package/dist/notification.js +36 -7
- package/dist/page-section.d.ts +2 -1
- package/dist/page-section.js +2 -2
- package/dist/popover-menu.d.ts +4 -2
- package/dist/popover-menu.js +65 -5
- package/dist/popover.d.ts +10 -2
- package/dist/popover.js +7 -3
- package/dist/rich-text-editor.d.ts +2 -1
- package/dist/rich-text-editor.js +29 -6
- package/dist/routing-action-panel.d.ts +38 -0
- package/dist/routing-action-panel.js +19 -0
- package/dist/routing-timeline.d.ts +7 -2
- package/dist/routing-timeline.js +67 -29
- package/dist/search-input.d.ts +6 -0
- package/dist/search-input.js +6 -4
- package/dist/select-primitive.js +1 -1
- package/dist/select.d.ts +3 -1
- package/dist/select.js +9 -4
- package/dist/status-dot.d.ts +13 -0
- package/dist/status-dot.js +19 -0
- package/dist/styles.css +0 -19
- package/dist/table-add-row.d.ts +8 -0
- package/dist/table-add-row.js +7 -0
- package/dist/table-column-filter.js +7 -5
- package/dist/table.d.ts +9 -1
- package/dist/table.js +25 -11
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +6 -4
- package/dist/tailwind-preset.js +10 -0
- package/dist/textarea.d.ts +5 -0
- package/dist/textarea.js +18 -3
- package/dist/theme-switcher.d.ts +3 -1
- package/dist/theme-switcher.js +8 -2
- package/package.json +1 -1
package/dist/rich-text-editor.js
CHANGED
|
@@ -11,6 +11,7 @@ import { TextStyle } from "@tiptap/extension-text-style";
|
|
|
11
11
|
import { Color } from "@tiptap/extension-color";
|
|
12
12
|
import Highlight from "@tiptap/extension-highlight";
|
|
13
13
|
import Underline from "@tiptap/extension-underline";
|
|
14
|
+
import { Baseline, Reply } from "lucide-react";
|
|
14
15
|
import { BoldIcon, ChevronDownIcon, HighlighterIcon, ItalicIcon, LinkIcon, ListIcon, ListOrderedIcon, MaximizeIcon, PaletteIcon, PaperclipIcon, PlusIcon, SendIcon, StrikethroughIcon, UnderlineIcon, XIcon, } from "./animated-icons";
|
|
15
16
|
import { Button } from "./button";
|
|
16
17
|
import { DropdownButton } from "./dropdown-button";
|
|
@@ -19,6 +20,7 @@ import { inputControlPaddingClassName } from "./input-control";
|
|
|
19
20
|
import { Popover } from "./popover";
|
|
20
21
|
import { PopoverMenuItem, PopoverMenuSection } from "./popover-menu";
|
|
21
22
|
import { Tooltip } from "./tooltip";
|
|
23
|
+
import { Typography } from "./typography";
|
|
22
24
|
import { cn } from "./lib/utils";
|
|
23
25
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
24
26
|
const DEFAULT_MAX_FILE_SIZE_LABEL = "10MB";
|
|
@@ -125,7 +127,8 @@ function ToolbarIconButton({ active, label, onClick, children, }) {
|
|
|
125
127
|
function getTemplateCategory(template) {
|
|
126
128
|
return template.category || DEFAULT_TEMPLATE_CATEGORY;
|
|
127
129
|
}
|
|
128
|
-
export function RichTextEditor({ placeholder = "Type message here...", editorAriaLabel = "Message body", header, isExpanded = false, onExpandToggle, onSend, className, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxFileSizeLabel = DEFAULT_MAX_FILE_SIZE_LABEL, acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES, acceptedFileTypesLabel = DEFAULT_ACCEPTED_FILE_TYPES_LABEL, isSending = false, showTemplates = true, templates = [], templateContext = {}, templateCategoryLabels = {}, templateCategoryOrder, templateCreateHref, templateCreateLabel = "Create new template", onFileRejected, colorPalette = DEFAULT_COLOR_PALETTE, sendLabel = "Send", sendingLabel = "Sending...", sendActions, }) {
|
|
130
|
+
export function RichTextEditor({ variant = "default", placeholder = "Type message here...", editorAriaLabel = "Message body", header, isExpanded = false, onExpandToggle, onSend, className, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxFileSizeLabel = DEFAULT_MAX_FILE_SIZE_LABEL, acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES, acceptedFileTypesLabel = DEFAULT_ACCEPTED_FILE_TYPES_LABEL, isSending = false, showTemplates = true, templates = [], templateContext = {}, templateCategoryLabels = {}, templateCategoryOrder, templateCreateHref, templateCreateLabel = "Create new template", onFileRejected, colorPalette = DEFAULT_COLOR_PALETTE, sendLabel = "Send", sendingLabel = "Sending...", sendActions, }) {
|
|
131
|
+
var _a;
|
|
129
132
|
const [isEmpty, setIsEmpty] = React.useState(true);
|
|
130
133
|
const [attachments, setAttachments] = React.useState([]);
|
|
131
134
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
@@ -133,6 +136,7 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
|
|
|
133
136
|
const [linkUrl, setLinkUrl] = React.useState("");
|
|
134
137
|
const [linkMenuPosition, setLinkMenuPosition] = React.useState(null);
|
|
135
138
|
const [templatePopoverOpen, setTemplatePopoverOpen] = React.useState(false);
|
|
139
|
+
const [isFormattingOpen, setIsFormattingOpen] = React.useState(false);
|
|
136
140
|
const fileInputRef = React.useRef(null);
|
|
137
141
|
const dropZoneRef = React.useRef(null);
|
|
138
142
|
const linkInputRef = React.useRef(null);
|
|
@@ -192,7 +196,9 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
|
|
|
192
196
|
role: "textbox",
|
|
193
197
|
"aria-multiline": "true",
|
|
194
198
|
"aria-label": editorAriaLabel,
|
|
195
|
-
class: cn("prose prose-sm max-w-none focus:outline-none", "
|
|
199
|
+
class: cn("prose prose-sm max-w-none focus:outline-none", variant === "message"
|
|
200
|
+
? "min-h-[48px] px-[10px] py-[8px] text-sm"
|
|
201
|
+
: cn("min-h-[120px]", inputControlPaddingClassName)),
|
|
196
202
|
},
|
|
197
203
|
},
|
|
198
204
|
onUpdate: ({ editor }) => {
|
|
@@ -345,13 +351,30 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
|
|
|
345
351
|
setTemplatePopoverOpen(false);
|
|
346
352
|
};
|
|
347
353
|
const hasSendActions = Boolean(sendActions === null || sendActions === void 0 ? void 0 : sendActions.length);
|
|
348
|
-
const
|
|
354
|
+
const renderSendAction = ({ compact = false, fullWidth = false, } = {}) => hasSendActions ? (_jsx(DropdownButton, { className: fullWidth ? "w-full" : undefined, align: "end", disabled: sendDisabled, label: isSending ? sendingLabel : sendLabel, leadingIcon: _jsx(SendIcon, { size: 16 }), size: compact ? "sm" : undefined, split: true, onClick: () => {
|
|
349
355
|
void handleSend();
|
|
350
356
|
}, children: _jsx(PopoverMenuSection, { children: sendActions === null || sendActions === void 0 ? void 0 : sendActions.map((action) => (_jsx(PopoverMenuItem, { disabled: sendDisabled || action.disabled, onClick: () => {
|
|
351
357
|
void handleSend(action.metadata);
|
|
352
|
-
}, children: action.label }, action.id))) }) })) : (_jsxs(Button, { type: "button", className: "w-full", disabled: sendDisabled, onClick: () => {
|
|
358
|
+
}, children: action.label }, action.id))) }) })) : (_jsxs(Button, { type: "button", className: fullWidth ? "w-full" : undefined, size: compact ? "sm" : undefined, disabled: sendDisabled, "aria-label": isSending ? sendingLabel : sendLabel, onClick: () => {
|
|
353
359
|
void handleSend();
|
|
354
360
|
}, children: [_jsx(SendIcon, { size: 16 }), isSending ? sendingLabel : sendLabel] }));
|
|
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 &&
|
|
363
|
+
linkMenuPosition &&
|
|
364
|
+
typeof document !== "undefined" &&
|
|
365
|
+
createPortal(_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-[99]", onClick: () => {
|
|
366
|
+
setLinkPopoverOpen(false);
|
|
367
|
+
setLinkUrl("");
|
|
368
|
+
setLinkMenuPosition(null);
|
|
369
|
+
editor === null || editor === void 0 ? void 0 : editor.chain().focus().run();
|
|
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
|
+
top: linkMenuPosition.top,
|
|
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 }) => {
|
|
374
|
+
var _a;
|
|
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
|
+
}) }), 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 })] })] })] }));
|
|
377
|
+
}
|
|
355
378
|
return (_jsxs("div", { ref: dropZoneRef, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, className: cn("overflow-hidden rounded-control border bg-background transition-all focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50", isExpanded && "shadow-md", isDragging && "border-2 border-primary bg-primary/5", className), children: [header && (_jsxs("div", { className: cn("flex items-center justify-between border-b bg-muted dark:bg-muted/30", inputControlPaddingClassName), children: [_jsx("div", { className: "flex-1", children: header }), _jsx("div", { className: "flex items-center gap-2", children: onExpandToggle && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-6 w-6", onClick: onExpandToggle, "aria-label": "Expand editor", children: _jsx(MaximizeIcon, { size: 14 }) })) })] })), _jsxs("div", { className: cn("bg-background transition-all", isExpanded ? "min-h-[200px]" : "min-h-[120px]"), children: [_jsx(EditorContent, { editor: editor, className: cn("text-sm", "[&_.ProseMirror]:min-h-[120px] [&_.ProseMirror]:outline-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:text-muted-foreground", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:float-left", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:pointer-events-none", "[&_.ProseMirror_p.is-editor-empty:first-child::before]:h-0", "[&_.ProseMirror_ul]:my-2 [&_.ProseMirror_ul]:list-disc [&_.ProseMirror_ul]:pl-6", "[&_.ProseMirror_ol]:my-2 [&_.ProseMirror_ol]:list-decimal [&_.ProseMirror_ol]:pl-6", "[&_.ProseMirror_li]:my-1", "[&_.ProseMirror_ul_ul]:list-[circle] [&_.ProseMirror_ul_ul_ul]:list-[square]") }), linkPopoverOpen &&
|
|
356
379
|
linkMenuPosition &&
|
|
357
380
|
typeof document !== "undefined" &&
|
|
@@ -387,8 +410,8 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
|
|
|
387
410
|
else {
|
|
388
411
|
editor === null || editor === void 0 ? void 0 : editor.chain().focus().setHighlight({ color: color.value }).run();
|
|
389
412
|
}
|
|
390
|
-
}, children: color.value === "" && (_jsx("span", { className: "text-xs text-muted-foreground", children: "A" })) }, `bg-${color.name}`))) }) }), _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 }) }), _jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 14 }) }) }), _jsx(ToolbarIconButton, { label: "Add link", active: editor === null || editor === void 0 ? void 0 : editor.isActive("link"), onClick: openLinkMenu, children: _jsx(LinkIcon, { size: 14 }) }), showTemplates && templates.length > 0 && (_jsx(_Fragment, { children: _jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "ghost", className: "h-8 gap-1 text-muted-foreground", children: ["Template", _jsx(ChevronDownIcon, { size: 14 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
|
|
413
|
+
}, children: color.value === "" && (_jsx("span", { className: "text-xs text-muted-foreground", children: "A" })) }, `bg-${color.name}`))) }) }), _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 }) }), _jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 14 }) }) }), _jsx(ToolbarIconButton, { label: "Add link", active: editor === null || editor === void 0 ? void 0 : editor.isActive("link"), onClick: openLinkMenu, children: _jsx(LinkIcon, { size: 14 }) }), showTemplates && (templates.length > 0 || templateCreateHref) && (_jsx(_Fragment, { children: _jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "ghost", className: "h-8 gap-1 text-muted-foreground", children: ["Template", _jsx(ChevronDownIcon, { size: 14 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
|
|
391
414
|
var _a;
|
|
392
415
|
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 }))] }, template.id)))] }, category));
|
|
393
|
-
}) }), 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] }) }))] }) }))] }) }), _jsx("div", { className: "bg-muted px-3 pb-2 pt-1 dark:bg-background", children:
|
|
416
|
+
}) }), 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] }) }))] }) }))] }) }), _jsx("div", { className: "bg-muted px-3 pb-2 pt-1 dark:bg-background", children: renderSendAction({ fullWidth: true }) })] }));
|
|
394
417
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type DashboardActionPanelProps } from "./layout";
|
|
3
|
+
import { type RoutingTimelineItem } from "./routing-timeline";
|
|
4
|
+
import { type SelectOption } from "./select";
|
|
5
|
+
export interface RoutingActionPanelProps extends Omit<DashboardActionPanelProps, "children"> {
|
|
6
|
+
items: RoutingTimelineItem[];
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
loadingLabel?: string;
|
|
9
|
+
emptyMessage?: React.ReactNode;
|
|
10
|
+
emptyDescription?: React.ReactNode;
|
|
11
|
+
routeOptions: SelectOption[];
|
|
12
|
+
routeValue?: string;
|
|
13
|
+
routePlaceholder?: string;
|
|
14
|
+
routeDescription?: React.ReactNode;
|
|
15
|
+
routeDisabled?: boolean;
|
|
16
|
+
onRouteValueChange?: (value: string) => void;
|
|
17
|
+
/** Workflow-specific fields rendered before the route target. */
|
|
18
|
+
supplementaryFields?: React.ReactNode;
|
|
19
|
+
remarks: string;
|
|
20
|
+
remarksDescription?: React.ReactNode;
|
|
21
|
+
remarksPlaceholder?: string;
|
|
22
|
+
remarksRequired?: boolean;
|
|
23
|
+
remarksRows?: number;
|
|
24
|
+
/** Blocking validation messages, collapsed above the submit action. */
|
|
25
|
+
validationMessages?: readonly string[];
|
|
26
|
+
onRemarksChange: (value: string) => void;
|
|
27
|
+
submitLabel?: React.ReactNode;
|
|
28
|
+
submitDisabled?: boolean;
|
|
29
|
+
submitLoading?: boolean;
|
|
30
|
+
onSubmit: () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Shared routing surface for constrained dashboard popovers and right panels.
|
|
34
|
+
* Consumers provide workflow data and actions; the component owns the complete
|
|
35
|
+
* visual composition so different records render routing consistently.
|
|
36
|
+
*/
|
|
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;
|
|
38
|
+
export { RoutingActionPanel };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Route } from "lucide-react";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { DashboardActionPanel, DashboardActionPanelEmptyState, DashboardRightPanelBody, } from "./layout";
|
|
6
|
+
import { RoutingTimeline, } from "./routing-timeline";
|
|
7
|
+
import { Select } from "./select";
|
|
8
|
+
import { Spinner } from "./spinner";
|
|
9
|
+
import { Textarea } from "./textarea";
|
|
10
|
+
/**
|
|
11
|
+
* Shared routing surface for constrained dashboard popovers and right panels.
|
|
12
|
+
* Consumers provide workflow data and actions; the component owns the complete
|
|
13
|
+
* visual composition so different records render routing consistently.
|
|
14
|
+
*/
|
|
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 })) }) }));
|
|
18
|
+
}
|
|
19
|
+
export { RoutingActionPanel };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
export type RoutingTimelineItemKind = "assigned" | "reassigned" | "returned" | "routed" | "system";
|
|
2
|
+
export type RoutingTimelineItemKind = "assigned" | "reassigned" | "returned" | "routed" | "message-received" | "message-sent" | "system";
|
|
3
3
|
export interface RoutingTimelineItem {
|
|
4
4
|
id: string;
|
|
5
5
|
kind?: RoutingTimelineItemKind;
|
|
@@ -8,15 +8,20 @@ export interface RoutingTimelineItem {
|
|
|
8
8
|
actor?: React.ReactNode;
|
|
9
9
|
remark?: React.ReactNode;
|
|
10
10
|
description?: React.ReactNode;
|
|
11
|
+
detailLabel?: React.ReactNode;
|
|
11
12
|
from?: React.ReactNode;
|
|
12
13
|
to?: React.ReactNode;
|
|
13
14
|
fromLabel?: React.ReactNode;
|
|
14
15
|
toLabel?: React.ReactNode;
|
|
15
16
|
badgeLabel?: React.ReactNode;
|
|
17
|
+
badge?: React.ReactNode;
|
|
18
|
+
icon?: React.ReactNode;
|
|
16
19
|
}
|
|
17
20
|
export interface RoutingTimelineProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
18
21
|
items: RoutingTimelineItem[];
|
|
19
22
|
emptyMessage?: React.ReactNode;
|
|
23
|
+
/** Reduces typography, icon, padding, and event spacing for constrained panels. */
|
|
24
|
+
density?: "default" | "compact";
|
|
20
25
|
}
|
|
21
|
-
declare function RoutingTimeline({ items, emptyMessage, className, ...props }: RoutingTimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
declare function RoutingTimeline({ items, emptyMessage, density, className, ...props }: RoutingTimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
22
27
|
export { RoutingTimeline };
|
package/dist/routing-timeline.js
CHANGED
|
@@ -1,58 +1,96 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowRight, Mail, Route } from "lucide-react";
|
|
3
|
+
import { ActivityCard, ActivityCardContent } from "./activity-card";
|
|
4
|
+
import { AvatarProfile } from "./avatar";
|
|
3
5
|
import { Badge } from "./badge";
|
|
4
6
|
import { EmptyState } from "./empty-state";
|
|
5
7
|
import { cn } from "./lib/utils";
|
|
6
8
|
import { Typography } from "./typography";
|
|
7
9
|
const routingKindConfig = {
|
|
8
10
|
assigned: {
|
|
9
|
-
title: "Assigned
|
|
10
|
-
label: "
|
|
11
|
-
|
|
12
|
-
markerClassName: "border-[hsl(var(--status-info-foreground)/0.35)] bg-[hsl(var(--status-info))]",
|
|
11
|
+
title: "Assigned case",
|
|
12
|
+
label: "ASSIGNED",
|
|
13
|
+
icon: "route",
|
|
13
14
|
},
|
|
14
15
|
reassigned: {
|
|
15
|
-
title: "Reassigned
|
|
16
|
-
label: "
|
|
17
|
-
|
|
18
|
-
markerClassName: "border-[hsl(var(--status-accent-foreground)/0.35)] bg-[hsl(var(--status-accent))]",
|
|
16
|
+
title: "Reassigned case",
|
|
17
|
+
label: "REASSIGNED",
|
|
18
|
+
icon: "route",
|
|
19
19
|
},
|
|
20
20
|
returned: {
|
|
21
|
-
title: "
|
|
22
|
-
label: "
|
|
23
|
-
|
|
24
|
-
markerClassName: "border-[hsl(var(--status-warning-foreground)/0.35)] bg-[hsl(var(--status-warning))]",
|
|
21
|
+
title: "Case returned",
|
|
22
|
+
label: "RETURNED",
|
|
23
|
+
icon: "route",
|
|
25
24
|
},
|
|
26
25
|
routed: {
|
|
27
|
-
title: "Case
|
|
28
|
-
label: "
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
title: "Case routed",
|
|
27
|
+
label: "ROUTED",
|
|
28
|
+
icon: "route",
|
|
29
|
+
},
|
|
30
|
+
"message-received": {
|
|
31
|
+
title: "Received from applicant",
|
|
32
|
+
label: "RECEIVED",
|
|
33
|
+
detailLabel: "Response from applicant:",
|
|
34
|
+
icon: "mail",
|
|
35
|
+
},
|
|
36
|
+
"message-sent": {
|
|
37
|
+
title: "Sent to applicant",
|
|
38
|
+
label: "SENT",
|
|
39
|
+
detailLabel: "Officer sent a message:",
|
|
40
|
+
icon: "mail",
|
|
31
41
|
},
|
|
32
42
|
system: {
|
|
33
|
-
title: "System
|
|
34
|
-
label: "
|
|
35
|
-
|
|
36
|
-
markerClassName: "border-muted-foreground/30 bg-muted",
|
|
43
|
+
title: "System update",
|
|
44
|
+
label: "SYSTEM",
|
|
45
|
+
icon: "route",
|
|
37
46
|
},
|
|
38
47
|
};
|
|
39
|
-
function RoutingTimelineHandoffEndpoint({ value, }) {
|
|
48
|
+
function RoutingTimelineHandoffEndpoint({ value, label, }) {
|
|
40
49
|
const isTextValue = typeof value === "string" || typeof value === "number";
|
|
41
|
-
|
|
50
|
+
if (!isTextValue) {
|
|
51
|
+
return (_jsx("div", { "data-slot": "routing-timeline-profile", "data-avatar-size": "xs", className: "min-w-0", children: value }));
|
|
52
|
+
}
|
|
53
|
+
const name = String(value);
|
|
54
|
+
const fallback = name
|
|
55
|
+
.split(/\s+/)
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.slice(0, 2)
|
|
58
|
+
.map((part) => part.charAt(0))
|
|
59
|
+
.join("")
|
|
60
|
+
.toUpperCase();
|
|
61
|
+
return (_jsx("div", { "data-slot": "routing-timeline-profile", "data-avatar-size": "xs", className: "min-w-0", children: _jsx(AvatarProfile, { size: "xs", fallback: fallback || "?", name: name, role: label, rolePlacement: "below" }) }));
|
|
62
|
+
}
|
|
63
|
+
function RoutingEventIcon({ icon, kind, density, }) {
|
|
64
|
+
if (icon)
|
|
65
|
+
return _jsx(_Fragment, { children: icon });
|
|
66
|
+
return routingKindConfig[kind].icon === "mail" ? (_jsx(Mail, { "aria-hidden": "true", className: density === "compact" ? "size-4" : "size-6" })) : (_jsx(Route, { "aria-hidden": "true", className: density === "compact" ? "size-4" : "size-6" }));
|
|
42
67
|
}
|
|
43
|
-
function RoutingTimeline({ items, emptyMessage = "No routing activity yet.", className, ...props }) {
|
|
68
|
+
function RoutingTimeline({ items, emptyMessage = "No routing activity yet.", density = "default", className, ...props }) {
|
|
44
69
|
if (items.length === 0) {
|
|
45
|
-
return (_jsx(EmptyState, { "data-slot": "routing-timeline", message: emptyMessage, className: cn("border-0 bg-transparent px-0
|
|
70
|
+
return (_jsx(EmptyState, { "data-slot": "routing-timeline", message: emptyMessage, className: cn("border-0 bg-transparent px-0", density === "compact"
|
|
71
|
+
? "py-[var(--space-4)]"
|
|
72
|
+
: "py-[var(--space-6)]", className), ...props }));
|
|
46
73
|
}
|
|
47
|
-
return (_jsx("div", { "data-slot": "routing-timeline", className: cn("min-w-0", className), ...props, children: _jsx("ol", { "data-slot": "routing-timeline-list", className:
|
|
48
|
-
var
|
|
74
|
+
return (_jsx("div", { "data-slot": "routing-timeline", "data-density": density, className: cn("min-w-0", className), ...props, children: _jsx("ol", { "data-slot": "routing-timeline-list", className: cn(density === "compact"
|
|
75
|
+
? "space-y-[var(--space-4)]"
|
|
76
|
+
: "space-y-[32px]"), children: items.map((item) => {
|
|
77
|
+
var _a, _b, _c, _d, _e;
|
|
49
78
|
const kind = (_a = item.kind) !== null && _a !== void 0 ? _a : "routed";
|
|
50
79
|
const config = routingKindConfig[kind];
|
|
51
80
|
const hasRouteEndpoints = Boolean(item.from || item.to);
|
|
52
81
|
const hasCompleteRoute = Boolean(item.from && item.to);
|
|
53
82
|
const title = (_b = item.title) !== null && _b !== void 0 ? _b : config.title;
|
|
54
83
|
const remark = (_c = item.remark) !== null && _c !== void 0 ? _c : item.description;
|
|
55
|
-
|
|
84
|
+
const detailLabel = (_d = item.detailLabel) !== null && _d !== void 0 ? _d : config.detailLabel;
|
|
85
|
+
const showDefaultBadge = item.badgeLabel !== null;
|
|
86
|
+
return (_jsx("li", { children: _jsx(ActivityCard, { "data-slot": "routing-timeline-event-card", density: density, icon: _jsx(RoutingEventIcon, { icon: item.icon, kind: kind, density: density }), title: title, meta: item.timestamp, trailing: item.badge ? (item.badge) : showDefaultBadge ? (_jsx(Badge, { variant: "outline", bordered: true, className: "max-w-full shrink-0", children: (_e = item.badgeLabel) !== null && _e !== void 0 ? _e : config.label })) : null, children: hasRouteEndpoints || remark ? (_jsxs(ActivityCardContent, { density: density, children: [hasRouteEndpoints ? (_jsxs("div", { "data-slot": "routing-timeline-handoff", "data-complete-route": hasCompleteRoute ? "true" : undefined, className: cn("flex min-w-0 items-center", density === "compact"
|
|
87
|
+
? "gap-[var(--space-3)]"
|
|
88
|
+
: "gap-[24px]"), children: [item.from ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.from, label: item.fromLabel })) : null, item.from && item.to ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "sr-only", children: "to" }), _jsx(ArrowRight, { "aria-hidden": "true", "data-slot": "routing-timeline-handoff-arrow", className: cn("shrink-0 text-foreground", density === "compact" ? "size-4" : "size-6") })] })) : null, item.to ? (_jsx(RoutingTimelineHandoffEndpoint, { value: item.to, label: item.toLabel })) : null] })) : null, remark ? (_jsxs("div", { "data-slot": "routing-timeline-remark", className: cn("bg-muted", density === "compact"
|
|
89
|
+
? "rounded-control p-[var(--space-2)]"
|
|
90
|
+
: "rounded-md p-[12px]"), children: [detailLabel ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "font-semibold text-instructional", children: detailLabel })) : null, _jsx(Typography, { as: "div", variant: "bodySm", className: cn(detailLabel &&
|
|
91
|
+
(density === "compact"
|
|
92
|
+
? "mt-[var(--space-1)]"
|
|
93
|
+
: "mt-[4px]")), children: remark })] })) : null] })) : null }) }, item.id));
|
|
56
94
|
}) }) }));
|
|
57
95
|
}
|
|
58
96
|
export { RoutingTimeline };
|
package/dist/search-input.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { InputControl } from "./input-control";
|
|
3
3
|
export interface SearchInputProps extends Omit<React.ComponentProps<typeof InputControl>, "onChange" | "type" | "value"> {
|
|
4
|
+
/** Display a character counter when maxLength is set. */
|
|
5
|
+
showCount?: boolean;
|
|
4
6
|
value: string;
|
|
5
7
|
onValueChange: (value: string) => void;
|
|
6
8
|
label?: React.ReactNode;
|
|
9
|
+
/** Visually hides the label while keeping it available to assistive technology. */
|
|
10
|
+
hideLabel?: boolean;
|
|
7
11
|
description?: React.ReactNode;
|
|
8
12
|
error?: React.ReactNode;
|
|
9
13
|
invalid?: boolean;
|
|
14
|
+
/** Keeps the control visually marked after a search result is selected. */
|
|
15
|
+
selected?: boolean;
|
|
10
16
|
containerClassName?: string;
|
|
11
17
|
labelClassName?: string;
|
|
12
18
|
descriptionClassName?: string;
|
package/dist/search-input.js
CHANGED
|
@@ -4,9 +4,10 @@ import * as React from "react";
|
|
|
4
4
|
import { XIcon } from "./animated-icons/x";
|
|
5
5
|
import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
6
6
|
import { InputControl } from "./input-control";
|
|
7
|
+
import { FieldCounter } from "./field-counter";
|
|
7
8
|
import { cn } from "./lib/utils";
|
|
8
9
|
import { SearchInputShell } from "./search-input-shell";
|
|
9
|
-
const SearchInput = React.forwardRef(({ value, onValueChange, label = "Search", description, error, invalid = false, placeholder = "Search...", containerClassName, labelClassName, descriptionClassName, errorClassName, onSearch, searchDelay = 300, searchOnChange = true, searchOnEnter = true, className, id, disabled, onKeyDown, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, ...props }, ref) => {
|
|
10
|
+
const SearchInput = React.forwardRef(({ value, showCount = false, onValueChange, label = "Search", hideLabel = true, description, error, invalid = false, selected = false, placeholder = "Search...", containerClassName, labelClassName, descriptionClassName, errorClassName, onSearch, searchDelay = 300, searchOnChange = true, searchOnEnter = true, className, id, disabled, onKeyDown, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, ...props }, ref) => {
|
|
10
11
|
const searchTimeoutRef = React.useRef();
|
|
11
12
|
React.useEffect(() => {
|
|
12
13
|
return () => {
|
|
@@ -17,6 +18,7 @@ const SearchInput = React.forwardRef(({ value, onValueChange, label = "Search",
|
|
|
17
18
|
}, []);
|
|
18
19
|
const generatedId = React.useId();
|
|
19
20
|
const inputId = id !== null && id !== void 0 ? id : `search-input-${generatedId}`;
|
|
21
|
+
const limitId = showCount && props.maxLength !== undefined ? `${inputId}-limit` : undefined;
|
|
20
22
|
const errorId = error ? `${inputId}-error` : undefined;
|
|
21
23
|
const showDescription = Boolean(description) && !error;
|
|
22
24
|
const descriptionId = showDescription ? `${inputId}-description` : undefined;
|
|
@@ -24,7 +26,7 @@ const SearchInput = React.forwardRef(({ value, onValueChange, label = "Search",
|
|
|
24
26
|
ariaInvalid === true ||
|
|
25
27
|
ariaInvalid === "true" ||
|
|
26
28
|
Boolean(error);
|
|
27
|
-
const describedBy = [ariaDescribedBy, descriptionId, errorId]
|
|
29
|
+
const describedBy = [ariaDescribedBy, descriptionId, errorId, limitId]
|
|
28
30
|
.filter(Boolean)
|
|
29
31
|
.join(" ") || undefined;
|
|
30
32
|
const runSearch = React.useCallback((nextValue) => {
|
|
@@ -64,10 +66,10 @@ const SearchInput = React.forwardRef(({ value, onValueChange, label = "Search",
|
|
|
64
66
|
}
|
|
65
67
|
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
66
68
|
}, [onKeyDown, runSearch, searchOnEnter]);
|
|
67
|
-
return (_jsxs(Field, { className: containerClassName, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn("sr-only", labelClassName), children: label }), _jsxs(SearchInputShell, { children: [_jsx(InputControl, { ref: ref, id: inputId, type: "text", placeholder: placeholder, value: value, onChange: handleChange, onKeyDown: handleKeyDown, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": describedBy, className: cn("bg-background pl-8 pr-7", className), ...props }), value ? (_jsx("button", { type: "button", onClick: () => {
|
|
69
|
+
return (_jsxs(Field, { className: containerClassName, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(SearchInputShell, { children: [_jsx(InputControl, { ref: ref, id: inputId, type: "text", placeholder: placeholder, value: value, onChange: handleChange, onKeyDown: handleKeyDown, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": describedBy, "data-selected": selected ? "true" : undefined, className: cn("bg-background pl-8 pr-7 data-[selected=true]:border-interactive data-[selected=true]:bg-highlight data-[selected=true]:ring-[3px] data-[selected=true]:ring-interactive/20", className), ...props }), value ? (_jsx("button", { type: "button", onClick: () => {
|
|
68
70
|
onValueChange("");
|
|
69
71
|
runSearch("");
|
|
70
|
-
}, disabled: disabled, className: "absolute right-1.5 top-1/2 -translate-y-1/2 cursor-pointer rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:pointer-events-none", "aria-label": "Clear search", children: _jsx(XIcon, { size: 14 }) })) : null] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
72
|
+
}, disabled: disabled, className: "absolute right-1.5 top-1/2 -translate-y-1/2 cursor-pointer rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:pointer-events-none", "aria-label": "Clear search", children: _jsx(XIcon, { size: 14 }) })) : null] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, limitId && _jsx(FieldCounter, { id: limitId, count: value.length, limit: props.maxLength }), _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
71
73
|
});
|
|
72
74
|
SearchInput.displayName = "SearchInput";
|
|
73
75
|
export { SearchInput };
|
package/dist/select-primitive.js
CHANGED
|
@@ -36,7 +36,7 @@ const SelectContent = React.forwardRef(({ className, children, header, position
|
|
|
36
36
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
37
37
|
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.Label, { ref: ref, "data-slot": "select-label", className: cn("px-2 py-1.5 text-xs text-muted-foreground", className), ...props })));
|
|
38
38
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
39
|
-
const SelectItem = React.forwardRef(({ className, children, description, ...props }, ref) => (_jsxs(SelectPrimitive.Item, { ref: ref, "data-slot": "select-item", className: cn("relative flex min-h-8 w-full cursor-pointer select-none items-center gap-1.5 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[state=checked]:bg-highlight data-[state=checked]:text-highlight-foreground data-[state=checked]:hover:bg-highlight/80 data-[state=checked]:hover:text-highlight-foreground data-[state=checked]:focus:bg-highlight/80 data-[state=checked]:focus:text-highlight-foreground data-[disabled]:
|
|
39
|
+
const SelectItem = React.forwardRef(({ className, children, description, ...props }, ref) => (_jsxs(SelectPrimitive.Item, { ref: ref, "data-slot": "select-item", className: cn("relative flex min-h-8 w-full cursor-pointer select-none items-center gap-1.5 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[state=checked]:bg-highlight data-[state=checked]:text-highlight-foreground data-[state=checked]:hover:bg-highlight/80 data-[state=checked]:hover:text-highlight-foreground data-[state=checked]:focus:bg-highlight/80 data-[state=checked]:focus:text-highlight-foreground data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:hover:bg-transparent data-[disabled]:hover:text-foreground", className), ...props, children: [_jsx("span", { "data-slot": "select-item-indicator", className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx(SelectPrimitive.ItemText, { className: "break-words font-normal", children: children }), description ? (_jsx(Typography, { as: "span", variant: "caption", children: description })) : null] })] })));
|
|
40
40
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
41
41
|
const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.Separator, { ref: ref, "data-slot": "select-separator", className: cn("-mx-1 my-1 h-px bg-border", className), ...props })));
|
|
42
42
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
package/dist/select.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface SelectOption {
|
|
|
5
5
|
value: string;
|
|
6
6
|
description?: React.ReactNode;
|
|
7
7
|
disabled?: boolean;
|
|
8
|
+
disabledReason?: React.ReactNode;
|
|
8
9
|
group?: string;
|
|
9
10
|
textValue?: string;
|
|
10
11
|
}
|
|
@@ -12,6 +13,7 @@ export interface SelectProps extends Pick<React.ComponentProps<typeof SelectRoot
|
|
|
12
13
|
label: React.ReactNode;
|
|
13
14
|
options: SelectOption[];
|
|
14
15
|
placeholder?: string;
|
|
16
|
+
size?: "default" | "comfortable";
|
|
15
17
|
description?: React.ReactNode;
|
|
16
18
|
error?: React.ReactNode;
|
|
17
19
|
invalid?: boolean;
|
|
@@ -28,5 +30,5 @@ export interface SelectProps extends Pick<React.ComponentProps<typeof SelectRoot
|
|
|
28
30
|
errorClassName?: string;
|
|
29
31
|
id?: string;
|
|
30
32
|
}
|
|
31
|
-
declare function Select({ label, options, placeholder, description, error, invalid, hideLabel, searchable, searchPlaceholder, searchEmptyMessage, searchInputAriaLabel, className, triggerClassName, contentClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, open, onOpenChange, ...props }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
declare function Select({ label, options, placeholder, size, description, error, invalid, hideLabel, searchable, searchPlaceholder, searchEmptyMessage, searchInputAriaLabel, className, triggerClassName, contentClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, open, onOpenChange, ...props }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
32
34
|
export { Select };
|
package/dist/select.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
|
5
5
|
import { InputControl } from "./input-control";
|
|
6
6
|
import { cn } from "./lib/utils";
|
|
7
7
|
import { usePopoverTabExit } from "./popover-focus";
|
|
8
|
+
import { Tooltip } from "./tooltip";
|
|
8
9
|
import { SearchHighlight } from "./use-search-highlight";
|
|
9
10
|
import { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectRoot, SelectSeparator, SelectTrigger, SelectValue, } from "./select-primitive";
|
|
10
11
|
function groupOptions(options) {
|
|
@@ -40,7 +41,7 @@ function renderHighlightedLabel(label, query) {
|
|
|
40
41
|
}
|
|
41
42
|
return label;
|
|
42
43
|
}
|
|
43
|
-
function Select({ label, options, placeholder = "Select an option", description, error, invalid = false, hideLabel = false, searchable = false, searchPlaceholder = "Search options...", searchEmptyMessage = "No options found.", searchInputAriaLabel = "Search options", className, triggerClassName, contentClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, open, onOpenChange, ...props }) {
|
|
44
|
+
function Select({ label, options, placeholder = "Select an option", size = "default", description, error, invalid = false, hideLabel = false, searchable = false, searchPlaceholder = "Search options...", searchEmptyMessage = "No options found.", searchInputAriaLabel = "Search options", className, triggerClassName, contentClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, open, onOpenChange, ...props }) {
|
|
44
45
|
const generatedId = React.useId();
|
|
45
46
|
const selectId = id !== null && id !== void 0 ? id : `select-${generatedId}`;
|
|
46
47
|
const triggerRef = React.useRef(null);
|
|
@@ -153,7 +154,8 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
153
154
|
searchable,
|
|
154
155
|
searchQuery,
|
|
155
156
|
]);
|
|
156
|
-
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: selectId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(SelectRoot, { disabled: disabled, open: isOpen, onOpenChange: handleOpenChange, ...props, children: [_jsx(SelectTrigger, { ref: triggerRef, id: selectId, className:
|
|
157
|
+
return (_jsxs(Field, { className: cn(size === "comfortable" && "gap-[8px]", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: selectId, className: cn(size === "comfortable" && "text-[16px] leading-[24px]", hideLabel && "sr-only", labelClassName), children: label }), _jsxs(SelectRoot, { disabled: disabled, open: isOpen, onOpenChange: handleOpenChange, ...props, children: [_jsx(SelectTrigger, { ref: triggerRef, id: selectId, className: cn(size === "comfortable" &&
|
|
158
|
+
"h-[46px] px-[16px] py-[11px] text-[16px] leading-[24px]", triggerClassName), "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { ref: contentRef, className: contentClassName, onCloseAutoFocus: handleCloseAutoFocus, onKeyDownCapture: handleContentKeyDownCapture, header: searchable ? (_jsx(InputControl, { ref: searchInputRef, value: searchQuery, onChange: (event) => setSearchQuery(event.target.value), placeholder: searchPlaceholder, "aria-label": searchInputAriaLabel, autoComplete: "off", className: "h-control border-0 bg-transparent px-2 py-0 text-sm shadow-none", onKeyDown: (event) => {
|
|
157
159
|
if (event.key === "ArrowDown" ||
|
|
158
160
|
event.key === "ArrowUp") {
|
|
159
161
|
event.preventDefault();
|
|
@@ -167,7 +169,10 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
167
169
|
event.stopPropagation();
|
|
168
170
|
} })) : undefined, viewportClassName: searchable ? "max-h-64" : undefined, children: hasVisibleOptions ? (optionGroups.map((group, groupIndex) => {
|
|
169
171
|
var _a;
|
|
170
|
-
return (_jsxs(React.Fragment, { children: [_jsxs(SelectGroup, { children: [group.group ? (_jsx(SelectLabel, { children: _jsx(SearchHighlight, { text: group.group, query: searchQuery }) })) : null, group.options.map((option) =>
|
|
171
|
-
|
|
172
|
+
return (_jsxs(React.Fragment, { children: [_jsxs(SelectGroup, { children: [group.group ? (_jsx(SelectLabel, { children: _jsx(SearchHighlight, { text: group.group, query: searchQuery }) })) : null, group.options.map((option) => {
|
|
173
|
+
const optionItem = (_jsx(SelectItem, { value: option.value, disabled: option.disabled, textValue: getOptionTextValue(option), description: option.description, children: renderHighlightedLabel(option.label, searchQuery) }));
|
|
174
|
+
return option.disabled && option.disabledReason ? (_jsx(Tooltip, { content: option.disabledReason, side: "right", children: optionItem }, option.value)) : (_jsx(React.Fragment, { children: optionItem }, option.value));
|
|
175
|
+
})] }), groupIndex < optionGroups.length - 1 ? (_jsx(SelectSeparator, {})) : null] }, (_a = group.group) !== null && _a !== void 0 ? _a : `group-${groupIndex}`));
|
|
176
|
+
})) : (_jsx("div", { className: "px-2.5 py-1.5 text-sm text-muted-foreground", children: searchEmptyMessage })) })] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: cn(size === "comfortable" && "text-[14px] leading-[20px]", descriptionClassName), children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
172
177
|
}
|
|
173
178
|
export { Select };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type StatusDotVariant = "default" | "muted" | "success" | "warning" | "info" | "destructive";
|
|
3
|
+
export type StatusDotSize = "sm" | "md" | "lg";
|
|
4
|
+
export interface StatusDotProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
5
|
+
/** Semantic colour of the status. */
|
|
6
|
+
variant?: StatusDotVariant;
|
|
7
|
+
/** Diameter of the dot. */
|
|
8
|
+
size?: StatusDotSize;
|
|
9
|
+
/** Accessible name. Omit when nearby text already describes the status. */
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const StatusDot: React.ForwardRefExoticComponent<StatusDotProps & React.RefAttributes<HTMLSpanElement>>;
|
|
13
|
+
export { StatusDot };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "./lib/utils";
|
|
4
|
+
const sizeClasses = {
|
|
5
|
+
sm: "size-[6px]",
|
|
6
|
+
md: "size-[8px]",
|
|
7
|
+
lg: "size-[10px]",
|
|
8
|
+
};
|
|
9
|
+
const variantClasses = {
|
|
10
|
+
default: "bg-primary",
|
|
11
|
+
muted: "bg-muted-foreground",
|
|
12
|
+
success: "bg-[hsl(var(--nea-green-400))]",
|
|
13
|
+
warning: "bg-[hsl(var(--nea-amber-400))]",
|
|
14
|
+
info: "bg-[hsl(var(--nea-blue-400))]",
|
|
15
|
+
destructive: "bg-[hsl(var(--nea-red-400))]",
|
|
16
|
+
};
|
|
17
|
+
const StatusDot = React.forwardRef(({ variant = "default", size = "md", label, className, ...props }, ref) => (_jsx("span", { ref: ref, "data-slot": "status-dot", role: label ? "img" : undefined, "aria-label": label, "aria-hidden": label ? undefined : true, className: cn("inline-block shrink-0 rounded-full", sizeClasses[size], variantClasses[variant], className), ...props })));
|
|
18
|
+
StatusDot.displayName = "StatusDot";
|
|
19
|
+
export { StatusDot };
|
package/dist/styles.css
CHANGED
|
@@ -413,25 +413,6 @@
|
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
@layer components {
|
|
416
|
-
[data-slot="routing-timeline"] {
|
|
417
|
-
--routing-timeline-background: var(--layout-right-panel-background);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
[data-slot="routing-timeline-list"]::before {
|
|
421
|
-
position: absolute;
|
|
422
|
-
top: var(--space-2);
|
|
423
|
-
bottom: var(--space-0);
|
|
424
|
-
left: calc(var(--space-3) / 2);
|
|
425
|
-
width: 1px;
|
|
426
|
-
content: "";
|
|
427
|
-
transform: translateX(-50%);
|
|
428
|
-
background: hsl(var(--border));
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
[data-slot="routing-timeline-marker"] {
|
|
432
|
-
box-shadow: 0 0 0 var(--space-1) hsl(var(--routing-timeline-background));
|
|
433
|
-
}
|
|
434
|
-
|
|
435
416
|
[data-slot="routing-timeline-event-card"] {
|
|
436
417
|
container-type: inline-size;
|
|
437
418
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface TableAddRowProps {
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Shared full-width add action beneath editable tables. */
|
|
8
|
+
export declare function TableAddRow({ children, onClick, disabled }: TableAddRowProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "./button";
|
|
3
|
+
import { PlusIcon } from "./animated-icons/plus";
|
|
4
|
+
/** Shared full-width add action beneath editable tables. */
|
|
5
|
+
export function TableAddRow({ children = "Add Row", onClick, disabled }) {
|
|
6
|
+
return (_jsxs(Button, { type: "button", variant: "outline", onClick: onClick, disabled: disabled, className: "w-full justify-center", children: [_jsx(PlusIcon, { size: 16 }), children] }));
|
|
7
|
+
}
|