@neasg/design-system 0.4.113 → 0.4.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button.d.ts +2 -0
- package/dist/button.js +4 -2
- package/dist/card.d.ts +2 -0
- package/dist/card.js +2 -2
- package/dist/checklist.js +5 -4
- package/dist/dialog.d.ts +4 -2
- package/dist/dialog.js +8 -3
- package/dist/editable-table.js +2 -2
- package/dist/empty-state.d.ts +5 -1
- package/dist/empty-state.js +2 -2
- package/dist/file-upload-status-row.d.ts +14 -0
- package/dist/file-upload-status-row.js +51 -0
- package/dist/file-upload-status-toast.js +7 -46
- package/dist/file-upload.js +10 -25
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/label-value-grid.js +3 -1
- package/dist/popover.d.ts +2 -1
- package/dist/popover.js +2 -2
- package/dist/status-banner.d.ts +15 -0
- package/dist/status-banner.js +6 -0
- package/dist/table-empty-state.d.ts +3 -0
- package/dist/table-empty-state.js +7 -0
- package/dist/table.js +4 -4
- package/package.json +1 -1
package/dist/button.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|
|
10
10
|
* Use this for links or custom interactive elements that should look like a button.
|
|
11
11
|
*/
|
|
12
12
|
asChild?: boolean;
|
|
13
|
+
/** Allow the label to wrap within its container and the button to grow in height. */
|
|
14
|
+
wrap?: boolean;
|
|
13
15
|
/**
|
|
14
16
|
* Shows a loading indicator, disables interaction, and sets `aria-busy`.
|
|
15
17
|
* When used with `asChild`, the component also prevents click handling and sets `aria-disabled`.
|
package/dist/button.js
CHANGED
|
@@ -102,7 +102,7 @@ function attachAnimatedIconRefs(node, refs) {
|
|
|
102
102
|
}
|
|
103
103
|
return node;
|
|
104
104
|
}
|
|
105
|
-
const Button = React.forwardRef(({ className, variant, size, asChild = false, loading = false, loadingText, loadingIndicator, disabled, children, onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, tabIndex, ...props }, ref) => {
|
|
105
|
+
const Button = React.forwardRef(({ className, variant, size, asChild = false, wrap = false, loading = false, loadingText, loadingIndicator, disabled, children, onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, tabIndex, ...props }, ref) => {
|
|
106
106
|
const Comp = asChild ? Slot : "button";
|
|
107
107
|
const isDisabled = disabled || loading;
|
|
108
108
|
const loadingLabel = loadingText !== null && loadingText !== void 0 ? loadingText : children;
|
|
@@ -147,7 +147,9 @@ const Button = React.forwardRef(({ className, variant, size, asChild = false, lo
|
|
|
147
147
|
stopAnimatedIcons();
|
|
148
148
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
149
149
|
};
|
|
150
|
-
return (_jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size
|
|
150
|
+
return (_jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size }), wrap && "h-auto max-w-full whitespace-normal break-words py-1.5", wrap && (size === "sm" || size === "compact" || size === "compactIcon"
|
|
151
|
+
? "min-h-8"
|
|
152
|
+
: size === "comfortable" ? "min-h-[46px]" : "min-h-control"), className), ref: ref, "aria-busy": loading || undefined, "aria-disabled": isDisabled || undefined, "data-loading": loading ? "" : undefined, "data-disabled": isDisabled ? "" : undefined, onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onFocus: handleFocus, onBlur: handleBlur, tabIndex: isDisabled && asChild ? -1 : tabIndex, disabled: !asChild ? isDisabled : undefined, ...props, children: content }));
|
|
151
153
|
});
|
|
152
154
|
Button.displayName = "Button";
|
|
153
155
|
export { Button, buttonVariants };
|
package/dist/card.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "t
|
|
|
13
13
|
accent?: "primary";
|
|
14
14
|
/** Optional visual affordance shown in the top-right corner, such as a filter icon. */
|
|
15
15
|
icon?: React.ReactNode;
|
|
16
|
+
/** Interactive action beside the card title. */
|
|
17
|
+
headerAction?: React.ReactNode;
|
|
16
18
|
/** Replaces title/stat/subMetric with skeleton placeholders while data is loading. */
|
|
17
19
|
loading?: boolean;
|
|
18
20
|
}
|
package/dist/card.js
CHANGED
|
@@ -55,7 +55,7 @@ function attachCardIconRef(node, ref) {
|
|
|
55
55
|
ref: mergeRefs(node.ref, refCallback),
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
const Card = React.forwardRef(({ title, description, variant = "default", size = "default", stat, subMetric, footer, active, accent, icon, loading = false, children, className, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, role, tabIndex, "aria-pressed": ariaPressed, ...props }, ref) => {
|
|
58
|
+
const Card = React.forwardRef(({ title, description, variant = "default", size = "default", stat, subMetric, footer, active, accent, icon, headerAction, loading = false, children, className, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, role, tabIndex, "aria-pressed": ariaPressed, ...props }, ref) => {
|
|
59
59
|
const interactive = Boolean(onClick);
|
|
60
60
|
const hasStat = stat !== undefined;
|
|
61
61
|
const iconRef = React.useRef(null);
|
|
@@ -111,7 +111,7 @@ const Card = React.forwardRef(({ title, description, variant = "default", size =
|
|
|
111
111
|
"border-[hsl(var(--card-border))] shadow-[inset_4px_0_0_0_hsl(var(--primary))]", interactive &&
|
|
112
112
|
"cursor-pointer transition-[border-color,background-color,box-shadow,transform] duration-150 ease-out hover:border-primary/50 hover:bg-primary/5 hover:shadow-md motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0 motion-safe:active:scale-[0.99] focus-visible:border-primary/50 focus-visible:bg-primary/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", className), ...props, children: [_jsxs("div", { className: cn("relative flex flex-col gap-2", size === "default" && "p-4", size === "compact" && "p-3", showIcon && size === "default" && "pr-12", showIcon && size === "compact" && "pr-10"), children: [showIcon ? (_jsx("span", { "aria-hidden": "true", "data-slot": "card-icon", className: cn("pointer-events-none absolute right-3 top-3 inline-flex size-7 items-center justify-center rounded-control border border-[hsl(var(--card-border))] bg-background/80 text-muted-foreground shadow-sm transition-[background-color,color,transform] duration-150 ease-out group-hover/card:scale-105 group-hover/card:bg-highlight group-hover/card:text-highlight-foreground group-focus-visible/card:bg-highlight group-focus-visible/card:text-highlight-foreground motion-reduce:transition-colors [&_svg]:size-3.5", size === "compact" && "right-2.5 top-2.5 size-6 [&_svg]:size-3", active &&
|
|
113
113
|
"border-primary/30 bg-highlight text-highlight-foreground", variant === "gradient" &&
|
|
114
|
-
"border-white/25 bg-white/10 text-current group-hover/card:bg-white/20 group-hover/card:text-current group-focus-visible/card:bg-white/20 group-focus-visible/card:text-current"), children: renderedIcon })) : null, loading ? (_jsx(Skeleton, { className: "h-3.5 w-24" })) : (renderedTitle), loading
|
|
114
|
+
"border-white/25 bg-white/10 text-current group-hover/card:bg-white/20 group-hover/card:text-current group-focus-visible/card:bg-white/20 group-focus-visible/card:text-current"), children: renderedIcon })) : null, loading ? (_jsx(Skeleton, { className: "h-3.5 w-24" })) : (headerAction ? (_jsxs("div", { className: "flex items-start justify-between gap-3", children: [renderedTitle, _jsx("div", { className: "shrink-0", children: headerAction })] })) : renderedTitle), loading
|
|
115
115
|
? description != null && _jsx(Skeleton, { className: "h-5 w-40" })
|
|
116
116
|
: renderedDescription, loading ? (stat !== undefined && _jsx(Skeleton, { className: "h-8 w-20" })) : stat ? (_jsx(Typography, { as: "p", variant: "title", color: variant === "gradient" ? "inherit" : undefined, children: stat })) : null, loading ? (subMetric !== undefined && _jsx(Skeleton, { className: "h-5 w-28" })) : subMetric ? (_jsx(Typography, { as: "p", variant: "caption", color: variant === "gradient" ? "inherit" : undefined, className: variant === "gradient" ? "opacity-80" : undefined, children: subMetric })) : null] }), children ? (_jsx("div", { className: cn(size === "default" && "p-4 pt-0", size === "compact" && "p-3 pt-0"), children: children })) : null, footer ? (_jsx("div", { className: cn("flex items-center", size === "default" && "p-4 pt-0", size === "compact" && "p-3 pt-0"), children: footer })) : null] }));
|
|
117
117
|
});
|
package/dist/checklist.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Camera, ChevronDown, ClipboardList, LoaderCircle, MessageSquareText, Tr
|
|
|
6
6
|
import { Button } from "./button";
|
|
7
7
|
import { Select } from "./select";
|
|
8
8
|
import { Textarea } from "./textarea";
|
|
9
|
+
import { Tooltip } from "./tooltip";
|
|
9
10
|
import { cn } from "./lib/utils";
|
|
10
11
|
export function ChecklistPanel({ title, action, children }) {
|
|
11
12
|
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] });
|
|
@@ -19,11 +20,11 @@ export function ChecklistSelection({ title, description, typeOptions, subtypeOpt
|
|
|
19
20
|
}
|
|
20
21
|
export function ChecklistQuestion({ label, options, value, onValueChange, disabled, remarks, onRemarksChange, remarksLimit = 500, remarksMaxLength, remarksRequired = false, evidence, evidenceCount = 0 }) {
|
|
21
22
|
const name = React.useId();
|
|
22
|
-
|
|
23
|
-
setShowRemarks(true); }, [remarksRequired, value]);
|
|
24
|
-
const [showRemarks, setShowRemarks] = React.useState(Boolean(remarks));
|
|
23
|
+
const [showRemarks, setShowRemarks] = React.useState(false);
|
|
25
24
|
const [showEvidence, setShowEvidence] = React.useState(false);
|
|
26
|
-
|
|
25
|
+
const remarksToggleLabel = showRemarks ? "Hide remarks" : "Show remarks";
|
|
26
|
+
const evidenceToggleLabel = showEvidence ? "Hide photos" : "Show photos";
|
|
27
|
+
return _jsxs("div", { "data-slot": "checklist-question", className: "space-y-4 rounded-xl border border-border bg-card p-4 shadow-xs", children: [_jsx("p", { id: `${name}-label`, className: "text-sm font-medium", children: label }), _jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx("fieldset", { disabled: disabled, "aria-labelledby": `${name}-label`, className: cn("flex min-w-0 border-0 p-0", disabled && "opacity-50"), children: options.map((option, index) => _jsxs("label", { className: cn("relative cursor-pointer border border-input px-2.5 py-2 text-sm font-medium focus-within:z-10 focus-within:ring-2 focus-within:ring-ring", index > 0 && "-ml-px", index === 0 && "rounded-l-md", index === options.length - 1 && "rounded-r-md", disabled && "cursor-not-allowed", value === option.value ? option.tone === "negative" ? "bg-destructive/20" : option.tone === "positive" ? "bg-primary/20" : "bg-muted" : "bg-background"), children: [_jsx("input", { className: "absolute inset-0 size-full cursor-inherit opacity-0", type: "radio", name: name, value: option.value, checked: value === option.value, onChange: () => onValueChange(option.value) }), option.label] }, option.value)) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tooltip, { content: remarksToggleLabel, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": remarksToggleLabel, "aria-expanded": showRemarks, "aria-controls": `${name}-remarks`, onClick: () => setShowRemarks((open) => !open), children: _jsx(MessageSquareText, { "aria-hidden": "true", size: 16 }) }) }), evidence ? _jsx(Tooltip, { content: `${evidenceToggleLabel}${evidenceCount > 0 ? ` (${evidenceCount})` : ""}`, children: _jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", "aria-label": evidenceToggleLabel, "aria-expanded": showEvidence, "aria-controls": `${name}-evidence`, onClick: () => setShowEvidence((open) => !open), children: _jsx(Camera, { "aria-hidden": "true", size: 16 }) }) }) : null] })] }), showEvidence && evidence ? _jsx("div", { id: `${name}-evidence`, children: evidence }) : null, showRemarks ? _jsx("div", { id: `${name}-remarks`, children: _jsx(Textarea, { label: "Remarks", hideLabel: true, placeholder: "Type...", value: remarks, disabled: disabled, "aria-required": remarksRequired, rows: 5, onChange: (event) => onRemarksChange(event.target.value), maxWords: remarksMaxLength === undefined ? remarksLimit : undefined, maxLength: remarksMaxLength, showCount: remarksMaxLength !== undefined }) }) : null] });
|
|
27
28
|
}
|
|
28
29
|
export function ChecklistAttachments({ items, onRemove }) {
|
|
29
30
|
return _jsx("div", { className: "flex flex-wrap gap-2", children: items.map((item) => _jsxs("div", { className: "relative", children: [_jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: _jsx("img", { src: item.url, alt: item.name, className: "size-20 rounded-md border border-border object-cover" }) }), onRemove ? _jsx(Button, { variant: "outline", size: "compactIcon", "aria-label": `Remove ${item.name}`, onClick: () => onRemove(item.key), children: _jsx(Trash2, { "aria-hidden": "true", size: 16 }) }) : null] }, item.key)) });
|
package/dist/dialog.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ 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
|
+
variant?: "default" | "confirmation" | "routingConfirmation";
|
|
13
|
+
/** Standard dialogs are 32rem wide; larger sizes are for complex editors. */
|
|
14
|
+
size?: "standard" | "large" | "extraLarge" | "full";
|
|
13
15
|
open?: boolean;
|
|
14
16
|
defaultOpen?: boolean;
|
|
15
17
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -27,5 +29,5 @@ export interface DialogProps {
|
|
|
27
29
|
/** Whether a pointer interaction outside the dialog closes it. */
|
|
28
30
|
closeOnOutsideClick?: boolean;
|
|
29
31
|
}
|
|
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;
|
|
32
|
+
declare function Dialog({ variant, size, open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton, closeOnOutsideClick, }: DialogProps): import("react/jsx-runtime").JSX.Element;
|
|
31
33
|
export { Dialog };
|
package/dist/dialog.js
CHANGED
|
@@ -32,7 +32,7 @@ function renderDialogAction({ action, close, defaultVariant, }) {
|
|
|
32
32
|
}
|
|
33
33
|
}, children: label }));
|
|
34
34
|
}
|
|
35
|
-
function Dialog({ variant = "default", open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
|
|
35
|
+
function Dialog({ variant = "default", size = "standard", open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, contentClassName, bodyClassName, hideCloseButton = false, closeOnOutsideClick = false, }) {
|
|
36
36
|
const [isOpen, setIsOpen] = useControllableOpen({
|
|
37
37
|
open,
|
|
38
38
|
defaultOpen,
|
|
@@ -44,11 +44,16 @@ function Dialog({ variant = "default", open, defaultOpen, onOpenChange, modal, t
|
|
|
44
44
|
const body = resolveDialogRenderable(children, helpers);
|
|
45
45
|
const customFooter = resolveDialogRenderable(footer, helpers);
|
|
46
46
|
const hasFooter = Boolean(customFooter || primaryAction || secondaryAction);
|
|
47
|
-
|
|
47
|
+
const isCompactConfirmation = variant === "confirmation";
|
|
48
|
+
const isRoutingConfirmation = variant === "routingConfirmation";
|
|
49
|
+
return (_jsxs(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, modal: modal, children: [trigger ? _jsx(DialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(DialogSurface, { ...(!description ? { "aria-describedby": undefined } : {}), className: cn(isCompactConfirmation &&
|
|
50
|
+
"gap-0 overflow-hidden rounded-xl p-0", isRoutingConfirmation &&
|
|
51
|
+
"gap-0 rounded-overlay p-[var(--space-8)]", "w-[calc(100vw-2rem)]", { standard: "max-w-lg", large: "max-w-3xl", extraLarge: "max-w-5xl", full: "max-w-[96rem]" }[size], contentClassName), hideCloseButton: hideCloseButton || isCompactConfirmation || isRoutingConfirmation, onPointerDownOutside: (event) => {
|
|
48
52
|
if (!closeOnOutsideClick) {
|
|
49
53
|
event.preventDefault();
|
|
50
54
|
}
|
|
51
|
-
}, children: [_jsxs("div", { className: cn(
|
|
55
|
+
}, children: [_jsxs("div", { className: cn(isCompactConfirmation && "flex items-start gap-4 p-4"), children: [isCompactConfirmation ? (_jsx("div", { className: "rounded-lg bg-muted p-2", children: _jsx(CircleAlert, { "aria-hidden": "true", className: "size-6" }) })) : null, _jsxs(DialogHeader, { className: cn(isRoutingConfirmation && "gap-3 space-y-0"), children: [_jsx(DialogTitle, { className: cn(isRoutingConfirmation && "text-2xl leading-8"), children: title }), description ? (_jsx(DialogDescription, { className: cn(isRoutingConfirmation &&
|
|
56
|
+
"text-base leading-6 text-foreground"), children: description })) : null] })] }), body ? (_jsx("div", { className: cn("grid gap-4", isRoutingConfirmation && "mt-3 gap-[var(--space-8)]", bodyClassName), children: body })) : null, hasFooter ? (_jsx(DialogFooter, { className: cn(isCompactConfirmation && "border-t bg-muted/30 p-4", isRoutingConfirmation && "mt-[var(--space-8)] gap-3"), children: customFooter !== null && customFooter !== void 0 ? customFooter : (_jsxs(_Fragment, { children: [secondaryAction
|
|
52
57
|
? renderDialogAction({
|
|
53
58
|
action: secondaryAction,
|
|
54
59
|
close: helpers.close,
|
package/dist/editable-table.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Button } from "./button";
|
|
|
4
4
|
import { TableAddRow } from "./table-add-row";
|
|
5
5
|
import { TrashIcon } from "./animated-icons/trash";
|
|
6
6
|
import { Checkbox } from "./checkbox";
|
|
7
|
-
import {
|
|
7
|
+
import { TableEmptyState } from "./table-empty-state";
|
|
8
8
|
import { InputControl } from "./input-control";
|
|
9
9
|
import { TableRoot as Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "./table";
|
|
10
10
|
import { cn } from "./lib/utils";
|
|
@@ -121,7 +121,7 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
|
|
|
121
121
|
const checkableColumnWidth = checkableHeader ? "9rem" : "48px";
|
|
122
122
|
return (_jsxs("div", { className: cn("space-y-2", className), children: [_jsx("div", { className: "overflow-x-auto rounded-surface border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [checkable ? (_jsx(TableHead, { width: checkableColumnWidth, minWidth: checkableColumnWidth, children: checkableHeader })) : null, columns.map((column, index) => (_jsx(TableHead, { width: column.width, minWidth: getColumnMinWidth(column), children: _jsx("div", { className: "whitespace-nowrap", children: column.header }) }, `header-${index}`))), hasActionsColumn ? (_jsx(TableHead, { width: renderRowActions ? undefined : "72px", minWidth: renderRowActions ? "120px" : "72px" })) : null] }) }), _jsx(TableBody, { children: value.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length +
|
|
123
123
|
(checkable ? 1 : 0) +
|
|
124
|
-
(hasActionsColumn ? 1 : 0), className: "p-0", children: _jsx(
|
|
124
|
+
(hasActionsColumn ? 1 : 0), className: "p-0", children: _jsx(TableEmptyState, {}) }) })) : (value.map((row, rowIndex) => (_jsxs(TableRow, { className: rowIndex % 2 === 0
|
|
125
125
|
? "bg-[hsl(var(--table-row-striped-background))]"
|
|
126
126
|
: undefined, children: [checkable ? (_jsx(TableCell, { width: checkableColumnWidth, minWidth: checkableColumnWidth, children: _jsx("div", { className: "flex items-center", onClick: (event) => event.stopPropagation(), children: _jsx(Checkbox, { checked: Boolean(row[activeField]), disabled: !isEditing, onChange: isEditing
|
|
127
127
|
? (event) => handleToggleRow(rowIndex, event.currentTarget.checked)
|
package/dist/empty-state.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** Muted panel treatment for section empty states. */
|
|
4
|
+
variant?: "default" | "muted";
|
|
5
|
+
/** Controls spacing between the icon, copy, and action. */
|
|
6
|
+
density?: "default" | "compact";
|
|
3
7
|
message: React.ReactNode;
|
|
4
8
|
description?: React.ReactNode;
|
|
5
9
|
icon?: React.ComponentType<{
|
|
@@ -7,5 +11,5 @@ export interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
7
11
|
}>;
|
|
8
12
|
action?: React.ReactNode;
|
|
9
13
|
}
|
|
10
|
-
declare function EmptyState({ message, description, icon: Icon, action, className, ...props }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
declare function EmptyState({ variant, density, message, description, icon: Icon, action, className, ...props }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
|
|
11
15
|
export { EmptyState };
|
package/dist/empty-state.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "./lib/utils";
|
|
3
3
|
import { Typography } from "./typography";
|
|
4
|
-
function EmptyState({ message, description, icon: Icon, action, className, ...props }) {
|
|
5
|
-
return (_jsxs("div", { className: cn("flex flex-col items-center justify-center px-6 py-10 text-center", className), ...props, children: [Icon ? (_jsx(Icon, { className: "
|
|
4
|
+
function EmptyState({ variant = "default", density = "default", message, description, icon: Icon, action, className, ...props }) {
|
|
5
|
+
return (_jsxs("div", { className: cn("flex flex-col items-center justify-center whitespace-normal px-6 py-10 text-center", variant === "muted" && "rounded-lg bg-muted/30", className), ...props, children: [Icon ? (_jsx(Icon, { className: cn("h-10 w-10 stroke-[1.5] text-muted-foreground/70", density === "compact" ? "mb-2" : "mb-4") })) : null, _jsxs("div", { className: "max-w-full space-y-1.5", children: [_jsx(Typography, { as: "p", variant: "bodySm", className: "font-semibold", children: message }), description ? (_jsx(Typography, { as: "p", variant: "caption", className: "mx-auto max-w-md", children: description })) : null] }), action ? _jsx("div", { className: density === "compact" ? "pt-4" : "pt-6", children: action }) : null] }));
|
|
6
6
|
}
|
|
7
7
|
export { EmptyState };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FileUploadStatus } from "./use-file-upload";
|
|
2
|
+
export declare function FileUploadStatusRow({ file, onCancel, onRemove, disabled, }: {
|
|
3
|
+
file: {
|
|
4
|
+
name: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
size?: number | string;
|
|
7
|
+
status?: FileUploadStatus;
|
|
8
|
+
progress?: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
};
|
|
11
|
+
onCancel?: () => void;
|
|
12
|
+
onRemove?: () => void;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AlertCircle, CheckCircle2, File, FileText, ImageIcon, Trash2, X } from "lucide-react";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { cn } from "./lib/utils";
|
|
6
|
+
import { Progress } from "./progress";
|
|
7
|
+
import { Typography } from "./typography";
|
|
8
|
+
function formatUploadFileSize(size) {
|
|
9
|
+
if (typeof size === "string")
|
|
10
|
+
return size;
|
|
11
|
+
if (typeof size !== "number")
|
|
12
|
+
return "";
|
|
13
|
+
if (size === 0)
|
|
14
|
+
return "0 B";
|
|
15
|
+
const k = 1024;
|
|
16
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
17
|
+
const i = Math.min(Math.floor(Math.log(size) / Math.log(k)), sizes.length - 1);
|
|
18
|
+
return `${parseFloat((size / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
19
|
+
}
|
|
20
|
+
function getStatusIcon(status, type) {
|
|
21
|
+
if (status === "complete") {
|
|
22
|
+
return (_jsx(CheckCircle2, { "aria-hidden": "true", className: "size-5 text-[hsl(var(--status-success-foreground))]" }));
|
|
23
|
+
}
|
|
24
|
+
if (status === "error") {
|
|
25
|
+
return (_jsx(AlertCircle, { "aria-hidden": "true", className: "size-5 text-[hsl(var(--status-destructive-foreground))]" }));
|
|
26
|
+
}
|
|
27
|
+
const Icon = (type === null || type === void 0 ? void 0 : type.startsWith("image/")) ? ImageIcon : type === "application/pdf" ? FileText : File;
|
|
28
|
+
return (_jsx(Icon, { "aria-hidden": "true", className: "size-5 text-muted-foreground" }));
|
|
29
|
+
}
|
|
30
|
+
export function FileUploadStatusRow({ file, onCancel, onRemove, disabled = false, }) {
|
|
31
|
+
var _a;
|
|
32
|
+
const isUploading = file.status === "uploading";
|
|
33
|
+
const isPending = file.status === "pending";
|
|
34
|
+
const isComplete = file.status === "complete";
|
|
35
|
+
const isError = file.status === "error";
|
|
36
|
+
const sizeLabel = formatUploadFileSize(file.size);
|
|
37
|
+
const canCancel = (isUploading || isPending) && onCancel;
|
|
38
|
+
const canRemove = !isUploading && onRemove;
|
|
39
|
+
const action = canCancel ? "cancel" : canRemove ? "remove" : null;
|
|
40
|
+
return (_jsxs("li", { className: cn("rounded-control border bg-background px-2.5 py-2", isError && "border-destructive/80 bg-destructive/5"), children: [_jsxs("div", { className: "grid grid-cols-[1.5rem_minmax(0,1fr)_auto_1.75rem] min-h-7 items-start gap-x-2", children: [_jsx("span", { className: "flex h-7 w-6 shrink-0 items-center justify-center", children: getStatusIcon(file.status, file.type) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1 py-1", children: [_jsx(Typography, { as: "p", variant: "label", className: cn("truncate", isComplete && "text-[hsl(var(--status-success-foreground))]"), title: file.name, children: file.name }), isError ? (_jsx(Typography, { as: "p", variant: "caption", className: "break-words text-xs leading-4 text-destructive", children: (_a = file.error) !== null && _a !== void 0 ? _a : "Upload failed" })) : null] }), sizeLabel ? (_jsx(Typography, { as: "span", variant: "bodySm", className: "col-start-3 mt-1.5 whitespace-nowrap text-xs leading-4 text-muted-foreground", children: sizeLabel })) : null, action ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "col-start-4 h-7 w-7 shrink-0", disabled: disabled, onClick: (event) => {
|
|
41
|
+
event.stopPropagation();
|
|
42
|
+
if (action === "cancel") {
|
|
43
|
+
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
onRemove === null || onRemove === void 0 ? void 0 : onRemove();
|
|
47
|
+
}
|
|
48
|
+
}, "aria-label": action === "cancel"
|
|
49
|
+
? `Cancel upload for ${file.name}`
|
|
50
|
+
: `Remove ${file.name}`, children: action === "cancel" ? (_jsx(X, { "aria-hidden": "true" })) : (_jsx(Trash2, { "aria-hidden": "true" })) })) : null] }), (isUploading || isPending) ? (_jsx(Progress, { className: "mt-2", value: file.progress, size: "sm", label: `Uploading ${file.name}` })) : null] }));
|
|
51
|
+
}
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { X } from "lucide-react";
|
|
4
|
+
import { FileUploadStatusRow } from "./file-upload-status-row";
|
|
4
5
|
import { Button } from "./button";
|
|
5
6
|
import { cn } from "./lib/utils";
|
|
6
|
-
import { Progress } from "./progress";
|
|
7
7
|
import { toast } from "./toaster";
|
|
8
8
|
import { Typography } from "./typography";
|
|
9
9
|
const DEFAULT_TOAST_ID = "file-upload-status";
|
|
10
|
-
function formatUploadFileSize(size) {
|
|
11
|
-
if (typeof size === "string")
|
|
12
|
-
return size;
|
|
13
|
-
if (typeof size !== "number")
|
|
14
|
-
return "";
|
|
15
|
-
if (size === 0)
|
|
16
|
-
return "0 B";
|
|
17
|
-
const k = 1024;
|
|
18
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
19
|
-
const i = Math.min(Math.floor(Math.log(size) / Math.log(k)), sizes.length - 1);
|
|
20
|
-
return `${parseFloat((size / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
21
|
-
}
|
|
22
10
|
function getProgress(uploadProgress, fileName) {
|
|
23
11
|
if (!uploadProgress)
|
|
24
12
|
return undefined;
|
|
@@ -34,6 +22,7 @@ function normalizeFiles({ files, uploadProgress, }) {
|
|
|
34
22
|
const result = (_a = progress === null || progress === void 0 ? void 0 : progress.result) !== null && _a !== void 0 ? _a : file.result;
|
|
35
23
|
return {
|
|
36
24
|
...file,
|
|
25
|
+
name: file.name,
|
|
37
26
|
progress: (_c = (_b = progress === null || progress === void 0 ? void 0 : progress.progress) !== null && _b !== void 0 ? _b : file.progress) !== null && _c !== void 0 ? _c : 0,
|
|
38
27
|
status: (_e = (_d = progress === null || progress === void 0 ? void 0 : progress.status) !== null && _d !== void 0 ? _d : file.status) !== null && _e !== void 0 ? _e : "pending",
|
|
39
28
|
error: (_f = progress === null || progress === void 0 ? void 0 : progress.error) !== null && _f !== void 0 ? _f : file.error,
|
|
@@ -72,43 +61,15 @@ function getDefaultDescription(files) {
|
|
|
72
61
|
].filter(Boolean);
|
|
73
62
|
return parts.length > 0 ? parts.join(" / ") : undefined;
|
|
74
63
|
}
|
|
75
|
-
function getStatusIcon(status) {
|
|
76
|
-
if (status === "complete") {
|
|
77
|
-
return (_jsx(CheckCircle2, { "aria-hidden": "true", className: "h-4 w-4 text-[hsl(var(--status-success-foreground))]" }));
|
|
78
|
-
}
|
|
79
|
-
if (status === "error") {
|
|
80
|
-
return (_jsx(AlertCircle, { "aria-hidden": "true", className: "h-4 w-4 text-[hsl(var(--status-destructive-foreground))]" }));
|
|
81
|
-
}
|
|
82
|
-
return (_jsx(Paperclip, { "aria-hidden": "true", className: "h-4 w-4 text-muted-foreground" }));
|
|
83
|
-
}
|
|
84
|
-
function FileUploadStatusRow({ file, onCancel, onRemove, }) {
|
|
85
|
-
var _a;
|
|
86
|
-
const isUploading = file.status === "uploading";
|
|
87
|
-
const isPending = file.status === "pending";
|
|
88
|
-
const isComplete = file.status === "complete";
|
|
89
|
-
const isError = file.status === "error";
|
|
90
|
-
const sizeLabel = formatUploadFileSize(file.size);
|
|
91
|
-
const canCancel = (isUploading || isPending) && onCancel;
|
|
92
|
-
const canRemove = (isComplete || isError) && onRemove;
|
|
93
|
-
const action = canCancel ? "cancel" : canRemove ? "remove" : null;
|
|
94
|
-
return (_jsxs("li", { className: cn("rounded-control border bg-background px-2.5 py-2", isError && "border-destructive/80 bg-destructive/5"), children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center", children: getStatusIcon(file.status) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Typography, { as: "p", variant: "label", className: cn("truncate", isComplete && "text-[hsl(var(--status-success-foreground))]"), title: file.name, children: file.name }), isError ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "mt-0.5 text-destructive", children: (_a = file.error) !== null && _a !== void 0 ? _a : "Upload failed" })) : null] }), sizeLabel ? (_jsx(Typography, { as: "span", variant: "bodySm", className: "shrink-0 text-muted-foreground", children: sizeLabel })) : null, action ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-7 w-7 shrink-0", onClick: () => {
|
|
95
|
-
if (action === "cancel") {
|
|
96
|
-
onCancel === null || onCancel === void 0 ? void 0 : onCancel(file);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
onRemove === null || onRemove === void 0 ? void 0 : onRemove(file);
|
|
100
|
-
}
|
|
101
|
-
}, "aria-label": action === "cancel"
|
|
102
|
-
? `Cancel upload for ${file.name}`
|
|
103
|
-
: `Remove ${file.name}`, children: action === "cancel" ? (_jsx(X, { "aria-hidden": "true" })) : (_jsx(Trash2, { "aria-hidden": "true" })) })) : null] }), (isUploading || isPending) ? (_jsx(Progress, { className: "mt-2", value: file.progress, size: "sm", label: `Uploading ${file.name}` })) : null] }));
|
|
104
|
-
}
|
|
105
64
|
export function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, onDismiss, }) {
|
|
106
65
|
const normalizedFiles = normalizeFiles({ files, uploadProgress });
|
|
107
66
|
const resolvedTitle = title !== null && title !== void 0 ? title : getDefaultTitle(normalizedFiles);
|
|
108
67
|
const resolvedDescription = description !== null && description !== void 0 ? description : getDefaultDescription(normalizedFiles);
|
|
109
|
-
return (_jsxs("section", { className: cn("w-[min(40rem,calc(100vw-2rem))] overflow-hidden rounded-overlay border border-border bg-background text-foreground shadow-overlay", className), "aria-label": "File upload status", children: [_jsxs("div", { className: "flex items-start gap-3 border-b px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Typography, { as: "p", variant: "label", className: "truncate", children: resolvedTitle }), resolvedDescription ? (_jsx(Typography, { as: "p", variant: "caption", className: "mt-0.5", children: resolvedDescription })) : null] }), onDismiss ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-7 w-7 shrink-0", onClick: onDismiss, "aria-label": "Dismiss file upload status", children: _jsx(X, { "aria-hidden": "true" }) })) : null] }), _jsx("ul", { className: "max-h-80 space-y-2 overflow-y-auto p-2.5", children: normalizedFiles.map((file, index) => {
|
|
68
|
+
return (_jsxs("section", { className: cn("w-[min(40rem,calc(100vw-2rem))] overflow-hidden rounded-overlay border border-border bg-background text-foreground shadow-overlay", className), "aria-label": "File upload status", children: [_jsxs("div", { className: "flex items-start gap-3 border-b px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Typography, { as: "p", variant: "label", className: "truncate", children: resolvedTitle }), resolvedDescription ? (_jsx(Typography, { as: "p", variant: "caption", className: "mt-0.5 text-xs leading-4", children: resolvedDescription })) : null] }), onDismiss ? (_jsx(Button, { type: "button", variant: "ghost", size: "compactIcon", className: "h-7 w-7 shrink-0", onClick: onDismiss, "aria-label": "Dismiss file upload status", children: _jsx(X, { "aria-hidden": "true" }) })) : null] }), _jsx("ul", { className: "max-h-80 space-y-2 overflow-y-auto p-2.5", children: normalizedFiles.map((file, index) => {
|
|
110
69
|
var _a;
|
|
111
|
-
return (_jsx(FileUploadStatusRow, { file: file, onCancel: onCancel
|
|
70
|
+
return (_jsx(FileUploadStatusRow, { file: file, onCancel: onCancel ? () => onCancel(file) : undefined, onRemove: onRemove && (file.status === "complete" || file.status === "error")
|
|
71
|
+
? () => onRemove(file)
|
|
72
|
+
: undefined }, `${file.name}-${(_a = file.size) !== null && _a !== void 0 ? _a : "unknown"}-${index}`));
|
|
112
73
|
}) })] }));
|
|
113
74
|
}
|
|
114
75
|
export function showFileUploadStatusToast({ id = DEFAULT_TOAST_ID, duration = Number.POSITIVE_INFINITY, dismissible = true, onDismiss, ...props }) {
|
package/dist/file-upload.js
CHANGED
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useCallback, useState } from "react";
|
|
5
|
-
import { File as FileIcon, ImageIcon } from "lucide-react";
|
|
6
|
-
import { CheckIcon } from "./animated-icons/check";
|
|
7
|
-
import { FileTextIcon } from "./animated-icons/file-text";
|
|
8
5
|
import { UploadIcon } from "./animated-icons/upload";
|
|
9
|
-
import { XIcon } from "./animated-icons/x";
|
|
10
|
-
import { Button } from "./button";
|
|
11
6
|
import { cn } from "./lib/utils";
|
|
12
|
-
import {
|
|
7
|
+
import { FileUploadStatusRow } from "./file-upload-status-row";
|
|
13
8
|
import { Typography } from "./typography";
|
|
14
9
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
15
10
|
function formatFileSize(bytes) {
|
|
@@ -20,15 +15,6 @@ function formatFileSize(bytes) {
|
|
|
20
15
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
21
16
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
22
17
|
}
|
|
23
|
-
function getFileIcon(file) {
|
|
24
|
-
if (file.type.startsWith("image/")) {
|
|
25
|
-
return _jsx(ImageIcon, { className: "h-4 w-4 text-muted-foreground" });
|
|
26
|
-
}
|
|
27
|
-
if (file.type === "application/pdf") {
|
|
28
|
-
return _jsx(FileTextIcon, { className: "text-muted-foreground", size: 16 });
|
|
29
|
-
}
|
|
30
|
-
return _jsx(FileIcon, { className: "h-4 w-4 text-muted-foreground" });
|
|
31
|
-
}
|
|
32
18
|
export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles = 10, onFilesChange, value = [], disabled = false, className, uploadProgress, }) {
|
|
33
19
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
34
20
|
const [error, setError] = useState(null);
|
|
@@ -92,16 +78,15 @@ export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles =
|
|
|
92
78
|
const fileInput = (_jsx("input", { ref: inputRef, type: "file", accept: accept, multiple: maxFiles > 1, onChange: handleInputChange, disabled: disabled, className: "hidden" }));
|
|
93
79
|
return (_jsxs("div", { className: cn("space-y-2", className), children: [fileInput, _jsxs("div", { onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onClick: openFilePicker, className: cn("relative flex cursor-pointer flex-col items-center justify-center gap-1.5 rounded-surface border border-dashed p-4 transition-colors", isDragOver && !disabled
|
|
94
80
|
? "border-primary bg-primary/5"
|
|
95
|
-
: "border-muted-foreground/25 hover:border-muted-foreground/50", disabled && "cursor-not-allowed opacity-50"), children: [_jsx(UploadIcon, { className: cn(isDragOver ? "text-interactive" : "text-muted-foreground"), size: 28 }), _jsxs("div", { className: "text-center", children: [_jsx(Typography, { as: "p", variant: "label", children: isDragOver ? "Drop files here" : "Drag & drop files here" }), _jsxs(Typography, { as: "p", variant: "caption", className: "mt-1", children: ["or click to browse (max ", formatFileSize(maxSize), " each)"] })] })] }), error ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "text-destructive", children: error })) : null, value.length > 0 ? (_jsx("
|
|
96
|
-
var _a;
|
|
81
|
+
: "border-muted-foreground/25 hover:border-muted-foreground/50", disabled && "cursor-not-allowed opacity-50"), children: [_jsx(UploadIcon, { className: cn(isDragOver ? "text-interactive" : "text-muted-foreground"), size: 28 }), _jsxs("div", { className: "text-center", children: [_jsx(Typography, { as: "p", variant: "label", children: isDragOver ? "Drop files here" : "Drag & drop files here" }), _jsxs(Typography, { as: "p", variant: "caption", className: "mt-1", children: ["or click to browse (max ", formatFileSize(maxSize), " each)"] })] })] }), error ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "text-destructive", children: error })) : null, value.length > 0 ? (_jsx("ul", { className: "space-y-2", children: value.map((file, index) => {
|
|
97
82
|
const progress = uploadProgress === null || uploadProgress === void 0 ? void 0 : uploadProgress.get(file.name);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
return (_jsx(FileUploadStatusRow, { file: {
|
|
84
|
+
name: file.name,
|
|
85
|
+
type: file.type,
|
|
86
|
+
size: file.size,
|
|
87
|
+
status: progress === null || progress === void 0 ? void 0 : progress.status,
|
|
88
|
+
progress: progress === null || progress === void 0 ? void 0 : progress.progress,
|
|
89
|
+
error: progress === null || progress === void 0 ? void 0 : progress.error,
|
|
90
|
+
}, disabled: disabled, onRemove: () => removeFile(index) }, `${file.name}-${file.size}-${index}`));
|
|
106
91
|
}) })) : null] }));
|
|
107
92
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export { CountBadge } from "./count-badge";
|
|
|
40
40
|
export type { CountBadgeProps } from "./count-badge";
|
|
41
41
|
export { StatusDot } from "./status-dot";
|
|
42
42
|
export type { StatusDotProps, StatusDotSize, StatusDotVariant, } from "./status-dot";
|
|
43
|
+
export { TableEmptyState } from "./table-empty-state";
|
|
43
44
|
export { EmptyState } from "./empty-state";
|
|
44
45
|
export type { EmptyStateProps } from "./empty-state";
|
|
45
46
|
export { EditableTable } from "./editable-table";
|
|
@@ -171,3 +172,4 @@ export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelect
|
|
|
171
172
|
export type { ChecklistAttachmentsProps, ChecklistPanelProps, ChecklistSectionProps, ChecklistSelectionProps, ChecklistQuestionProps, ChecklistQuestionOption } from "./checklist";
|
|
172
173
|
export { TableAddRow, type TableAddRowProps } from "./table-add-row";
|
|
173
174
|
export { PopoverList, type PopoverListProps } from "./popover";
|
|
175
|
+
export { StatusBanner, type StatusBannerProps } from "./status-banner";
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ export { ThemeSwitcher } from "./theme-switcher";
|
|
|
20
20
|
export { DateInput } from "./date-input";
|
|
21
21
|
export { CountBadge } from "./count-badge";
|
|
22
22
|
export { StatusDot } from "./status-dot";
|
|
23
|
+
export { TableEmptyState } from "./table-empty-state";
|
|
23
24
|
export { EmptyState } from "./empty-state";
|
|
24
25
|
export { EditableTable } from "./editable-table";
|
|
25
26
|
export { EditButton } from "./edit-button";
|
|
@@ -90,3 +91,4 @@ export { parseDateTime, formatDateTime } from "./lib/date-utils";
|
|
|
90
91
|
export { ChecklistAttachments, ChecklistPanel, ChecklistSection, ChecklistSelection, ChecklistQuestion } from "./checklist";
|
|
91
92
|
export { TableAddRow } from "./table-add-row";
|
|
92
93
|
export { PopoverList } from "./popover";
|
|
94
|
+
export { StatusBanner } from "./status-banner";
|
package/dist/label-value-grid.js
CHANGED
|
@@ -66,7 +66,9 @@ function LabelValueGrid({ items, columns = 4, density = "default", className, it
|
|
|
66
66
|
}, [columns, density, resolvedMinColumnWidth]);
|
|
67
67
|
return (_jsx("div", { ref: containerRef, className: cn("grid items-start", density === "compact"
|
|
68
68
|
? "auto-rows-auto gap-x-4 gap-y-3"
|
|
69
|
-
:
|
|
69
|
+
: resolvedColumns === 1
|
|
70
|
+
? "auto-rows-auto gap-x-6 gap-y-6"
|
|
71
|
+
: "auto-rows-fr gap-x-6 gap-y-6", className), style: {
|
|
70
72
|
...style,
|
|
71
73
|
gridTemplateColumns: `repeat(${resolvedColumns}, minmax(0, 1fr))`,
|
|
72
74
|
}, ...props, children: resolvedItems.map((item, index) => {
|
package/dist/popover.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "mo
|
|
|
13
13
|
children: React.ReactNode;
|
|
14
14
|
/** Visible popover heading. Also labels the popover for assistive technology. */
|
|
15
15
|
title?: React.ReactNode;
|
|
16
|
-
|
|
16
|
+
/** List surfaces scroll at the overlay edge and stay within the available viewport height. */
|
|
17
|
+
surface?: "panel" | "menu" | "message" | "list";
|
|
17
18
|
/** Controls whether Tab stays within the popover or closes it and continues in the page Tab order. */
|
|
18
19
|
tabBehavior?: PopoverTabBehavior;
|
|
19
20
|
contentClassName?: string;
|
package/dist/popover.js
CHANGED
|
@@ -79,7 +79,7 @@ function Popover({ defaultOpen, modal, onOpenChange, open, trigger, virtualAncho
|
|
|
79
79
|
contentOnCloseAutoFocus === null || contentOnCloseAutoFocus === void 0 ? void 0 : contentOnCloseAutoFocus(event);
|
|
80
80
|
handleCloseAutoFocus(event);
|
|
81
81
|
}, [contentOnCloseAutoFocus, handleCloseAutoFocus]);
|
|
82
|
-
return (_jsxs(PopoverRoot, { modal: modal, onOpenChange: setOpen, open: currentOpen, children: [virtualAnchorRef ? (_jsx(PopoverAnchor, { virtualRef: virtualAnchorRef })) : null, triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsxs(PopoverContent, { ref: contentRef, align: align, sideOffset: sideOffset, "aria-labelledby": titleId !== null && titleId !== void 0 ? titleId : contentProps["aria-labelledby"], className: cn(surface === "menu" && "overflow-hidden p-1", surface === "message" && "overflow-hidden p-0", contentClassName), style: style, ...contentProps, onCloseAutoFocus: handleContentCloseAutoFocus, onKeyDown: handleContentKeyDown, onKeyDownCapture: handleContentKeyDownCapture, children: [title ? (_jsx(PopoverTitle, { id: titleId, className: cn(surface === "menu"
|
|
82
|
+
return (_jsxs(PopoverRoot, { modal: modal, onOpenChange: setOpen, open: currentOpen, children: [virtualAnchorRef ? (_jsx(PopoverAnchor, { virtualRef: virtualAnchorRef })) : null, triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsxs(PopoverContent, { ref: contentRef, align: align, sideOffset: sideOffset, "aria-labelledby": titleId !== null && titleId !== void 0 ? titleId : contentProps["aria-labelledby"], className: cn(surface === "menu" && "overflow-hidden p-1", surface === "message" && "overflow-hidden p-0", surface === "list" && "max-h-[min(18rem,var(--radix-popover-content-available-height))] overflow-y-auto overscroll-contain", contentClassName), style: style, ...contentProps, onCloseAutoFocus: handleContentCloseAutoFocus, onKeyDown: handleContentKeyDown, onKeyDownCapture: handleContentKeyDownCapture, children: [title ? (_jsx(PopoverTitle, { id: titleId, className: cn(surface === "menu"
|
|
83
83
|
? "px-2 py-1.5"
|
|
84
84
|
: "-mx-4 -mt-4 mb-4 border-b px-4 py-3"), children: title })) : null, children] })] }));
|
|
85
85
|
}
|
|
@@ -89,5 +89,5 @@ function PopoverTitle({ className, children, ...props }) {
|
|
|
89
89
|
export { Popover, PopoverTitle };
|
|
90
90
|
/** Read-only information rows for a popover, without implying menu actions. */
|
|
91
91
|
export function PopoverList({ items }) {
|
|
92
|
-
return (_jsx("ul", { "data-slot": "popover-list", className: "
|
|
92
|
+
return (_jsx("ul", { "data-slot": "popover-list", className: "space-y-3 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
93
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface StatusBannerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
label: string;
|
|
4
|
+
detail?: React.ReactNode;
|
|
5
|
+
icon?: React.ComponentType<{
|
|
6
|
+
className?: string;
|
|
7
|
+
"aria-hidden"?: React.AriaAttributes["aria-hidden"];
|
|
8
|
+
}>;
|
|
9
|
+
trailingIcon?: React.ComponentType<{
|
|
10
|
+
className?: string;
|
|
11
|
+
"aria-hidden"?: React.AriaAttributes["aria-hidden"];
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/** Compact primary-colour status strip for an ongoing activity. */
|
|
15
|
+
export declare function StatusBanner({ label, detail, icon: Icon, trailingIcon: TrailingIcon, className, ...props }: StatusBannerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./lib/utils";
|
|
3
|
+
/** Compact primary-colour status strip for an ongoing activity. */
|
|
4
|
+
export function StatusBanner({ label, detail, icon: Icon, trailingIcon: TrailingIcon, className, ...props }) {
|
|
5
|
+
return (_jsxs("div", { ...props, className: cn("flex w-full items-center justify-center gap-2 rounded-none border border-border bg-primary px-4 py-1.5 text-sm text-primary-foreground shadow-sm", className), children: [Icon ? _jsx(Icon, { className: "h-4 w-4 shrink-0", "aria-hidden": true }) : null, _jsx("span", { className: "font-medium", children: label }), detail, TrailingIcon ? (_jsx(TrailingIcon, { className: "h-4 w-4 shrink-0 opacity-50", "aria-hidden": true })) : null] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type EmptyStateProps } from "./empty-state";
|
|
2
|
+
/** Shared empty-table presentation, with an icon and explanatory copy by default. */
|
|
3
|
+
export declare function TableEmptyState({ message, description, icon, ...props }: Partial<EmptyStateProps>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Inbox } from "lucide-react";
|
|
3
|
+
import { EmptyState } from "./empty-state";
|
|
4
|
+
/** Shared empty-table presentation, with an icon and explanatory copy by default. */
|
|
5
|
+
export function TableEmptyState({ message = "No data available", description = "There are no rows to display in this table yet.", icon = Inbox, ...props }) {
|
|
6
|
+
return _jsx(EmptyState, { ...props, message: message, description: description, icon: icon });
|
|
7
|
+
}
|
package/dist/table.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CSS } from "@dnd-kit/utilities";
|
|
|
6
6
|
import { ArrowUpDown, ChevronRight } from "lucide-react";
|
|
7
7
|
import { ArrowDownIcon } from "./animated-icons/arrow-down";
|
|
8
8
|
import { ArrowUpIcon } from "./animated-icons/arrow-up";
|
|
9
|
-
import {
|
|
9
|
+
import { TableEmptyState } from "./table-empty-state";
|
|
10
10
|
import { Pagination } from "./pagination";
|
|
11
11
|
import { Skeleton } from "./skeleton";
|
|
12
12
|
import { TableColumnFilterControl, } from "./table-column-filter";
|
|
@@ -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 [
|
|
174
|
+
const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-[4px] align-middle text-card-foreground [&:has([role=checkbox])]:pr-0 [&:not([data-slot=table-empty-cell])_[data-slot=button]]:px-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
|
|
175
175
|
style,
|
|
176
176
|
width,
|
|
177
177
|
minWidth,
|
|
@@ -399,7 +399,7 @@ function getColumnPairResizeRange(column, nextColumn, columnWidths) {
|
|
|
399
399
|
maxWidth: Math.round(currentWidth + maxDelta),
|
|
400
400
|
};
|
|
401
401
|
}
|
|
402
|
-
function Table({ accessibleLabel, columns, rows, getRowKey, emptyState = (_jsx(
|
|
402
|
+
function Table({ accessibleLabel, columns, rows, getRowKey, emptyState = (_jsx(TableEmptyState, {})), toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, minHeight, sorting, pagination, loading = false, loadingLabel = "Loading table data", loadingRows = 5, visibleColumnKeys, resizableColumns = false, columnResizeMode = "pair", columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, tableLayout, centerEmptyState = false, emptyStateMinHeight = DEFAULT_CENTERED_EMPTY_STATE_MIN_HEIGHT, striped = false, rowIndicator, rowExpansion, reorderableColumns = false, onColumnOrderChange, onScroll, style, ...props }) {
|
|
403
403
|
var _a;
|
|
404
404
|
const visibleColumns = getVisibleColumns(columns, visibleColumnKeys);
|
|
405
405
|
const hasRowIndicatorColumn = Boolean(rowIndicator);
|
|
@@ -746,7 +746,7 @@ function Table({ accessibleLabel, columns, rows, getRowKey, emptyState = (_jsx(E
|
|
|
746
746
|
})] }), isExpanded ? (_jsx(TableRow, { id: expandedContentId, "data-slot": "table-expanded-row", className: "bg-[hsl(var(--table-row-striped-background))] hover:bg-[hsl(var(--table-row-striped-background))]", children: _jsx(TableCell, { colSpan: Math.max(visibleColumns.length +
|
|
747
747
|
(hasRowIndicatorColumn ? 1 : 0) +
|
|
748
748
|
(hasExpansionColumn ? 1 : 0), 1), className: "h-auto whitespace-normal p-4", children: rowExpansion === null || rowExpansion === void 0 ? void 0 : rowExpansion.renderExpandedContent(row, rowIndex) }) })) : null] }, rowKey));
|
|
749
|
-
})) : (_jsx(TableRow, { className: "cursor-default hover:bg-card", children: _jsx(TableCell, { colSpan: Math.max(visibleColumns.length +
|
|
749
|
+
})) : (_jsx(TableRow, { className: "cursor-default hover:bg-card", children: _jsx(TableCell, { "data-slot": "table-empty-cell", colSpan: Math.max(visibleColumns.length +
|
|
750
750
|
(hasRowIndicatorColumn ? 1 : 0) +
|
|
751
751
|
(hasExpansionColumn ? 1 : 0), 1), className: "p-6", children: resolvedEmptyState }) })) })] }), _jsx("div", { role: "status", className: "sr-only", children: loading ? loadingLabel : null })] }));
|
|
752
752
|
const reorderableContainer = reorderableColumns ? (_jsx(DndContext, { sensors: columnReorderSensors, collisionDetection: closestCenter, onDragEnd: handleColumnDragEnd, children: _jsx(SortableContext, { items: reorderableColumnIds, strategy: horizontalListSortingStrategy, children: container }) })) : (container);
|