@neasg/design-system 0.4.95 → 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 +2 -0
- package/dist/activity-card.js +2 -2
- package/dist/alert.d.ts +1 -1
- package/dist/badge.d.ts +1 -1
- package/dist/button.d.ts +1 -1
- package/dist/button.js +2 -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 +2 -0
- package/dist/compound-search-input.js +13 -3
- package/dist/date-input.js +2 -2
- package/dist/dialog.d.ts +2 -1
- package/dist/dialog.js +4 -3
- 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 +10 -0
- package/dist/index.js +6 -0
- package/dist/input.d.ts +2 -0
- package/dist/input.js +11 -3
- package/dist/message-action-panel.d.ts +12 -0
- package/dist/message-action-panel.js +11 -0
- package/dist/notification.js +8 -4
- package/dist/page-section.d.ts +2 -1
- package/dist/page-section.js +2 -2
- package/dist/popover.d.ts +5 -0
- package/dist/popover.js +4 -0
- package/dist/rich-text-editor.js +4 -6
- package/dist/routing-action-panel.d.ts +38 -0
- package/dist/routing-action-panel.js +19 -0
- package/dist/search-input.d.ts +2 -0
- package/dist/search-input.js +5 -3
- package/dist/table-add-row.d.ts +8 -0
- package/dist/table-add-row.js +7 -0
- package/dist/table.js +1 -1
- package/dist/tailwind-preset.js +10 -0
- package/dist/textarea.d.ts +4 -0
- package/dist/textarea.js +17 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -331,3 +331,12 @@ GitHub Actions builds Storybook with `npm run build-storybook` to verify that
|
|
|
331
331
|
the stories compile. The Chromatic publishing step is temporarily commented
|
|
332
332
|
out in `.github/workflows/ci.yml` and can be uncommented when snapshot
|
|
333
333
|
publishing is needed again.
|
|
334
|
+
|
|
335
|
+
### Text length counters
|
|
336
|
+
|
|
337
|
+
Use `showCount` with `maxLength` on `Input`, `SearchInput`, `CompoundSearchInput`, or `Textarea` for a character counter. Use `maxWords` on `Textarea` for a word counter; it takes precedence if both limits are supplied. Counters appear below the field, aligned right, as `count / limit`. The unit is available to screen readers without repeating a visible maximum-length hint. Native character limits remain enforced; word limits report validation errors without truncating text.
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
const [reference, setReference] = React.useState("");
|
|
341
|
+
<Input label="Reference" value={reference} onChange={(event) => setReference(event.target.value)} maxLength={50} showCount />
|
|
342
|
+
```
|
package/dist/activity-card.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface ActivityCardProps extends Omit<React.HTMLAttributes<HTMLElement
|
|
|
9
9
|
title: React.ReactNode;
|
|
10
10
|
titleLines?: 1 | 2;
|
|
11
11
|
meta?: React.ReactNode;
|
|
12
|
+
/** Optional supporting content rendered inside the card header. */
|
|
13
|
+
headerContent?: React.ReactNode;
|
|
12
14
|
trailing?: React.ReactNode;
|
|
13
15
|
href?: string;
|
|
14
16
|
type?: "button" | "submit" | "reset";
|
package/dist/activity-card.js
CHANGED
|
@@ -38,7 +38,7 @@ function getActivityActorInitials(value) {
|
|
|
38
38
|
.map((part) => part.charAt(0).toUpperCase())
|
|
39
39
|
.join("");
|
|
40
40
|
}
|
|
41
|
-
const ActivityCard = React.forwardRef(({ as = "article", density = "default", icon, iconTone, title, titleLines = 2, meta, trailing, href, type, className, children, "data-slot": dataSlot, ...props }, ref) => {
|
|
41
|
+
const ActivityCard = React.forwardRef(({ as = "article", density = "default", icon, iconTone, title, titleLines = 2, meta, headerContent, trailing, href, type, className, children, "data-slot": dataSlot, ...props }, ref) => {
|
|
42
42
|
const Comp = as;
|
|
43
43
|
return (_jsxs(Comp, { ref: ref, "data-slot": dataSlot !== null && dataSlot !== void 0 ? dataSlot : "activity-card", "data-activity-card": "", "data-density": density, href: as === "a" ? href : undefined, type: as === "button" ? (type !== null && type !== void 0 ? type : "button") : undefined, className: cn("min-w-0 overflow-hidden rounded-surface border border-border bg-card text-card-foreground shadow-sm", className), ...props, children: [_jsxs("header", { "data-slot": "activity-card-header", className: cn("flex items-center", density === "compact"
|
|
44
44
|
? "min-h-[var(--space-12)] p-[var(--space-3)]"
|
|
@@ -50,7 +50,7 @@ const ActivityCard = React.forwardRef(({ as = "article", density = "default", ic
|
|
|
50
50
|
: "pl-[16px]"), trailing &&
|
|
51
51
|
(density === "compact"
|
|
52
52
|
? "pr-[var(--space-3)]"
|
|
53
|
-
: "pr-[32px]")), children: [_jsx(Typography, { as: "h3", variant: "label", className: cn("font-semibold", activityCardTitleLineClasses[titleLines]), children: title }), meta ? (_jsx(Typography, { as: "div", variant: "caption", className: "font-medium text-instructional", children: meta })) : null] }), trailing ? (_jsx("div", { "data-slot": "activity-card-trailing", className: "max-w-full shrink-0", children: trailing })) : null] }), children] }));
|
|
53
|
+
: "pr-[32px]")), children: [_jsx(Typography, { as: "h3", variant: "label", className: cn("font-semibold", activityCardTitleLineClasses[titleLines]), children: title }), meta ? (_jsx(Typography, { as: "div", variant: "caption", className: "font-medium text-instructional", children: meta })) : null, headerContent ? (_jsx("div", { "data-slot": "activity-card-header-content", className: "mt-[var(--space-2)]", children: headerContent })) : null] }), trailing ? (_jsx("div", { "data-slot": "activity-card-trailing", className: "max-w-full shrink-0", children: trailing })) : null] }), children] }));
|
|
54
54
|
});
|
|
55
55
|
ActivityCard.displayName = "ActivityCard";
|
|
56
56
|
const ActivityCardContent = React.forwardRef(({ density = "default", className, ...props }, ref) => (_jsx("div", { ref: ref, "data-slot": "activity-card-content", "data-density": density, className: cn("border-t border-border", density === "compact"
|
package/dist/alert.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import { type ButtonProps } from "./button";
|
|
4
4
|
import { type CheckboxProps } from "./checkbox";
|
|
5
5
|
declare const alertVariants: (props?: ({
|
|
6
|
-
variant?: "
|
|
6
|
+
variant?: "warning" | "destructive" | "info" | "success" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
export interface AlertProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title">, VariantProps<typeof alertVariants> {
|
|
9
9
|
title?: React.ReactNode;
|
package/dist/badge.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
declare const badgeVariants: (props?: ({
|
|
4
|
-
variant?: "accent" | "muted" | "destructive" | "outline" | "secondary" | "info" | "success" |
|
|
4
|
+
variant?: "accent" | "muted" | "warning" | "destructive" | "outline" | "secondary" | "info" | "success" | null | undefined;
|
|
5
5
|
size?: "default" | "icon" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
/**
|
package/dist/button.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
declare const buttonVariants: (props?: ({
|
|
4
|
-
variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
4
|
+
variant?: "link" | "default" | "warning" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
5
5
|
size?: "default" | "sm" | "compact" | "comfortable" | "icon" | "compactIcon" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
package/dist/button.js
CHANGED
|
@@ -7,6 +7,7 @@ const buttonVariants = cva("inline-flex cursor-pointer items-center justify-cent
|
|
|
7
7
|
variants: {
|
|
8
8
|
variant: {
|
|
9
9
|
default: "bg-button-primary text-button-primary-foreground hover:bg-button-primary-hover",
|
|
10
|
+
warning: "border border-[hsl(var(--status-warning-foreground))]/30 bg-[hsl(var(--status-warning))] text-[hsl(var(--status-warning-foreground))] hover:bg-[hsl(var(--status-warning))]/80 focus-visible:ring-[hsl(var(--status-warning-foreground))]",
|
|
10
11
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
11
12
|
outline: "border border-[hsl(var(--button-outline-border))] bg-[hsl(var(--button-outline-background))] text-foreground hover:bg-[hsl(var(--button-outline-hover-background))] hover:text-accent-foreground",
|
|
12
13
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
@@ -146,7 +147,7 @@ const Button = React.forwardRef(({ className, variant, size, asChild = false, lo
|
|
|
146
147
|
stopAnimatedIcons();
|
|
147
148
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
148
149
|
};
|
|
149
|
-
return (_jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref: ref, "aria-busy": loading || undefined, "aria-disabled": isDisabled || undefined, "data-loading": loading ? "" : undefined, "data-disabled": isDisabled ? "" : undefined, onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleFocus, onBlur: handleBlur, tabIndex: isDisabled && asChild ? -1 : tabIndex, disabled: !asChild ? isDisabled : undefined, ...props, children: content }));
|
|
150
|
+
return (_jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ref: ref, "aria-busy": loading || undefined, "aria-disabled": isDisabled || undefined, "data-loading": loading ? "" : undefined, "data-disabled": isDisabled ? "" : undefined, onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleFocus, onBlur: handleBlur, tabIndex: isDisabled && asChild ? -1 : tabIndex, disabled: !asChild ? isDisabled : undefined, ...props, children: content }));
|
|
150
151
|
});
|
|
151
152
|
Button.displayName = "Button";
|
|
152
153
|
export { Button, buttonVariants };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type DashboardActionPanelProps } from "./layout";
|
|
3
|
+
import { type NotificationItemData } from "./notification";
|
|
4
|
+
export interface ChangelogActionPanelProps extends Omit<DashboardActionPanelProps, "children" | "headerSpacing" | "title"> {
|
|
5
|
+
title?: React.ReactNode;
|
|
6
|
+
notifications: NotificationItemData[];
|
|
7
|
+
isLoading?: boolean;
|
|
8
|
+
loadingLabel?: string;
|
|
9
|
+
emptyMessage?: React.ReactNode;
|
|
10
|
+
emptyDescription?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Shared change-log panel. Consumers map their domain events to notification
|
|
14
|
+
* items; this component owns the complete list, loading, and empty-state UI.
|
|
15
|
+
*/
|
|
16
|
+
declare function ChangelogActionPanel({ title, subtitle, notifications, isLoading, loadingLabel, emptyMessage, emptyDescription, ...panelProps }: ChangelogActionPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export { ChangelogActionPanel };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { History } from "lucide-react";
|
|
4
|
+
import { DashboardActionPanel, DashboardActionPanelEmptyState, DashboardRightPanelBody, } from "./layout";
|
|
5
|
+
import { NotificationList, } from "./notification";
|
|
6
|
+
import { Spinner } from "./spinner";
|
|
7
|
+
/**
|
|
8
|
+
* Shared change-log panel. Consumers map their domain events to notification
|
|
9
|
+
* items; this component owns the complete list, loading, and empty-state UI.
|
|
10
|
+
*/
|
|
11
|
+
function ChangelogActionPanel({ title = "Change log", subtitle = "Review all activity recorded here.", notifications, isLoading = false, loadingLabel = "Loading change log", emptyMessage = "No change log entries yet", emptyDescription = "Changes will be recorded here.", ...panelProps }) {
|
|
12
|
+
return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", title: title, subtitle: subtitle, ...panelProps, children: _jsx(DashboardRightPanelBody, { spacing: "comfortable", topInset: "none", children: isLoading ? (_jsx("div", { "data-slot": "changelog-action-panel-loading", className: "absolute inset-0 flex items-center justify-center", children: _jsx(Spinner, { size: "md", label: loadingLabel }) })) : notifications.length === 0 ? (_jsx(DashboardActionPanelEmptyState, { icon: History, message: emptyMessage, description: emptyDescription })) : (_jsx(NotificationList, { density: "compact", notifications: notifications, descriptionLines: 3, emptyState: null, variant: "bordered" })) }) }));
|
|
13
|
+
}
|
|
14
|
+
export { ChangelogActionPanel };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type SelectOption } from "./select";
|
|
3
|
+
export interface ChecklistPanelProps {
|
|
4
|
+
title: React.ReactNode;
|
|
5
|
+
action?: React.ReactNode;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function ChecklistPanel({ title, action, children }: ChecklistPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export interface ChecklistSectionProps {
|
|
10
|
+
title: string;
|
|
11
|
+
progress?: string;
|
|
12
|
+
/** Independent control displayed beside the section chevron. */
|
|
13
|
+
action?: React.ReactNode;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
defaultOpen?: boolean;
|
|
16
|
+
open?: boolean;
|
|
17
|
+
onOpenChange?: (open: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function ChecklistSection({ title, progress, action, children, defaultOpen, open, onOpenChange }: ChecklistSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export interface ChecklistSelectionProps {
|
|
21
|
+
title: string;
|
|
22
|
+
description: string;
|
|
23
|
+
typeOptions: SelectOption[];
|
|
24
|
+
subtypeOptions: SelectOption[];
|
|
25
|
+
type: string;
|
|
26
|
+
subtype: string;
|
|
27
|
+
onTypeChange: (value: string) => void;
|
|
28
|
+
onSubtypeChange: (value: string) => void;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
loading?: boolean;
|
|
31
|
+
action?: React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export declare function ChecklistSelection({ title, description, typeOptions, subtypeOptions, type, subtype, onTypeChange, onSubtypeChange, disabled, loading, action }: ChecklistSelectionProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export interface ChecklistQuestionOption {
|
|
35
|
+
value: string;
|
|
36
|
+
label: string;
|
|
37
|
+
tone?: "positive" | "negative" | "neutral";
|
|
38
|
+
}
|
|
39
|
+
export interface ChecklistQuestionProps {
|
|
40
|
+
label: string;
|
|
41
|
+
options: ChecklistQuestionOption[];
|
|
42
|
+
value: string;
|
|
43
|
+
onValueChange: (value: string) => void;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
remarks: string;
|
|
46
|
+
onRemarksChange: (value: string) => void;
|
|
47
|
+
remarksLimit?: number;
|
|
48
|
+
/** Count and limit characters instead of words when supplied. */
|
|
49
|
+
remarksMaxLength?: number;
|
|
50
|
+
remarksRequired?: boolean;
|
|
51
|
+
evidence?: React.ReactNode;
|
|
52
|
+
evidenceCount?: number;
|
|
53
|
+
}
|
|
54
|
+
export declare function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit, remarksMaxLength, remarksRequired, evidence, evidenceCount }: ChecklistQuestionProps): import("react/jsx-runtime").JSX.Element;
|
|
55
|
+
export interface ChecklistAttachmentsProps {
|
|
56
|
+
items: {
|
|
57
|
+
key: string;
|
|
58
|
+
name: string;
|
|
59
|
+
url: string;
|
|
60
|
+
}[];
|
|
61
|
+
onRemove?: (key: string) => void;
|
|
62
|
+
}
|
|
63
|
+
export declare function ChecklistAttachments({ items, onRemove }: ChecklistAttachmentsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
|
5
|
+
import { Camera, ChevronDown, ClipboardList, LoaderCircle, MessageSquareText, Trash2 } from "lucide-react";
|
|
6
|
+
import { Button } from "./button";
|
|
7
|
+
import { Select } from "./select";
|
|
8
|
+
import { Textarea } from "./textarea";
|
|
9
|
+
import { cn } from "./lib/utils";
|
|
10
|
+
export function ChecklistPanel({ title, action, children }) {
|
|
11
|
+
return _jsxs("div", { "data-slot": "checklist-panel", className: "rounded-xl border border-border bg-card p-4 shadow-xs", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between gap-4", children: [_jsx("h3", { className: "text-base font-medium text-card-foreground", children: title }), action] }), children] });
|
|
12
|
+
}
|
|
13
|
+
export function ChecklistSection({ title, progress, action, children, defaultOpen = false, open, onOpenChange }) {
|
|
14
|
+
return _jsxs(CollapsiblePrimitive.Root, { open: open, onOpenChange: onOpenChange, defaultOpen: defaultOpen, className: "border-b border-border last:border-0", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(CollapsiblePrimitive.Trigger, { className: "group flex min-w-0 flex-1 items-center gap-1.5 py-3 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx("span", { className: "text-base font-medium", children: title }), progress ? _jsx("span", { className: "text-sm text-muted-foreground", children: progress }) : null, !action ? _jsx(ChevronDown, { "aria-hidden": "true", className: "ml-auto size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" }) : null] }), action ? _jsxs(_Fragment, { children: [_jsx("div", { "data-slot": "checklist-section-action", className: "shrink-0", children: action }), _jsx(CollapsiblePrimitive.Trigger, { "aria-label": `Toggle ${title}`, className: "group py-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(ChevronDown, { "aria-hidden": "true", className: "size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" }) })] }) : null] }), _jsx(CollapsiblePrimitive.Content, { className: "overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up motion-reduce:data-[state=open]:animate-none motion-reduce:data-[state=closed]:animate-none", children: _jsx("div", { className: "flex flex-col gap-4 pb-4", children: children }) })] });
|
|
15
|
+
}
|
|
16
|
+
export function ChecklistSelection({ title, description, typeOptions, subtypeOptions, type, subtype, onTypeChange, onSubtypeChange, disabled, loading, action }) {
|
|
17
|
+
const Icon = loading ? LoaderCircle : ClipboardList;
|
|
18
|
+
return _jsxs("div", { "data-slot": "checklist-selection", "aria-busy": loading, className: "flex flex-col items-center gap-4 rounded-lg bg-muted/30 p-6 text-center", children: [_jsx(Icon, { "aria-hidden": "true", className: cn("size-8", loading && "animate-spin") }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium", children: title }), _jsx("p", { className: "text-sm text-muted-foreground", children: description })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4", children: [_jsx(Select, { className: "w-auto", triggerClassName: "w-auto min-w-0", label: "Type", hideLabel: true, placeholder: "Type", options: typeOptions, value: type, onValueChange: onTypeChange, disabled: disabled || loading }), _jsx(Select, { className: "w-auto", triggerClassName: "w-auto min-w-0", label: "Sub-Type", hideLabel: true, placeholder: "Sub-Type", options: subtypeOptions, value: subtype, onValueChange: onSubtypeChange, disabled: disabled || loading || !type }), action] })] });
|
|
19
|
+
}
|
|
20
|
+
export function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit = 500, remarksMaxLength, remarksRequired = false, evidence, evidenceCount = 0 }) {
|
|
21
|
+
const name = React.useId();
|
|
22
|
+
React.useEffect(() => { if (remarksRequired)
|
|
23
|
+
setShowRemarks(true); }, [remarksRequired, value]);
|
|
24
|
+
const [showRemarks, setShowRemarks] = React.useState(Boolean(remarks));
|
|
25
|
+
const [showEvidence, setShowEvidence] = React.useState(false);
|
|
26
|
+
return _jsxs("div", { "data-slot": "checklist-question", className: "rounded-xl border border-border bg-card p-4 shadow-xs", children: [_jsx("p", { id: `${name}-label`, className: "mb-4 text-sm font-medium", children: label }), _jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx("fieldset", { disabled: disabled, "aria-labelledby": `${name}-label`, className: cn("flex min-w-0 border-0 p-0", disabled && "opacity-50"), children: options.map((option, index) => _jsxs("label", { className: cn("relative cursor-pointer border border-input px-2.5 py-2 text-sm font-medium focus-within:z-10 focus-within:ring-2 focus-within:ring-ring", index > 0 && "-ml-px", index === 0 && "rounded-l-md", index === options.length - 1 && "rounded-r-md", disabled && "cursor-not-allowed", value === option.value ? option.tone === "negative" ? "bg-destructive/20" : option.tone === "positive" ? "bg-primary/20" : "bg-muted" : "bg-background"), children: [_jsx("input", { className: "absolute inset-0 size-full cursor-inherit opacity-0", type: "radio", name: name, value: option.value, checked: value === option.value, onChange: () => onValueChange(option.value) }), option.label] }, option.value)) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", size: "compactIcon", "aria-label": "Add remarks", disabled: disabled, onClick: () => setShowRemarks(true), children: _jsx(MessageSquareText, { "aria-hidden": "true", size: 16 }) }), _jsx(Button, { variant: "ghost", size: "compactIcon", "aria-label": "Add photos", disabled: disabled, onClick: () => setShowEvidence((open) => !open), children: _jsx(Camera, { "aria-hidden": "true", size: 16 }) })] })] }), showRemarks || remarks ? _jsx("div", { className: "mt-2 space-y-2", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(Textarea, { label: "Remarks", hideLabel: true, placeholder: "Type...", value: remarks, disabled: disabled, rows: 2, onChange: (event) => onRemarksChange(event.target.value), maxWords: remarksMaxLength === undefined ? remarksLimit : undefined, maxLength: remarksMaxLength, showCount: remarksMaxLength !== undefined }) }), _jsx(Button, { variant: "ghost", size: "compactIcon", "aria-label": "Remove remarks", disabled: disabled, onClick: () => { onRemarksChange(""); setShowRemarks(false); }, children: _jsx(Trash2, { "aria-hidden": "true", size: 16 }) })] }) }) : null, (showEvidence || evidenceCount > 0) && evidence ? _jsx("div", { className: "mt-2", children: evidence }) : null] });
|
|
27
|
+
}
|
|
28
|
+
export function ChecklistAttachments({ items, onRemove }) {
|
|
29
|
+
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)) });
|
|
30
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export interface CompoundSearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
3
|
+
/** Display a character counter when maxLength is set. */
|
|
4
|
+
showCount?: boolean;
|
|
3
5
|
actions?: React.ReactNode;
|
|
4
6
|
criteria?: React.ReactNode;
|
|
5
7
|
selected?: boolean;
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { SearchIcon } from "lucide-react";
|
|
4
|
+
import { FieldCounter } from "./field-counter";
|
|
4
5
|
import { cn } from "./lib/utils";
|
|
5
|
-
const CompoundSearchInput = React.forwardRef(({ actions, "aria-invalid": ariaInvalid, className, criteria, selected = false, ...props }, ref) =>
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const CompoundSearchInput = React.forwardRef(({ actions, showCount = false, value, defaultValue, onChange, "aria-invalid": ariaInvalid, className, criteria, selected = false, ...props }, ref) => {
|
|
7
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : "");
|
|
8
|
+
const generatedId = React.useId();
|
|
9
|
+
const limitId = showCount && props.maxLength !== undefined ? `search-limit-${generatedId}` : undefined;
|
|
10
|
+
return (_jsxs("div", { className: "grid gap-2", children: [_jsxs("div", { "data-slot": "compound-search-input", "data-selected": selected ? "true" : "false", className: cn("flex min-h-12 w-full min-w-0 flex-wrap items-center gap-1.5 rounded-control border border-input bg-background p-1.5 transition-[border-color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 data-[selected=true]:border-interactive data-[selected=true]:bg-highlight data-[selected=true]:ring-[3px] data-[selected=true]:ring-interactive/20", ariaInvalid === true || ariaInvalid === "true"
|
|
11
|
+
? "border-destructive ring-destructive/20"
|
|
12
|
+
: undefined), children: [_jsx(SearchIcon, { "aria-hidden": "true", className: "ml-1.5 h-4 w-4 shrink-0 text-muted-foreground" }), criteria, _jsx("input", { ref: ref, "aria-invalid": ariaInvalid, className: cn("h-8 min-w-44 basis-52 flex-1 bg-transparent px-1.5 text-sm outline-none placeholder:text-muted-foreground", className), ...props, "aria-describedby": [props["aria-describedby"], limitId].filter(Boolean).join(" ") || undefined, value: value, defaultValue: defaultValue, onChange: (event) => {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
setUncontrolledValue(event.target.value);
|
|
15
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
16
|
+
} }), actions ? (_jsx("span", { className: "ml-auto inline-flex h-8 shrink-0 items-center gap-1 text-muted-foreground", children: actions })) : null] }), limitId && _jsx(FieldCounter, { id: limitId, count: String(value !== null && value !== void 0 ? value : uncontrolledValue).length, limit: props.maxLength })] }));
|
|
17
|
+
});
|
|
8
18
|
CompoundSearchInput.displayName = "CompoundSearchInput";
|
|
9
19
|
export { CompoundSearchInput };
|
package/dist/date-input.js
CHANGED
|
@@ -166,9 +166,9 @@ const DateInput = React.forwardRef((props, ref) => {
|
|
|
166
166
|
const disabledDays = (date) => isDateOutsideBounds(date, minDate, maxDate);
|
|
167
167
|
const calendarContent = (_jsx(_Fragment, { children: props.mode === "range" ? (_jsx(Calendar, { mode: "range", selected: selectedRange, onSelect: handleRangeSelect, disabled: disabledDays, defaultMonth: (_b = selectedRange === null || selectedRange === void 0 ? void 0 : selectedRange.from) !== null && _b !== void 0 ? _b : selectedRange === null || selectedRange === void 0 ? void 0 : selectedRange.to, numberOfMonths: (_c = props.numberOfMonths) !== null && _c !== void 0 ? _c : 2, showOutsideDays: false, className: "[&_.rdp-month:not(:first-of-type)]:hidden sm:[&_.rdp-month:not(:first-of-type)]:flex", autoFocus: true })) : (_jsx(Calendar, { mode: "single", selected: selectedDate, onSelect: handleSelect, disabled: disabledDays, defaultMonth: selectedDate, autoFocus: true })) }));
|
|
168
168
|
if (appearance === "inline") {
|
|
169
|
-
return (_jsxs("span", { className: cn("inline-flex", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(Popover, { open: open, onOpenChange: handleOpenChange, align: "start", title: popoverTitle, surface: popoverTitle ? "menu" : "panel", collisionPadding: 8, contentClassName: "max-h-[var(--radix-popover-content-available-height)] w-auto overflow-auto p-0 [&_[data-slot=popover-title]]:
|
|
169
|
+
return (_jsxs("span", { className: cn("inline-flex", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(Popover, { open: open, onOpenChange: handleOpenChange, align: "start", title: popoverTitle, surface: popoverTitle ? "menu" : "panel", collisionPadding: 8, contentClassName: "max-h-[var(--radix-popover-content-available-height)] w-auto overflow-auto p-0 [&_[data-slot=popover-title]]:p-3", trigger: _jsxs("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn("inline-flex cursor-pointer items-baseline rounded-sm border-0 bg-transparent p-0 font-medium text-interactive underline decoration-interactive/40 underline-offset-4 transition-colors hover:decoration-interactive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", !displayValue && "text-muted-foreground decoration-border", isInvalid && "text-destructive decoration-destructive", triggerClassName), children: [_jsxs("span", { className: "sr-only", children: [label, ": "] }), _jsx("span", { children: displayValue !== null && displayValue !== void 0 ? displayValue : placeholder })] }), children: calendarContent }), showDescription ? (_jsx("span", { id: descriptionId, className: "sr-only", children: description })) : null, error ? (_jsx("span", { id: errorId, role: "alert", className: "sr-only", children: error })) : null] }));
|
|
170
170
|
}
|
|
171
|
-
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs("div", { className: "relative", children: [_jsx(Popover, { open: open, onOpenChange: handleOpenChange, align: "start", title: popoverTitle, surface: popoverTitle ? "menu" : "panel", collisionPadding: 8, contentClassName: "max-h-[var(--radix-popover-content-available-height)] w-auto overflow-auto p-0 [&_[data-slot=popover-title]]:
|
|
171
|
+
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs("div", { className: "relative", children: [_jsx(Popover, { open: open, onOpenChange: handleOpenChange, align: "start", title: popoverTitle, surface: popoverTitle ? "menu" : "panel", collisionPadding: 8, contentClassName: "max-h-[var(--radix-popover-content-available-height)] w-auto overflow-auto p-0 [&_[data-slot=popover-title]]:p-3", trigger: _jsx("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn(inputControlClassName, "relative cursor-pointer pr-9 text-left", showClearButton && "pr-16", !displayValue && "text-muted-foreground", isInvalid && "border-destructive focus-visible:ring-destructive", triggerClassName), children: _jsx("span", { className: "block truncate", children: displayValue !== null && displayValue !== void 0 ? displayValue : placeholder }) }), children: calendarContent }), _jsxs("span", { "aria-hidden": showClearButton ? undefined : true, className: "pointer-events-none absolute inset-y-0 right-3 flex items-center gap-2 text-muted-foreground", children: [showClearButton ? (_jsx("button", { type: "button", "aria-label": resolvedClearLabel, onClick: handleClear, className: "pointer-events-auto inline-flex h-4 w-4 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed", children: _jsx(XIcon, { size: 14, className: "flex items-center justify-center" }) })) : null, _jsx(CalendarIcon, { "aria-hidden": "true", className: "h-4 w-4 shrink-0", strokeWidth: 2 })] })] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
172
172
|
});
|
|
173
173
|
DateInput.displayName = "DateInput";
|
|
174
174
|
export { DateInput };
|
package/dist/dialog.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ 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";
|
|
12
13
|
open?: boolean;
|
|
13
14
|
defaultOpen?: boolean;
|
|
14
15
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -26,5 +27,5 @@ export interface DialogProps {
|
|
|
26
27
|
/** Whether a pointer interaction outside the dialog closes it. */
|
|
27
28
|
closeOnOutsideClick?: boolean;
|
|
28
29
|
}
|
|
29
|
-
declare function Dialog({ open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton, closeOnOutsideClick, }: DialogProps): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
declare function Dialog({ variant, open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton, closeOnOutsideClick, }: DialogProps): import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
export { Dialog };
|
package/dist/dialog.js
CHANGED
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Button } from "./button";
|
|
5
5
|
import { DialogDescription, DialogFooter, DialogHeader, DialogRoot, DialogSurface, DialogTitle, DialogTrigger, } from "./dialog-primitive";
|
|
6
|
+
import { CircleAlert } from "lucide-react";
|
|
6
7
|
import { cn } from "./lib/utils";
|
|
7
8
|
function useControllableOpen({ open, defaultOpen, onOpenChange, }) {
|
|
8
9
|
const [internalOpen, setInternalOpen] = React.useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
|
|
@@ -31,7 +32,7 @@ function renderDialogAction({ action, close, defaultVariant, }) {
|
|
|
31
32
|
}
|
|
32
33
|
}, children: label }));
|
|
33
34
|
}
|
|
34
|
-
function Dialog({ open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
|
|
35
|
+
function Dialog({ variant = "default", open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
|
|
35
36
|
const [isOpen, setIsOpen] = useControllableOpen({
|
|
36
37
|
open,
|
|
37
38
|
defaultOpen,
|
|
@@ -43,11 +44,11 @@ function Dialog({ open, defaultOpen, onOpenChange, modal, trigger, title, descri
|
|
|
43
44
|
const body = resolveDialogRenderable(children, helpers);
|
|
44
45
|
const customFooter = resolveDialogRenderable(footer, helpers);
|
|
45
46
|
const hasFooter = Boolean(customFooter || primaryAction || secondaryAction);
|
|
46
|
-
return (_jsxs(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, modal: modal, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogSurface, { ...(!description ? { "aria-describedby": undefined } : {}), className: contentClassName, hideCloseButton: hideCloseButton, onPointerDownOutside: (event) => {
|
|
47
|
+
return (_jsxs(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, modal: modal, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogSurface, { ...(!description ? { "aria-describedby": undefined } : {}), className: cn(variant === "confirmation" && "max-w-sm gap-0 overflow-hidden rounded-xl p-0", contentClassName), hideCloseButton: hideCloseButton || variant === "confirmation", onPointerDownOutside: (event) => {
|
|
47
48
|
if (!closeOnOutsideClick) {
|
|
48
49
|
event.preventDefault();
|
|
49
50
|
}
|
|
50
|
-
}, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), description ?
|
|
51
|
+
}, children: [_jsxs("div", { className: cn(variant === "confirmation" && "flex items-start gap-4 p-4"), children: [variant === "confirmation" ? _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", bodyClassName), children: body }) : null, hasFooter ? (_jsx(DialogFooter, { className: variant === "confirmation" ? "border-t bg-muted/30 p-4" : undefined, children: customFooter !== null && customFooter !== void 0 ? customFooter : (_jsxs(_Fragment, { children: [secondaryAction
|
|
51
52
|
? renderDialogAction({
|
|
52
53
|
action: secondaryAction,
|
|
53
54
|
close: helpers.close,
|
package/dist/dropdown-button.js
CHANGED
|
@@ -8,6 +8,7 @@ import { cn } from "./lib/utils";
|
|
|
8
8
|
const splitButtonDividerClassNames = {
|
|
9
9
|
default: "before:bg-button-primary-foreground/30",
|
|
10
10
|
destructive: "before:bg-destructive-foreground/30",
|
|
11
|
+
warning: "before:bg-[hsl(var(--status-warning-foreground))]/30",
|
|
11
12
|
outline: "before:bg-[hsl(var(--button-outline-border))]",
|
|
12
13
|
secondary: "before:bg-secondary-foreground/20",
|
|
13
14
|
ghost: "before:bg-border",
|
package/dist/editable-table.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Button } from "./button";
|
|
4
|
-
import {
|
|
4
|
+
import { TableAddRow } from "./table-add-row";
|
|
5
5
|
import { TrashIcon } from "./animated-icons/trash";
|
|
6
6
|
import { Checkbox } from "./checkbox";
|
|
7
7
|
import { EmptyState } from "./empty-state";
|
|
@@ -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 ? (
|
|
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] }));
|
|
149
149
|
}
|
|
150
150
|
EditableTable.displayName = "EditableTable";
|
|
151
151
|
export { EditableTable };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Internal shared presentation for field length counters. */
|
|
3
|
+
export function FieldCounter({ id, count, limit, unit = "characters" }) {
|
|
4
|
+
return (_jsxs("div", { "data-slot": "field-counter", className: "flex items-start justify-between gap-3 text-sm text-muted-foreground", children: [_jsxs("span", { id: id, className: "sr-only", children: ["Maximum ", limit, " ", unit] }), _jsxs("span", { className: "ml-auto shrink-0 tabular-nums", "aria-live": "polite", "aria-atomic": "true", "aria-label": `${count} of ${limit} ${unit}`, children: [count, " / ", limit] })] }));
|
|
5
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,16 +9,22 @@ export { AlertStack } from "./alert-stack";
|
|
|
9
9
|
export type { AlertStackProps } from "./alert-stack";
|
|
10
10
|
export { ActivityCard, ActivityCardActor, ActivityCardContent, } from "./activity-card";
|
|
11
11
|
export type { ActivityCardActorProps, ActivityCardContentProps, ActivityCardDensity, ActivityCardIconTone, ActivityCardProps, } from "./activity-card";
|
|
12
|
+
export { ChangelogActionPanel } from "./changelog-action-panel";
|
|
13
|
+
export type { ChangelogActionPanelProps } from "./changelog-action-panel";
|
|
12
14
|
export { Badge, badgeVariants } from "./badge";
|
|
13
15
|
export { Card, CardGrid, CardSplitGrid, CardList, CardListItem, } from "./card";
|
|
14
16
|
export type { CardGridProps, CardListItemProps, CardListProps, CardProps, CardSplitGridProps, } from "./card";
|
|
15
17
|
export type { BadgeProps } from "./badge";
|
|
16
18
|
export { MessageAttachmentRow, MessageItem, MessageNewDivider, MessageThread, formatMessageCardTimestamp, formatMessageDate, } from "./message-item";
|
|
17
19
|
export type { MessageAttachmentRowProps, MessageItemAttachment, MessageItemData, MessageItemProps, MessageNewDividerProps, MessageThreadProps, } from "./message-item";
|
|
20
|
+
export { MessageActionPanel } from "./message-action-panel";
|
|
21
|
+
export type { MessageActionPanelProps } from "./message-action-panel";
|
|
18
22
|
export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
|
|
19
23
|
export type { NotificationAction, NotificationFilter, NotificationIcon, NotificationItemData, NotificationItemProps, NotificationKind, NotificationListProps, NotificationPopoverProps, NotificationTimestamp, NotificationTriggerProps, NotificationTone, FormatNotificationTimestampOptions, } from "./notification";
|
|
20
24
|
export { RoutingTimeline } from "./routing-timeline";
|
|
21
25
|
export type { RoutingTimelineItem, RoutingTimelineItemKind, RoutingTimelineProps, } from "./routing-timeline";
|
|
26
|
+
export { RoutingActionPanel } from "./routing-action-panel";
|
|
27
|
+
export type { RoutingActionPanelProps } from "./routing-action-panel";
|
|
22
28
|
export { RichTextEditor, formatFileSize, replaceTemplateVariables, sanitizeRichTextHtml, } from "./rich-text-editor";
|
|
23
29
|
export type { AttachmentFile, RichTextEditorColor, RichTextEditorProps, RichTextEditorSendAction, RichTextEditorSendMetadata, RichTextEditorTemplate, TemplateContext, } from "./rich-text-editor";
|
|
24
30
|
export { Calendar, CalendarDayButton } from "./calendar";
|
|
@@ -161,3 +167,7 @@ export type { CollapsibleProps } from "./collapsible";
|
|
|
161
167
|
export { cn } from "./lib/utils";
|
|
162
168
|
export { parseDateTime, formatDateTime } from "./lib/date-utils";
|
|
163
169
|
export type { ParseDateTimeOptions } from "./lib/date-utils";
|
|
170
|
+
export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelection, ChecklistQuestion } from "./checklist";
|
|
171
|
+
export type { ChecklistAttachmentsProps, ChecklistPanelProps, ChecklistSectionProps, ChecklistSelectionProps, ChecklistQuestionProps, ChecklistQuestionOption } from "./checklist";
|
|
172
|
+
export { TableAddRow, type TableAddRowProps } from "./table-add-row";
|
|
173
|
+
export { PopoverList, type PopoverListProps } from "./popover";
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,14 @@ export * from "./animated-icons";
|
|
|
4
4
|
export { Alert, AlertAction, AlertConsent, alertVariants, } from "./alert";
|
|
5
5
|
export { AlertStack } from "./alert-stack";
|
|
6
6
|
export { ActivityCard, ActivityCardActor, ActivityCardContent, } from "./activity-card";
|
|
7
|
+
export { ChangelogActionPanel } from "./changelog-action-panel";
|
|
7
8
|
export { Badge, badgeVariants } from "./badge";
|
|
8
9
|
export { Card, CardGrid, CardSplitGrid, CardList, CardListItem, } from "./card";
|
|
9
10
|
export { MessageAttachmentRow, MessageItem, MessageNewDivider, MessageThread, formatMessageCardTimestamp, formatMessageDate, } from "./message-item";
|
|
11
|
+
export { MessageActionPanel } from "./message-action-panel";
|
|
10
12
|
export { formatNotificationTimestamp, NotificationItem, NotificationList, NotificationPopover, NotificationTrigger, } from "./notification";
|
|
11
13
|
export { RoutingTimeline } from "./routing-timeline";
|
|
14
|
+
export { RoutingActionPanel } from "./routing-action-panel";
|
|
12
15
|
export { RichTextEditor, formatFileSize, replaceTemplateVariables, sanitizeRichTextHtml, } from "./rich-text-editor";
|
|
13
16
|
export { Calendar, CalendarDayButton } from "./calendar";
|
|
14
17
|
export { Checkbox } from "./checkbox";
|
|
@@ -84,3 +87,6 @@ export { Spinner, PageLoader, FullPageLoader } from "./spinner";
|
|
|
84
87
|
export { Collapsible, CollapsibleSection } from "./collapsible";
|
|
85
88
|
export { cn } from "./lib/utils";
|
|
86
89
|
export { parseDateTime, formatDateTime } from "./lib/date-utils";
|
|
90
|
+
export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelection, ChecklistQuestion } from "./checklist";
|
|
91
|
+
export { TableAddRow } from "./table-add-row";
|
|
92
|
+
export { PopoverList } from "./popover";
|
package/dist/input.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { InputControl } from "./input-control";
|
|
3
3
|
export interface InputProps extends Omit<React.ComponentProps<typeof InputControl>, "className"> {
|
|
4
|
+
/** Display a character counter when maxLength is set. */
|
|
5
|
+
showCount?: boolean;
|
|
4
6
|
label: React.ReactNode;
|
|
5
7
|
description?: React.ReactNode;
|
|
6
8
|
error?: React.ReactNode;
|
package/dist/input.js
CHANGED
|
@@ -2,10 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
4
4
|
import { InputControl } from "./input-control";
|
|
5
|
+
import { FieldCounter } from "./field-counter";
|
|
5
6
|
import { cn } from "./lib/utils";
|
|
6
|
-
const Input = React.forwardRef(({ label, description, error, invalid = false, hideLabel = false, className, inputClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, required, ...props }, ref) => {
|
|
7
|
+
const Input = React.forwardRef(({ label, showCount = false, value, defaultValue, onChange, description, error, invalid = false, hideLabel = false, className, inputClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, required, ...props }, ref) => {
|
|
8
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : "");
|
|
9
|
+
const count = String(value !== null && value !== void 0 ? value : uncontrolledValue).length;
|
|
7
10
|
const generatedId = React.useId();
|
|
8
11
|
const inputId = id !== null && id !== void 0 ? id : `input-${generatedId}`;
|
|
12
|
+
const limitId = showCount && props.maxLength !== undefined ? `${inputId}-limit` : undefined;
|
|
9
13
|
const errorId = error ? `${inputId}-error` : undefined;
|
|
10
14
|
const showDescription = Boolean(description) && !error;
|
|
11
15
|
const descriptionId = showDescription ? `${inputId}-description` : undefined;
|
|
@@ -13,10 +17,14 @@ const Input = React.forwardRef(({ label, description, error, invalid = false, hi
|
|
|
13
17
|
ariaInvalid === true ||
|
|
14
18
|
ariaInvalid === "true" ||
|
|
15
19
|
Boolean(error);
|
|
16
|
-
const describedBy = [ariaDescribedBy, descriptionId, errorId]
|
|
20
|
+
const describedBy = [ariaDescribedBy, descriptionId, errorId, limitId]
|
|
17
21
|
.filter(Boolean)
|
|
18
22
|
.join(" ") || undefined;
|
|
19
|
-
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, required: required, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsx(InputControl, { ref: ref, id: inputId, disabled: disabled, required: required, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": describedBy, className: inputClassName, ...props
|
|
23
|
+
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, required: required, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsx(InputControl, { ref: ref, id: inputId, disabled: disabled, required: required, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": describedBy, className: inputClassName, ...props, value: value, defaultValue: defaultValue, onChange: (event) => {
|
|
24
|
+
if (value === undefined)
|
|
25
|
+
setUncontrolledValue(event.target.value);
|
|
26
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
27
|
+
} }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, limitId && _jsx(FieldCounter, { id: limitId, count: count, limit: props.maxLength }), _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
20
28
|
});
|
|
21
29
|
Input.displayName = "Input";
|
|
22
30
|
export { Input };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type DashboardActionPanelProps } from "./layout";
|
|
3
|
+
export interface MessageActionPanelProps extends Omit<DashboardActionPanelProps, "children" | "headerSpacing" | "title"> {
|
|
4
|
+
title?: React.ReactNode;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Shared message-panel frame. Message data and composer behaviour remain with
|
|
9
|
+
* the consuming application while this component keeps the panel UI stable.
|
|
10
|
+
*/
|
|
11
|
+
declare function MessageActionPanel({ title, subtitle, children, ...panelProps }: MessageActionPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export { MessageActionPanel };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { DashboardActionPanel, } from "./layout";
|
|
4
|
+
/**
|
|
5
|
+
* Shared message-panel frame. Message data and composer behaviour remain with
|
|
6
|
+
* the consuming application while this component keeps the panel UI stable.
|
|
7
|
+
*/
|
|
8
|
+
function MessageActionPanel({ title = "Messages", subtitle = "Find all correspondence here.", children, ...panelProps }) {
|
|
9
|
+
return (_jsx(DashboardActionPanel, { headerSpacing: "comfortable", title: title, subtitle: subtitle, ...panelProps, children: children }));
|
|
10
|
+
}
|
|
11
|
+
export { MessageActionPanel };
|
package/dist/notification.js
CHANGED
|
@@ -144,8 +144,12 @@ function NotificationItem({ notification, density = "default", variant = "row",
|
|
|
144
144
|
};
|
|
145
145
|
if (variant === "card") {
|
|
146
146
|
const activityDensity = (_d = notification.activityDensity) !== null && _d !== void 0 ? _d : density;
|
|
147
|
-
const
|
|
148
|
-
notification.
|
|
147
|
+
const hasDescription = notification.description !== undefined &&
|
|
148
|
+
notification.description !== null &&
|
|
149
|
+
notification.description !== "";
|
|
150
|
+
const actorBelongsInHeader = notification.actor !== undefined && !hasDescription && !hasActions;
|
|
151
|
+
const hasCardContent = hasDescription ||
|
|
152
|
+
(notification.actor !== undefined && !actorBelongsInHeader) ||
|
|
149
153
|
hasActions;
|
|
150
154
|
const activityElement = notification.href && !hasActions
|
|
151
155
|
? "a"
|
|
@@ -154,8 +158,8 @@ function NotificationItem({ notification, density = "default", variant = "row",
|
|
|
154
158
|
: "article";
|
|
155
159
|
const unreadIndicator = isUnread && unreadLabel !== null ? (_jsx("span", { "data-slot": "notification-unread-indicator", className: "block size-2.5 rounded-full bg-primary ring-2 ring-background", children: _jsx("span", { className: "sr-only", children: unreadLabel }) })) : null;
|
|
156
160
|
const cardTrailing = notification.badge || unreadIndicator ? (_jsxs("span", { className: "flex items-center gap-[var(--space-2)]", children: [notification.badge, unreadIndicator] })) : null;
|
|
157
|
-
return (_jsx(ActivityCard, { as: activityElement, "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, density: activityDensity, icon: _jsx(Icon, { className: activityDensity === "compact" ? "size-3.5" : "size-[24px]" }), iconTone: notification.iconAppearance === "plain" ? undefined : tone, title: notification.title, titleLines: labelLines, meta: meta, trailing: cardTrailing, href: notification.href, className: cn("group relative w-full text-left outline-none transition-colors", isUnread && "bg-primary/[0.045]", hasHoverSurface && "hover:bg-accent/70", isInteractive &&
|
|
158
|
-
"cursor-pointer focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", className), onClick: isInteractive ? handleClick : undefined, ...props, children: hasCardContent ? (_jsxs(ActivityCardContent, { density: activityDensity, children: [
|
|
161
|
+
return (_jsx(ActivityCard, { as: activityElement, "data-slot": "notification-item", "data-unread": isUnread ? "" : undefined, density: activityDensity, icon: _jsx(Icon, { className: activityDensity === "compact" ? "size-3.5" : "size-[24px]" }), iconTone: notification.iconAppearance === "plain" ? undefined : tone, title: notification.title, titleLines: labelLines, meta: meta, headerContent: actorBelongsInHeader ? (_jsx(ActivityCardActor, { actor: notification.actor, label: actorLabel })) : undefined, trailing: cardTrailing, href: notification.href, className: cn("group relative w-full text-left outline-none transition-colors", isUnread && "bg-primary/[0.045]", hasHoverSurface && "hover:bg-accent/70", isInteractive &&
|
|
162
|
+
"cursor-pointer focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", className), onClick: isInteractive ? handleClick : undefined, ...props, children: hasCardContent ? (_jsxs(ActivityCardContent, { density: activityDensity, children: [hasDescription ? (_jsx(Typography, { as: "div", variant: "bodySm", className: cn("text-left", isUnread ? "text-foreground/75" : "text-muted-foreground", descriptionLineClasses[descriptionLines]), children: notification.description })) : null, notification.actor !== undefined && !actorBelongsInHeader ? (_jsx(ActivityCardActor, { actor: notification.actor, label: actorLabel })) : null, hasActions ? (_jsx("div", { className: "flex flex-wrap gap-2", children: actions.map((action, index) => {
|
|
159
163
|
var _a, _b;
|
|
160
164
|
return (_jsx(Button, { type: "button", variant: (_a = action.variant) !== null && _a !== void 0 ? _a : (index === 0 ? "outline" : "default"), size: "sm", disabled: action.disabled, className: cn("min-w-24", action.className), onClick: (event) => {
|
|
161
165
|
var _a;
|
package/dist/page-section.d.ts
CHANGED
|
@@ -8,8 +8,9 @@ export interface PageSectionProps extends Omit<React.HTMLAttributes<HTMLElement>
|
|
|
8
8
|
description?: React.ReactNode;
|
|
9
9
|
headerAction?: React.ReactNode;
|
|
10
10
|
titleAdornment?: React.ReactNode;
|
|
11
|
+
titleAdornmentSpacing?: "default" | "comfortable";
|
|
11
12
|
headerClassName?: string;
|
|
12
13
|
contentClassName?: string;
|
|
13
14
|
}
|
|
14
|
-
declare function PageSection({ title, description, headerAction, titleAdornment, className, headerClassName, contentClassName, children, ...props }: PageSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
declare function PageSection({ title, description, headerAction, titleAdornment, titleAdornmentSpacing, className, headerClassName, contentClassName, children, ...props }: PageSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
15
16
|
export { PageSection, PageSectionGroup };
|
package/dist/page-section.js
CHANGED
|
@@ -6,9 +6,9 @@ function PageSectionGroup({ variant = "divided", className, ...props }) {
|
|
|
6
6
|
return (_jsx("div", { className: cn(variant === "divided" && "divide-y divide-border", variant === "cards" &&
|
|
7
7
|
"space-y-4 [&>[data-slot=page-section]]:rounded-surface [&>[data-slot=page-section]]:border [&>[data-slot=page-section]]:border-border/70 [&>[data-slot=page-section]]:bg-card [&>[data-slot=page-section]]:p-5", className), ...props }));
|
|
8
8
|
}
|
|
9
|
-
function PageSection({ title, description, headerAction, titleAdornment, className, headerClassName, contentClassName, children, ...props }) {
|
|
9
|
+
function PageSection({ title, description, headerAction, titleAdornment, titleAdornmentSpacing = "default", className, headerClassName, contentClassName, children, ...props }) {
|
|
10
10
|
const renderedTitle = React.isValidElement(title) ? (title) : (_jsx(Typography, { as: "h3", variant: "subheading", children: title }));
|
|
11
11
|
const renderedDescription = description == null ? null : React.isValidElement(description) ? (description) : (_jsx(Typography, { as: "p", variant: "caption", children: description }));
|
|
12
|
-
return (_jsxs("section", { "data-slot": "page-section", className: cn("py-6", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", headerClassName), children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [renderedTitle, titleAdornment] }), renderedDescription] }), headerAction ? (_jsx("div", { className: "flex flex-wrap items-center justify-end gap-3 sm:shrink-0", children: headerAction })) : null] }), children ? (_jsx("div", { className: cn("pt-4", contentClassName), children: children })) : null] }));
|
|
12
|
+
return (_jsxs("section", { "data-slot": "page-section", className: cn("py-6", className), ...props, children: [_jsxs("div", { className: cn("flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", headerClassName), children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsxs("div", { className: cn("flex flex-wrap items-center", titleAdornmentSpacing === "comfortable" ? "gap-4" : "gap-1.5"), children: [renderedTitle, titleAdornment] }), renderedDescription] }), headerAction ? (_jsx("div", { className: "flex flex-wrap items-center justify-end gap-3 sm:shrink-0", children: headerAction })) : null] }), children ? (_jsx("div", { className: cn("pt-4", contentClassName), children: children })) : null] }));
|
|
13
13
|
}
|
|
14
14
|
export { PageSection, PageSectionGroup };
|
package/dist/popover.d.ts
CHANGED
|
@@ -27,3 +27,8 @@ export interface PopoverTitleProps extends Omit<React.ComponentPropsWithoutRef<t
|
|
|
27
27
|
}
|
|
28
28
|
declare function PopoverTitle({ className, children, ...props }: PopoverTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
29
29
|
export { Popover, PopoverTitle };
|
|
30
|
+
export interface PopoverListProps {
|
|
31
|
+
items: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
/** Read-only information rows for a popover, without implying menu actions. */
|
|
34
|
+
export declare function PopoverList({ items }: PopoverListProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/popover.js
CHANGED
|
@@ -87,3 +87,7 @@ function PopoverTitle({ className, children, ...props }) {
|
|
|
87
87
|
return (_jsx(Typography, { as: "p", variant: "label", "data-slot": "popover-title", className: className, ...props, children: children }));
|
|
88
88
|
}
|
|
89
89
|
export { Popover, PopoverTitle };
|
|
90
|
+
/** Read-only information rows for a popover, without implying menu actions. */
|
|
91
|
+
export function PopoverList({ items }) {
|
|
92
|
+
return (_jsx("ul", { "data-slot": "popover-list", className: "max-h-64 space-y-3 overflow-y-auto text-sm text-foreground", children: items.map((item, index) => (_jsxs("li", { className: "flex items-start gap-2", children: [_jsx("span", { "aria-hidden": "true", className: "mt-2 size-1 shrink-0 rounded-full bg-muted-foreground" }), _jsx("span", { className: "min-w-0 break-words", children: item })] }, index))) }));
|
|
93
|
+
}
|
package/dist/rich-text-editor.js
CHANGED
|
@@ -351,11 +351,11 @@ export function RichTextEditor({ variant = "default", placeholder = "Type messag
|
|
|
351
351
|
setTemplatePopoverOpen(false);
|
|
352
352
|
};
|
|
353
353
|
const hasSendActions = Boolean(sendActions === null || sendActions === void 0 ? void 0 : sendActions.length);
|
|
354
|
-
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: () => {
|
|
355
355
|
void handleSend();
|
|
356
356
|
}, children: _jsx(PopoverMenuSection, { children: sendActions === null || sendActions === void 0 ? void 0 : sendActions.map((action) => (_jsx(PopoverMenuItem, { disabled: sendDisabled || action.disabled, onClick: () => {
|
|
357
357
|
void handleSend(action.metadata);
|
|
358
|
-
}, 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: () => {
|
|
359
359
|
void handleSend();
|
|
360
360
|
}, children: [_jsx(SendIcon, { size: 16 }), isSending ? sendingLabel : sendLabel] }));
|
|
361
361
|
if (variant === "message") {
|
|
@@ -373,9 +373,7 @@ export function RichTextEditor({ variant = "default", placeholder = "Type messag
|
|
|
373
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
374
|
var _a;
|
|
375
375
|
return (_jsxs("div", { children: [_jsx("div", { className: "sticky top-0 bg-muted px-3 py-2 text-sm font-semibold text-foreground", children: (_a = templateCategoryLabels[category]) !== null && _a !== void 0 ? _a : category }), templates.map((template) => (_jsxs("button", { type: "button", className: "w-full border-b border-border/50 px-3 py-2 text-left text-sm transition-colors last:border-0 hover:bg-muted", onClick: () => applyTemplate(template), children: [_jsx("div", { className: "truncate font-medium", children: template.name }), template.description ? (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: template.description })) : null] }, template.id)))] }, category));
|
|
376
|
-
}) }), 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,
|
|
377
|
-
void handleSend();
|
|
378
|
-
}, children: isSending ? sendingLabel : sendLabel })] })] })] }));
|
|
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 })] })] })] }));
|
|
379
377
|
}
|
|
380
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 &&
|
|
381
379
|
linkMenuPosition &&
|
|
@@ -415,5 +413,5 @@ export function RichTextEditor({ variant = "default", placeholder = "Type messag
|
|
|
415
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 }) => {
|
|
416
414
|
var _a;
|
|
417
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));
|
|
418
|
-
}) }), 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 }) })] }));
|
|
419
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 };
|
package/dist/search-input.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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;
|
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", 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
|
+
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) => {
|
|
@@ -67,7 +69,7 @@ const SearchInput = React.forwardRef(({ value, onValueChange, label = "Search",
|
|
|
67
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 };
|
|
@@ -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
|
+
}
|
package/dist/table.js
CHANGED
|
@@ -171,7 +171,7 @@ const TableHead = React.forwardRef(({ className, style, width, minWidth, maxWidt
|
|
|
171
171
|
treatWidthAsMinWidth,
|
|
172
172
|
}), ...props })));
|
|
173
173
|
TableHead.displayName = "TableHead";
|
|
174
|
-
const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-[4px] align-middle text-card-foreground [&:has([role=checkbox])]:pr-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
|
|
174
|
+
const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-[4px] align-middle text-card-foreground [&:has([role=checkbox])]:pr-0 [&_[data-slot=button]]:px-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
|
|
175
175
|
style,
|
|
176
176
|
width,
|
|
177
177
|
minWidth,
|
package/dist/tailwind-preset.js
CHANGED
|
@@ -147,6 +147,14 @@ export const neaTailwindPreset = {
|
|
|
147
147
|
12: "var(--space-12)",
|
|
148
148
|
},
|
|
149
149
|
keyframes: {
|
|
150
|
+
"collapsible-down": {
|
|
151
|
+
from: { height: "0" },
|
|
152
|
+
to: { height: "var(--radix-collapsible-content-height)" },
|
|
153
|
+
},
|
|
154
|
+
"collapsible-up": {
|
|
155
|
+
from: { height: "var(--radix-collapsible-content-height)" },
|
|
156
|
+
to: { height: "0" },
|
|
157
|
+
},
|
|
150
158
|
"accordion-down": {
|
|
151
159
|
from: { height: "0" },
|
|
152
160
|
to: { height: "var(--radix-accordion-content-height)" },
|
|
@@ -157,6 +165,8 @@ export const neaTailwindPreset = {
|
|
|
157
165
|
},
|
|
158
166
|
},
|
|
159
167
|
animation: {
|
|
168
|
+
"collapsible-down": "collapsible-down 0.2s ease-out",
|
|
169
|
+
"collapsible-up": "collapsible-up 0.2s ease-out",
|
|
160
170
|
"accordion-down": "accordion-down 0.2s ease-out",
|
|
161
171
|
"accordion-up": "accordion-up 0.2s ease-out",
|
|
162
172
|
},
|
package/dist/textarea.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export interface TextareaProps extends Omit<React.ComponentProps<typeof Textarea
|
|
|
4
4
|
label: React.ReactNode;
|
|
5
5
|
size?: "default" | "comfortable";
|
|
6
6
|
description?: React.ReactNode;
|
|
7
|
+
/** Show a word counter and validate this limit without truncating the entered text. */
|
|
8
|
+
maxWords?: number;
|
|
9
|
+
/** Display a character counter for maxLength; maxWords takes precedence. */
|
|
10
|
+
showCount?: boolean;
|
|
7
11
|
error?: React.ReactNode;
|
|
8
12
|
invalid?: boolean;
|
|
9
13
|
hideLabel?: boolean;
|
package/dist/textarea.js
CHANGED
|
@@ -2,12 +2,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
4
4
|
import { inputControlPaddingClassName } from "./input-control";
|
|
5
|
+
import { FieldCounter } from "./field-counter";
|
|
5
6
|
import { cn } from "./lib/utils";
|
|
6
7
|
const TextareaControl = React.forwardRef(({ className, ...props }, ref) => (_jsx("textarea", { ref: ref, "data-slot": "textarea", className: cn("flex min-h-16 w-full rounded-control border border-input bg-background text-sm transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20", inputControlPaddingClassName, className), ...props })));
|
|
7
8
|
TextareaControl.displayName = "TextareaControl";
|
|
8
|
-
const Textarea = React.forwardRef(({ label, size = "default", description, error, invalid = false, hideLabel = false, className, textareaClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, ...props }, ref) => {
|
|
9
|
+
const Textarea = React.forwardRef(({ label, size = "default", description, maxWords, showCount = false, value, defaultValue, onChange, error: suppliedError, invalid = false, hideLabel = false, className, textareaClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, ...props }, ref) => {
|
|
10
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : "");
|
|
11
|
+
const text = String(value !== null && value !== void 0 ? value : uncontrolledValue);
|
|
12
|
+
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
13
|
+
const hasWordLimit = maxWords !== undefined;
|
|
14
|
+
const error = suppliedError || (hasWordLimit && words > maxWords
|
|
15
|
+
? `Use ${maxWords} words or fewer.`
|
|
16
|
+
: undefined);
|
|
9
17
|
const generatedId = React.useId();
|
|
10
18
|
const textareaId = id !== null && id !== void 0 ? id : `textarea-${generatedId}`;
|
|
19
|
+
const limit = maxWords !== null && maxWords !== void 0 ? maxWords : (showCount ? props.maxLength : undefined);
|
|
20
|
+
const wordLimitId = limit !== undefined ? `${textareaId}-word-limit` : undefined;
|
|
11
21
|
const errorId = error ? `${textareaId}-error` : undefined;
|
|
12
22
|
const showDescription = Boolean(description) && !error;
|
|
13
23
|
const descriptionId = showDescription ? `${textareaId}-description` : undefined;
|
|
@@ -15,11 +25,15 @@ const Textarea = React.forwardRef(({ label, size = "default", description, error
|
|
|
15
25
|
ariaInvalid === true ||
|
|
16
26
|
ariaInvalid === "true" ||
|
|
17
27
|
Boolean(error);
|
|
18
|
-
const describedBy = [ariaDescribedBy, descriptionId, errorId]
|
|
28
|
+
const describedBy = [ariaDescribedBy, descriptionId, errorId, wordLimitId]
|
|
19
29
|
.filter(Boolean)
|
|
20
30
|
.join(" ") || undefined;
|
|
21
31
|
return (_jsxs(Field, { className: cn(size === "comfortable" && "gap-[8px]", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: textareaId, className: cn(size === "comfortable" && "text-[16px] leading-[24px]", hideLabel && "sr-only", labelClassName), children: label }), _jsx(TextareaControl, { ref: ref, id: textareaId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": describedBy, className: cn(size === "comfortable" &&
|
|
22
|
-
"min-h-[76px] px-[16px] py-[12px] text-[16px] leading-[24px]", textareaClassName), ...props
|
|
32
|
+
"min-h-[76px] px-[16px] py-[12px] text-[16px] leading-[24px]", textareaClassName), ...props, value: value, defaultValue: defaultValue, onChange: (event) => {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
setUncontrolledValue(event.target.value);
|
|
35
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
36
|
+
} }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: cn(size === "comfortable" && "text-[14px] leading-[20px]", descriptionClassName), children: description })) : null, limit !== undefined && _jsx(FieldCounter, { id: wordLimitId, count: hasWordLimit ? words : text.length, limit: limit, unit: hasWordLimit ? "words" : "characters" }), _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
23
37
|
});
|
|
24
38
|
Textarea.displayName = "Textarea";
|
|
25
39
|
export { Textarea };
|