@neasg/design-system 0.4.8 → 0.4.9
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 +4 -4
- package/dist/alert.d.ts +21 -2
- package/dist/alert.js +10 -3
- package/dist/avatar.d.ts +5 -2
- package/dist/avatar.js +76 -8
- package/dist/back-button.d.ts +2 -0
- package/dist/back-button.js +1 -1
- package/dist/badge.d.ts +3 -2
- package/dist/badge.js +9 -3
- package/dist/breadcrumb.js +2 -1
- package/dist/button.d.ts +1 -1
- package/dist/button.js +12 -9
- package/dist/calendar.js +10 -9
- package/dist/card.d.ts +29 -1
- package/dist/card.js +61 -7
- package/dist/checkbox.d.ts +2 -0
- package/dist/checkbox.js +2 -2
- package/dist/close-button.d.ts +6 -0
- package/dist/close-button.js +8 -0
- package/dist/collapsible.d.ts +7 -1
- package/dist/collapsible.js +2 -2
- package/dist/command-search.d.ts +16 -1
- package/dist/command-search.js +42 -16
- package/dist/command.js +3 -3
- package/dist/copy-button.d.ts +3 -1
- package/dist/copy-button.js +4 -2
- package/dist/count-badge.d.ts +3 -1
- package/dist/count-badge.js +10 -5
- package/dist/date-input.d.ts +18 -3
- package/dist/date-input.js +94 -21
- package/dist/dialog-primitive.js +4 -3
- package/dist/draggable-tabs.d.ts +7 -0
- package/dist/draggable-tabs.js +6 -5
- package/dist/drawer.js +2 -2
- package/dist/dropdown-button.d.ts +18 -0
- package/dist/dropdown-button.js +31 -0
- package/dist/edit-button.d.ts +10 -0
- package/dist/edit-button.js +7 -0
- package/dist/editable-table.d.ts +3 -1
- package/dist/editable-table.js +25 -12
- package/dist/field.d.ts +2 -2
- package/dist/field.js +4 -3
- package/dist/file-upload-status-toast.d.ts +28 -0
- package/dist/file-upload-status-toast.js +136 -0
- package/dist/file-upload.js +8 -7
- package/dist/filter-popover.d.ts +37 -0
- package/dist/filter-popover.js +73 -0
- package/dist/filter-toggle.js +2 -2
- package/dist/index.d.ts +45 -14
- package/dist/index.js +19 -4
- package/dist/input-control.d.ts +3 -2
- package/dist/input-control.js +3 -2
- package/dist/label-value-grid.d.ts +4 -2
- package/dist/label-value-grid.js +7 -6
- package/dist/label-value.js +3 -1
- package/dist/layout-right-panel-edge.d.ts +6 -0
- package/dist/layout-right-panel-edge.js +6 -0
- package/dist/layout.d.ts +77 -5
- package/dist/layout.js +439 -35
- package/dist/lib/date-utils.d.ts +10 -2
- package/dist/lib/date-utils.js +91 -11
- package/dist/lib/utils.js +8 -1
- package/dist/link.js +2 -2
- package/dist/message-item.d.ts +62 -0
- package/dist/message-item.js +148 -0
- package/dist/multi-select.js +31 -5
- package/dist/notification.d.ts +82 -0
- package/dist/notification.js +121 -0
- package/dist/otp-input.js +1 -1
- package/dist/page-header.js +1 -1
- package/dist/page-section.d.ts +2 -1
- package/dist/page-section.js +4 -3
- package/dist/pagination.js +2 -2
- package/dist/popover-menu.d.ts +23 -1
- package/dist/popover-menu.js +75 -5
- package/dist/popover-primitive.js +1 -1
- package/dist/progress.d.ts +5 -2
- package/dist/progress.js +6 -3
- package/dist/radio-group.d.ts +21 -0
- package/dist/radio-group.js +41 -0
- package/dist/rich-text-editor.d.ts +56 -0
- package/dist/rich-text-editor.js +394 -0
- package/dist/routing-timeline.d.ts +22 -0
- package/dist/routing-timeline.js +58 -0
- package/dist/scroll-hint.d.ts +8 -0
- package/dist/scroll-hint.js +7 -0
- package/dist/search-input-shell.js +1 -1
- package/dist/search-input.js +2 -2
- package/dist/section-nav.js +4 -4
- package/dist/select-primitive.js +3 -3
- package/dist/select.js +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/styles.css +235 -57
- package/dist/table-column-visibility.d.ts +21 -0
- package/dist/table-column-visibility.js +74 -0
- package/dist/table-toolbar.js +1 -1
- package/dist/table.d.ts +12 -1
- package/dist/table.js +67 -33
- package/dist/tabs.d.ts +14 -3
- package/dist/tabs.js +60 -6
- package/dist/tailwind-preset.js +46 -0
- package/dist/task-mode-shell.d.ts +24 -0
- package/dist/task-mode-shell.js +110 -0
- package/dist/textarea.js +2 -1
- package/dist/theme-switcher.d.ts +28 -0
- package/dist/theme-switcher.js +27 -0
- package/dist/theme.d.ts +167 -0
- package/dist/theme.js +112 -0
- package/dist/toaster.js +5 -4
- package/dist/tooltip.js +1 -1
- package/dist/typography.d.ts +1 -1
- package/dist/typography.js +5 -5
- package/dist/workspace-header.d.ts +13 -0
- package/dist/workspace-header.js +11 -0
- package/package.json +15 -1
package/dist/draggable-tabs.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core";
|
|
4
4
|
import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, } from "@dnd-kit/sortable";
|
|
5
5
|
import { CSS } from "@dnd-kit/utilities";
|
|
6
6
|
import { XIcon } from "./animated-icons/x";
|
|
7
|
+
import { Badge } from "./badge";
|
|
7
8
|
import { cn } from "./lib/utils";
|
|
8
9
|
import { Tooltip } from "./tooltip";
|
|
9
10
|
function TabButton({ item, dragHandleProps, }) {
|
|
@@ -14,13 +15,13 @@ function TabButton({ item, dragHandleProps, }) {
|
|
|
14
15
|
: item.active
|
|
15
16
|
? "text-foreground"
|
|
16
17
|
: "text-muted-foreground hover:text-foreground";
|
|
17
|
-
const content = (_jsxs("div", { className: cn("group flex items-center gap-
|
|
18
|
+
const content = (_jsxs("div", { className: cn("group flex items-center gap-1.5 border-b-2 px-2.5 transition-colors select-none", item.disabled
|
|
18
19
|
? "cursor-not-allowed opacity-50"
|
|
19
20
|
: isDraggable
|
|
20
21
|
? "cursor-grab active:cursor-grabbing"
|
|
21
22
|
: "cursor-pointer", item.active
|
|
22
|
-
? (_a = item.activeClassName) !== null && _a !== void 0 ? _a : "border-
|
|
23
|
-
: (_b = item.inactiveClassName) !== null && _b !== void 0 ? _b : "border-transparent hover:bg-
|
|
23
|
+
? (_a = item.activeClassName) !== null && _a !== void 0 ? _a : "border-[hsl(var(--tab-indicator))] bg-transparent hover:bg-transparent"
|
|
24
|
+
: (_b = item.inactiveClassName) !== null && _b !== void 0 ? _b : "border-transparent hover:bg-[hsl(var(--tab-trigger-hover-background))]", item.className), ...dragHandleProps, children: [_jsxs("button", { type: "button", onClick: item.disabled ? undefined : item.onSelect, disabled: item.disabled, className: cn("flex cursor-pointer items-center gap-1.5 py-2 pr-1 text-sm font-medium whitespace-nowrap disabled:cursor-not-allowed", labelClassName), children: [item.badge ? (_jsx(Badge, { variant: item.badge.variant, bordered: item.badge.bordered, size: "icon", children: item.badge.label })) : null, item.leading, item.label, item.trailing] }), item.onClose ? (_jsx("button", { type: "button", onClick: (event) => {
|
|
24
25
|
var _a;
|
|
25
26
|
event.stopPropagation();
|
|
26
27
|
(_a = item.onClose) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
@@ -56,6 +57,6 @@ function DraggableTabs({ staticTabs = [], tabs, onReorder, className, ...props }
|
|
|
56
57
|
onReorder(oldIndex, newIndex);
|
|
57
58
|
}
|
|
58
59
|
};
|
|
59
|
-
return (_jsxs("div", { className: cn("flex items-end gap-1 border-b overflow-x-auto overflow-y-hidden bg-
|
|
60
|
+
return (_jsxs("div", { "data-slot": "draggable-tabs", className: cn("flex items-end gap-1 border-b overflow-x-auto overflow-y-hidden bg-transparent", "border-[hsl(var(--layout-surface-border))]", className), ...props, children: [staticTabs.map((tab) => (_jsx(TabButton, { item: tab }, tab.id))), _jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: tabs.map((tab) => tab.id), strategy: horizontalListSortingStrategy, children: tabs.map((tab) => (_jsx(SortableTab, { item: tab }, tab.id))) }) })] }));
|
|
60
61
|
}
|
|
61
62
|
export { DraggableTabs };
|
package/dist/drawer.js
CHANGED
|
@@ -34,7 +34,7 @@ function renderDrawerAction({ action, close, defaultVariant, }) {
|
|
|
34
34
|
}
|
|
35
35
|
}, children: label }));
|
|
36
36
|
}
|
|
37
|
-
const drawerSurfaceVariants = cva("fixed z-50 flex flex-col gap-
|
|
37
|
+
const drawerSurfaceVariants = cva("fixed z-50 flex flex-col gap-3 overflow-y-auto bg-background p-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", {
|
|
38
38
|
variants: {
|
|
39
39
|
side: {
|
|
40
40
|
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-lg",
|
|
@@ -46,7 +46,7 @@ const drawerSurfaceVariants = cva("fixed z-50 flex flex-col gap-4 overflow-y-aut
|
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
function DrawerSurface({ side, className, children, hideCloseButton, ...props }) {
|
|
49
|
-
return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { className: cn(drawerSurfaceVariants({ side }), className), ...props, children: [children, !hideCloseButton ? (_jsxs(DialogPrimitive.Close, { className: "absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-
|
|
49
|
+
return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { className: cn(drawerSurfaceVariants({ side }), className), ...props, children: [children, !hideCloseButton ? (_jsxs(DialogPrimitive.Close, { className: "absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-control text-muted-foreground ring-offset-background transition-colors hover:bg-accent hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none", children: [_jsx(XIcon, { size: 16 }), _jsx("span", { className: "sr-only", children: "Close" })] })) : null] })] }));
|
|
50
50
|
}
|
|
51
51
|
function Drawer({ open, defaultOpen, onOpenChange, modal, trigger, title, description, children, footer, primaryAction, secondaryAction, side = "right", contentClassName, bodyClassName, hideCloseButton, }) {
|
|
52
52
|
const [isOpen, setIsOpen] = useControllableOpen({
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ButtonProps } from "./button";
|
|
3
|
+
import { type PopoverProps } from "./popover";
|
|
4
|
+
type DropdownButtonPopoverProps = Pick<PopoverProps, "align" | "alignOffset" | "avoidCollisions" | "collisionPadding" | "defaultOpen" | "hideWhenDetached" | "modal" | "onOpenChange" | "open" | "side" | "sideOffset" | "sticky">;
|
|
5
|
+
export interface DropdownButtonProps extends Omit<ButtonProps, "children">, DropdownButtonPopoverProps {
|
|
6
|
+
label: React.ReactNode;
|
|
7
|
+
leadingIcon?: React.ReactNode;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
contentClassName?: string;
|
|
10
|
+
dropdownAriaLabel?: string;
|
|
11
|
+
menuWidth?: "trigger" | "content";
|
|
12
|
+
/**
|
|
13
|
+
* Renders separate primary and menu triggers. Set `false` to keep one popover trigger even when `onClick` is provided.
|
|
14
|
+
*/
|
|
15
|
+
split?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare function DropdownButton({ label, leadingIcon, children, className, contentClassName, dropdownAriaLabel, menuWidth, align, alignOffset, avoidCollisions, collisionPadding, defaultOpen, disabled, hideWhenDetached, loading, loadingIndicator, loadingText, modal, onClick, onOpenChange, open, side, sideOffset, split: splitProp, sticky, type, variant, size, ...buttonProps }: DropdownButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export { DropdownButton };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronDownIcon } from "./animated-icons";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { Popover } from "./popover";
|
|
6
|
+
import { PopoverAnchor, PopoverContent, PopoverRoot, PopoverTrigger, } from "./popover-primitive";
|
|
7
|
+
import { cn } from "./lib/utils";
|
|
8
|
+
const splitButtonDividerClassNames = {
|
|
9
|
+
default: "before:bg-primary-foreground/30",
|
|
10
|
+
destructive: "before:bg-destructive-foreground/30",
|
|
11
|
+
outline: "before:bg-[hsl(var(--button-outline-border))]",
|
|
12
|
+
secondary: "before:bg-secondary-foreground/20",
|
|
13
|
+
ghost: "before:bg-border",
|
|
14
|
+
link: "before:bg-primary/25",
|
|
15
|
+
};
|
|
16
|
+
function DropdownButton({ label, leadingIcon, children, className, contentClassName, dropdownAriaLabel, menuWidth = "trigger", align = "end", alignOffset, avoidCollisions, collisionPadding, defaultOpen, disabled, hideWhenDetached, loading, loadingIndicator, loadingText, modal, onClick, onOpenChange, open, side, sideOffset = 4, split: splitProp, sticky, type = "button", variant, size, ...buttonProps }) {
|
|
17
|
+
const menuContentClassName = cn("shadow-overlay-strong border-border/80 bg-popover ring-1 ring-border/50", menuWidth === "trigger" &&
|
|
18
|
+
"w-[var(--radix-popover-trigger-width)] min-w-56", contentClassName);
|
|
19
|
+
const labelContent = (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [leadingIcon ? (_jsx("span", { className: "inline-flex shrink-0 items-center", children: leadingIcon })) : null, _jsx("span", { className: "truncate", children: label })] }));
|
|
20
|
+
const menuButtonLabel = dropdownAriaLabel !== null && dropdownAriaLabel !== void 0 ? dropdownAriaLabel : (typeof label === "string" ? `${label} menu` : "Open menu");
|
|
21
|
+
const menuButtonSizeClassName = size === "sm" || size === "compact" || size === "compactIcon"
|
|
22
|
+
? "w-8"
|
|
23
|
+
: "w-control";
|
|
24
|
+
const resolvedVariant = variant !== null && variant !== void 0 ? variant : "default";
|
|
25
|
+
const split = splitProp !== null && splitProp !== void 0 ? splitProp : Boolean(onClick);
|
|
26
|
+
if (split) {
|
|
27
|
+
return (_jsxs(PopoverRoot, { defaultOpen: defaultOpen, modal: modal, onOpenChange: onOpenChange, open: open, children: [_jsx(PopoverAnchor, { asChild: true, children: _jsxs("div", { className: cn("inline-flex items-stretch", className), children: [_jsx(Button, { type: type, variant: variant, size: size, className: cn("min-w-0 flex-1 justify-start rounded-r-none", variant === "outline" && "border-r-0"), disabled: disabled, loading: loading, loadingIndicator: loadingIndicator, loadingText: loadingText, onClick: onClick, ...buttonProps, children: labelContent }), _jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: variant, size: size, "aria-label": menuButtonLabel, className: cn("relative shrink-0 rounded-l-none px-0 before:absolute before:inset-y-0 before:left-0 before:w-px before:content-['']", menuButtonSizeClassName, splitButtonDividerClassNames[resolvedVariant], variant === "outline" && "border-l-0"), disabled: disabled || loading, children: _jsx(ChevronDownIcon, { size: 14 }) }) })] }) }), _jsx(PopoverContent, { align: align, alignOffset: alignOffset, avoidCollisions: avoidCollisions, collisionPadding: collisionPadding, hideWhenDetached: hideWhenDetached, side: side, sideOffset: sideOffset, sticky: sticky, className: cn("overflow-hidden p-1", menuContentClassName), children: children })] }));
|
|
28
|
+
}
|
|
29
|
+
return (_jsx(Popover, { surface: "menu", align: align, alignOffset: alignOffset, avoidCollisions: avoidCollisions, collisionPadding: collisionPadding, defaultOpen: defaultOpen, hideWhenDetached: hideWhenDetached, modal: modal, onOpenChange: onOpenChange, open: open, side: side, sideOffset: sideOffset, sticky: sticky, contentClassName: menuContentClassName, trigger: _jsxs(Button, { type: type, variant: variant, size: size, className: cn("justify-between", className), disabled: disabled, loading: loading, loadingIndicator: loadingIndicator, loadingText: loadingText, onClick: onClick, ...buttonProps, children: [labelContent, _jsx(ChevronDownIcon, { className: "ml-auto", size: 14 })] }), children: children }));
|
|
30
|
+
}
|
|
31
|
+
export { DropdownButton };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ButtonProps } from "./button";
|
|
3
|
+
export interface EditButtonProps extends Omit<ButtonProps, "children" | "size" | "variant"> {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
targetLabel?: string;
|
|
6
|
+
size?: Extract<ButtonProps["size"], "sm" | "compact" | "default">;
|
|
7
|
+
variant?: Extract<ButtonProps["variant"], "outline" | "ghost" | "secondary">;
|
|
8
|
+
}
|
|
9
|
+
declare function EditButton({ label, targetLabel, size, variant, "aria-label": ariaLabel, ...props }: EditButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export { EditButton };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PencilIcon } from "./animated-icons";
|
|
3
|
+
import { Button } from "./button";
|
|
4
|
+
function EditButton({ label = "Edit", targetLabel, size = "compact", variant = "outline", "aria-label": ariaLabel, ...props }) {
|
|
5
|
+
return (_jsxs(Button, { variant: variant, size: size, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (targetLabel ? `Edit ${targetLabel}` : undefined), ...props, children: [_jsx(PencilIcon, { size: 14 }), label] }));
|
|
6
|
+
}
|
|
7
|
+
export { EditButton };
|
package/dist/editable-table.d.ts
CHANGED
|
@@ -23,10 +23,12 @@ export interface EditableTableProps {
|
|
|
23
23
|
className?: string;
|
|
24
24
|
emptyPlaceholder?: string;
|
|
25
25
|
checkable?: boolean;
|
|
26
|
+
checkableHeader?: React.ReactNode;
|
|
26
27
|
activeField?: string;
|
|
28
|
+
addRowLabel?: React.ReactNode;
|
|
27
29
|
renderRowActions?: (context: EditableTableRowActionContext) => React.ReactNode;
|
|
28
30
|
}
|
|
29
|
-
declare function EditableTable({ value, onChange, columns, isEditing, className, emptyPlaceholder, checkable, activeField, renderRowActions, }: EditableTableProps): import("react/jsx-runtime").JSX.Element;
|
|
31
|
+
declare function EditableTable({ value, onChange, columns, isEditing, className, emptyPlaceholder, checkable, checkableHeader, activeField, addRowLabel, renderRowActions, }: EditableTableProps): import("react/jsx-runtime").JSX.Element;
|
|
30
32
|
declare namespace EditableTable {
|
|
31
33
|
var displayName: string;
|
|
32
34
|
}
|
package/dist/editable-table.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Button } from "./button";
|
|
|
4
4
|
import { PlusIcon } from "./animated-icons/plus";
|
|
5
5
|
import { TrashIcon } from "./animated-icons/trash";
|
|
6
6
|
import { Checkbox } from "./checkbox";
|
|
7
|
+
import { EmptyState } from "./empty-state";
|
|
7
8
|
import { InputControl } from "./input-control";
|
|
8
9
|
import { TableRoot as Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "./table";
|
|
9
10
|
import { cn } from "./lib/utils";
|
|
@@ -32,7 +33,7 @@ function setNestedValue(obj, path, value) {
|
|
|
32
33
|
current[parts[parts.length - 1]] = value;
|
|
33
34
|
return result;
|
|
34
35
|
}
|
|
35
|
-
function EditableTable({ value, onChange, columns, isEditing = false, className, emptyPlaceholder = "
|
|
36
|
+
function EditableTable({ value, onChange, columns, isEditing = false, className, emptyPlaceholder = "Enter text...", checkable = false, checkableHeader, activeField = "active", addRowLabel = "Add Row", renderRowActions, }) {
|
|
36
37
|
const [editingCell, setEditingCell] = React.useState(null);
|
|
37
38
|
const [editValue, setEditValue] = React.useState("");
|
|
38
39
|
const inputRef = React.useRef(null);
|
|
@@ -48,8 +49,12 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
|
|
|
48
49
|
if (!isEditing) {
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
52
|
+
if ((editingCell === null || editingCell === void 0 ? void 0 : editingCell.rowIndex) === rowIndex &&
|
|
53
|
+
(editingCell === null || editingCell === void 0 ? void 0 : editingCell.colPath) === colPath) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
51
56
|
const cellValue = getNestedValue(value[rowIndex], colPath);
|
|
52
|
-
setEditValue(cellValue
|
|
57
|
+
setEditValue(isEmptyCellValue(cellValue) ? "" : String(cellValue));
|
|
53
58
|
setEditingCell({ rowIndex, colPath });
|
|
54
59
|
};
|
|
55
60
|
const commitEdit = () => {
|
|
@@ -102,27 +107,35 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
|
|
|
102
107
|
};
|
|
103
108
|
onChange(nextRows);
|
|
104
109
|
}, [activeField, onChange, value]);
|
|
110
|
+
const isEmptyCellValue = (cellValue) => cellValue === null ||
|
|
111
|
+
cellValue === undefined ||
|
|
112
|
+
cellValue === "" ||
|
|
113
|
+
(typeof cellValue === "string" && cellValue.trim() === "-");
|
|
105
114
|
const formatCellValue = (cellValue) => {
|
|
106
|
-
if (cellValue
|
|
107
|
-
return
|
|
115
|
+
if (isEmptyCellValue(cellValue)) {
|
|
116
|
+
return "";
|
|
108
117
|
}
|
|
109
118
|
return String(cellValue);
|
|
110
119
|
};
|
|
111
120
|
const hasActionsColumn = isEditing || Boolean(renderRowActions);
|
|
112
|
-
|
|
121
|
+
const checkableColumnWidth = checkableHeader ? "9rem" : "48px";
|
|
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 +
|
|
113
123
|
(checkable ? 1 : 0) +
|
|
114
|
-
(hasActionsColumn ? 1 : 0), className: "
|
|
124
|
+
(hasActionsColumn ? 1 : 0), className: "p-0", children: _jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-[var(--space-6)]" }) }) })) : (value.map((row, rowIndex) => (_jsxs(TableRow, { className: rowIndex % 2 === 0
|
|
125
|
+
? "bg-[hsl(var(--table-row-striped-background))]"
|
|
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
|
|
115
127
|
? (event) => handleToggleRow(rowIndex, event.currentTarget.checked)
|
|
116
128
|
: undefined }) }) })) : null, columns.map((column, columnIndex) => {
|
|
117
129
|
const cellValue = getNestedValue(row, column.path);
|
|
130
|
+
const isEmptyCell = isEmptyCellValue(cellValue);
|
|
118
131
|
const isEditingCell = (editingCell === null || editingCell === void 0 ? void 0 : editingCell.rowIndex) === rowIndex &&
|
|
119
132
|
(editingCell === null || editingCell === void 0 ? void 0 : editingCell.colPath) === column.path;
|
|
120
|
-
return (_jsx(TableCell, { className: cn("whitespace-normal", isEditing && "cursor-pointer hover:bg-muted/50"), width: column.width, minWidth: getColumnMinWidth(column), maxWidth: getColumnMaxWidth(column), onClick: () => handleCellClick(rowIndex, column.path), children: isEditingCell ? (_jsx(InputControl, { ref: inputRef, value: editValue, onChange: (event) => setEditValue(event.target.value), onBlur: commitEdit, onKeyDown: handleKeyDown, className: "h-
|
|
121
|
-
cellValue === undefined ||
|
|
122
|
-
cellValue === ""
|
|
133
|
+
return (_jsx(TableCell, { className: cn("whitespace-normal", isEditing && "cursor-pointer hover:bg-muted/50"), width: column.width, minWidth: getColumnMinWidth(column), maxWidth: getColumnMaxWidth(column), onClick: () => handleCellClick(rowIndex, column.path), children: isEditingCell ? (_jsx("div", { className: "flex min-h-8 items-center", children: _jsx(InputControl, { ref: inputRef, value: editValue, placeholder: emptyPlaceholder, onChange: (event) => setEditValue(event.target.value), onClick: (event) => event.stopPropagation(), onBlur: commitEdit, onKeyDown: handleKeyDown, className: "h-8 w-full px-2 py-1 text-sm" }) })) : (_jsx("span", { className: cn("flex min-h-8 items-center whitespace-normal break-words", isEmptyCell
|
|
123
134
|
? "text-muted-foreground"
|
|
124
|
-
: undefined), style: { maxWidth: getColumnMaxWidth(column) }, children:
|
|
125
|
-
|
|
135
|
+
: undefined), style: { maxWidth: getColumnMaxWidth(column) }, children: isEmptyCell && isEditing
|
|
136
|
+
? emptyPlaceholder
|
|
137
|
+
: formatCellValue(cellValue) })) }, `cell-${rowIndex}-${columnIndex}`));
|
|
138
|
+
}), hasActionsColumn ? (_jsx(TableCell, { width: renderRowActions ? undefined : "72px", minWidth: renderRowActions ? "120px" : "72px", children: _jsxs("div", { className: "flex min-h-8 items-center justify-end gap-1", children: [renderRowActions
|
|
126
139
|
? renderRowActions({
|
|
127
140
|
row,
|
|
128
141
|
rowIndex,
|
|
@@ -132,7 +145,7 @@ function EditableTable({ value, onChange, columns, isEditing = false, className,
|
|
|
132
145
|
insertRow: (nextRow, index = rowIndex + 1) => handleInsertRow(nextRow, index),
|
|
133
146
|
removeRow: () => handleDeleteRow(rowIndex),
|
|
134
147
|
})
|
|
135
|
-
: null, isEditing ? (_jsx(Button, { type: "button", variant: "ghost", size: "
|
|
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 ? (_jsxs(Button, { type: "button", variant: "outline", onClick: handleAddRow, className: "w-full justify-center", children: [_jsx(PlusIcon, { size: 16 }), addRowLabel] })) : null] }));
|
|
136
149
|
}
|
|
137
150
|
EditableTable.displayName = "EditableTable";
|
|
138
151
|
export { EditableTable };
|
package/dist/field.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ interface FieldLabelProps extends React.ComponentProps<"label"> {
|
|
|
4
4
|
required?: boolean;
|
|
5
5
|
}
|
|
6
6
|
declare function FieldLabel({ className, required, children, ...props }: FieldLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
declare function FieldDescription({ className, ...props }: React.ComponentProps<"p">): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
declare function FieldError({ className, ...props }: React.ComponentProps<"p">): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
declare function FieldDescription({ className, ...props }: Omit<React.ComponentProps<"p">, "color">): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function FieldError({ className, ...props }: Omit<React.ComponentProps<"p">, "color">): import("react/jsx-runtime").JSX.Element | null;
|
|
9
9
|
export { Field, FieldDescription, FieldError, FieldLabel };
|
package/dist/field.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "./lib/utils";
|
|
3
|
+
import { Typography } from "./typography";
|
|
3
4
|
function Field({ className, ...props }) {
|
|
4
5
|
return (_jsx("div", { "data-slot": "field", className: cn("group/field flex w-full flex-col gap-2", className), ...props }));
|
|
5
6
|
}
|
|
6
7
|
function FieldLabel({ className, required, children, ...props }) {
|
|
7
|
-
return (
|
|
8
|
+
return (_jsx(Typography, { asChild: true, variant: "label", className: cn("group-data-[disabled=true]/field:cursor-not-allowed group-data-[disabled=true]/field:opacity-50 group-data-[invalid=true]/field:text-destructive", className), children: _jsxs("label", { "data-slot": "field-label", ...props, children: [children, required ? (_jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-destructive", children: "*" })) : null] }) }));
|
|
8
9
|
}
|
|
9
10
|
function FieldDescription({ className, ...props }) {
|
|
10
|
-
return (_jsx("p",
|
|
11
|
+
return (_jsx(Typography, { as: "p", variant: "bodySm", color: "muted", "data-slot": "field-description", className: className, ...props }));
|
|
11
12
|
}
|
|
12
13
|
function FieldError({ className, ...props }) {
|
|
13
14
|
if (!props.children) {
|
|
14
15
|
return null;
|
|
15
16
|
}
|
|
16
|
-
return (_jsx("p",
|
|
17
|
+
return (_jsx(Typography, { as: "p", variant: "bodySm", role: "alert", "data-slot": "field-error", className: cn("text-destructive", className), ...props }));
|
|
17
18
|
}
|
|
18
19
|
export { Field, FieldDescription, FieldError, FieldLabel };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { FileUploadProgress, FileUploadResult, FileUploadStatus } from "./use-file-upload";
|
|
3
|
+
export interface FileUploadStatusToastFile {
|
|
4
|
+
name: string;
|
|
5
|
+
size?: number | string;
|
|
6
|
+
type?: string;
|
|
7
|
+
progress?: number;
|
|
8
|
+
status?: FileUploadStatus;
|
|
9
|
+
error?: string;
|
|
10
|
+
result?: FileUploadResult;
|
|
11
|
+
}
|
|
12
|
+
export interface FileUploadStatusToastProps {
|
|
13
|
+
files: FileUploadStatusToastFile[];
|
|
14
|
+
uploadProgress?: Map<string, FileUploadProgress> | FileUploadProgress[];
|
|
15
|
+
title?: React.ReactNode;
|
|
16
|
+
description?: React.ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
onCancel?: (file: FileUploadStatusToastFile) => void;
|
|
19
|
+
onRemove?: (file: FileUploadStatusToastFile) => void;
|
|
20
|
+
onDismiss?: () => void;
|
|
21
|
+
}
|
|
22
|
+
export interface ShowFileUploadStatusToastOptions extends FileUploadStatusToastProps {
|
|
23
|
+
id?: string | number;
|
|
24
|
+
duration?: number;
|
|
25
|
+
dismissible?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, onDismiss, }: FileUploadStatusToastProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function showFileUploadStatusToast({ id, duration, dismissible, onDismiss, ...props }: ShowFileUploadStatusToastOptions): string | number;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AlertCircle, CheckCircle2, Paperclip, Trash2, X, } from "lucide-react";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { cn } from "./lib/utils";
|
|
6
|
+
import { Progress } from "./progress";
|
|
7
|
+
import { toast } from "./toaster";
|
|
8
|
+
import { Typography } from "./typography";
|
|
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
|
+
function getProgress(uploadProgress, fileName) {
|
|
23
|
+
if (!uploadProgress)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (Array.isArray(uploadProgress)) {
|
|
26
|
+
return uploadProgress.find((progress) => progress.name === fileName);
|
|
27
|
+
}
|
|
28
|
+
return uploadProgress.get(fileName);
|
|
29
|
+
}
|
|
30
|
+
function normalizeFiles({ files, uploadProgress, }) {
|
|
31
|
+
return files.map((file) => {
|
|
32
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
33
|
+
const progress = getProgress(uploadProgress, file.name);
|
|
34
|
+
const result = (_a = progress === null || progress === void 0 ? void 0 : progress.result) !== null && _a !== void 0 ? _a : file.result;
|
|
35
|
+
return {
|
|
36
|
+
...file,
|
|
37
|
+
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
|
+
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
|
+
error: (_f = progress === null || progress === void 0 ? void 0 : progress.error) !== null && _f !== void 0 ? _f : file.error,
|
|
40
|
+
result,
|
|
41
|
+
size: (_g = result === null || result === void 0 ? void 0 : result.size) !== null && _g !== void 0 ? _g : file.size,
|
|
42
|
+
type: (_h = result === null || result === void 0 ? void 0 : result.type) !== null && _h !== void 0 ? _h : file.type,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function pluralize(count, singular, plural = `${singular}s`) {
|
|
47
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
48
|
+
}
|
|
49
|
+
function getDefaultTitle(files) {
|
|
50
|
+
const failed = files.filter((file) => file.status === "error").length;
|
|
51
|
+
const uploading = files.filter((file) => file.status === "pending" || file.status === "uploading").length;
|
|
52
|
+
const complete = files.filter((file) => file.status === "complete").length;
|
|
53
|
+
if (failed > 0)
|
|
54
|
+
return `${pluralize(failed, "file")} failed to upload`;
|
|
55
|
+
if (uploading > 0)
|
|
56
|
+
return `Uploading ${pluralize(files.length, "file")}`;
|
|
57
|
+
if (complete === files.length && files.length > 0) {
|
|
58
|
+
return `Uploaded ${pluralize(files.length, "file")}`;
|
|
59
|
+
}
|
|
60
|
+
return "File upload status";
|
|
61
|
+
}
|
|
62
|
+
function getDefaultDescription(files) {
|
|
63
|
+
const complete = files.filter((file) => file.status === "complete").length;
|
|
64
|
+
const failed = files.filter((file) => file.status === "error").length;
|
|
65
|
+
const uploading = files.filter((file) => file.status === "uploading").length;
|
|
66
|
+
const pending = files.filter((file) => file.status === "pending").length;
|
|
67
|
+
const parts = [
|
|
68
|
+
complete > 0 ? `${complete} complete` : null,
|
|
69
|
+
uploading > 0 ? `${uploading} uploading` : null,
|
|
70
|
+
pending > 0 ? `${pending} pending` : null,
|
|
71
|
+
failed > 0 ? `${failed} failed` : null,
|
|
72
|
+
].filter(Boolean);
|
|
73
|
+
return parts.length > 0 ? parts.join(" / ") : undefined;
|
|
74
|
+
}
|
|
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
|
+
export function FileUploadStatusToast({ files, uploadProgress, title, description, className, onCancel, onRemove, onDismiss, }) {
|
|
106
|
+
const normalizedFiles = normalizeFiles({ files, uploadProgress });
|
|
107
|
+
const resolvedTitle = title !== null && title !== void 0 ? title : getDefaultTitle(normalizedFiles);
|
|
108
|
+
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) => {
|
|
110
|
+
var _a;
|
|
111
|
+
return (_jsx(FileUploadStatusRow, { file: file, onCancel: onCancel, onRemove: onRemove }, `${file.name}-${(_a = file.size) !== null && _a !== void 0 ? _a : "unknown"}-${index}`));
|
|
112
|
+
}) })] }));
|
|
113
|
+
}
|
|
114
|
+
export function showFileUploadStatusToast({ id = DEFAULT_TOAST_ID, duration = Number.POSITIVE_INFINITY, dismissible = true, onDismiss, ...props }) {
|
|
115
|
+
if (props.files.length === 0) {
|
|
116
|
+
toast.dismiss(id);
|
|
117
|
+
onDismiss === null || onDismiss === void 0 ? void 0 : onDismiss();
|
|
118
|
+
return id;
|
|
119
|
+
}
|
|
120
|
+
return toast.custom((toastId) => (_jsx(FileUploadStatusToast, { ...props, onDismiss: dismissible
|
|
121
|
+
? () => {
|
|
122
|
+
toast.dismiss(toastId);
|
|
123
|
+
onDismiss === null || onDismiss === void 0 ? void 0 : onDismiss();
|
|
124
|
+
}
|
|
125
|
+
: undefined })), {
|
|
126
|
+
id,
|
|
127
|
+
duration,
|
|
128
|
+
dismissible,
|
|
129
|
+
onDismiss,
|
|
130
|
+
classNames: {
|
|
131
|
+
toast: "!block !rounded-none !border-0 !bg-transparent !p-0 !pr-0 !shadow-none",
|
|
132
|
+
content: "!block !min-w-0 !flex-none !gap-0",
|
|
133
|
+
title: "!block !font-normal !leading-normal !text-foreground",
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
package/dist/file-upload.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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, ImageIcon } from "lucide-react";
|
|
5
|
+
import { File as FileIcon, ImageIcon } from "lucide-react";
|
|
6
6
|
import { CheckIcon } from "./animated-icons/check";
|
|
7
7
|
import { FileTextIcon } from "./animated-icons/file-text";
|
|
8
8
|
import { UploadIcon } from "./animated-icons/upload";
|
|
@@ -10,6 +10,7 @@ import { XIcon } from "./animated-icons/x";
|
|
|
10
10
|
import { Button } from "./button";
|
|
11
11
|
import { cn } from "./lib/utils";
|
|
12
12
|
import { Progress } from "./progress";
|
|
13
|
+
import { Typography } from "./typography";
|
|
13
14
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
14
15
|
function formatFileSize(bytes) {
|
|
15
16
|
if (bytes === 0)
|
|
@@ -21,12 +22,12 @@ function formatFileSize(bytes) {
|
|
|
21
22
|
}
|
|
22
23
|
function getFileIcon(file) {
|
|
23
24
|
if (file.type.startsWith("image/")) {
|
|
24
|
-
return _jsx(ImageIcon, { className: "h-4 w-4 text-
|
|
25
|
+
return _jsx(ImageIcon, { className: "h-4 w-4 text-muted-foreground" });
|
|
25
26
|
}
|
|
26
27
|
if (file.type === "application/pdf") {
|
|
27
|
-
return _jsx(FileTextIcon, { className: "text-
|
|
28
|
+
return _jsx(FileTextIcon, { className: "text-muted-foreground", size: 16 });
|
|
28
29
|
}
|
|
29
|
-
return _jsx(
|
|
30
|
+
return _jsx(FileIcon, { className: "h-4 w-4 text-muted-foreground" });
|
|
30
31
|
}
|
|
31
32
|
export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles = 10, onFilesChange, value = [], disabled = false, className, uploadProgress, }) {
|
|
32
33
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
@@ -89,15 +90,15 @@ export function FileUpload({ accept, maxSize = DEFAULT_MAX_FILE_SIZE, maxFiles =
|
|
|
89
90
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
90
91
|
}, []);
|
|
91
92
|
const fileInput = (_jsx("input", { ref: inputRef, type: "file", accept: accept, multiple: maxFiles > 1, onChange: handleInputChange, disabled: disabled, className: "hidden" }));
|
|
92
|
-
return (_jsxs("div", { className: cn("space-y-
|
|
93
|
+
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
|
|
93
94
|
? "border-primary bg-primary/5"
|
|
94
|
-
: "border-muted-foreground/25 hover:border-muted-foreground/50", disabled && "cursor-not-allowed opacity-50"), children: [_jsx(UploadIcon, { className: cn(isDragOver ? "text-primary" : "text-muted-foreground"), size:
|
|
95
|
+
: "border-muted-foreground/25 hover:border-muted-foreground/50", disabled && "cursor-not-allowed opacity-50"), children: [_jsx(UploadIcon, { className: cn(isDragOver ? "text-primary" : "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("div", { className: "space-y-2", children: value.map((file, index) => {
|
|
95
96
|
var _a;
|
|
96
97
|
const progress = uploadProgress === null || uploadProgress === void 0 ? void 0 : uploadProgress.get(file.name);
|
|
97
98
|
const isUploading = (progress === null || progress === void 0 ? void 0 : progress.status) === "uploading";
|
|
98
99
|
const isComplete = (progress === null || progress === void 0 ? void 0 : progress.status) === "complete";
|
|
99
100
|
const isError = (progress === null || progress === void 0 ? void 0 : progress.status) === "error";
|
|
100
|
-
return (_jsxs("div", { className: "rounded-
|
|
101
|
+
return (_jsxs("div", { className: "rounded-control border bg-muted/30 px-2.5 py-1.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [getFileIcon(file), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Typography, { as: "p", variant: "label", className: "truncate", children: file.name }), _jsxs(Typography, { as: "p", variant: "caption", children: [formatFileSize(file.size), isUploading && ` • ${progress === null || progress === void 0 ? void 0 : progress.progress}%`, isComplete && " • Complete", isError && ` • ${(progress === null || progress === void 0 ? void 0 : progress.error) || "Error"}`] })] }), isComplete ? (_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center text-[hsl(var(--status-success-foreground))]", children: _jsx(CheckIcon, { size: 16 }) })) : (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-6 w-6 shrink-0", onClick: (e) => {
|
|
101
102
|
e.stopPropagation();
|
|
102
103
|
removeFile(index);
|
|
103
104
|
}, disabled: disabled || isUploading, "aria-label": `Remove ${file.name}`, children: _jsx(XIcon, { size: 16 }) }))] }), (isUploading ||
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type PopoverProps } from "./popover";
|
|
3
|
+
export interface FilterPopoverOption {
|
|
4
|
+
id: string;
|
|
5
|
+
label: React.ReactNode;
|
|
6
|
+
selected?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
searchValue?: string;
|
|
9
|
+
ariaLabel?: string;
|
|
10
|
+
onSelect?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface FilterPopoverGroup {
|
|
13
|
+
id: string;
|
|
14
|
+
label: React.ReactNode;
|
|
15
|
+
options: FilterPopoverOption[];
|
|
16
|
+
selectedCount?: number;
|
|
17
|
+
selectionLabel?: string;
|
|
18
|
+
searchable?: boolean;
|
|
19
|
+
searchPlaceholder?: string;
|
|
20
|
+
emptyLabel?: React.ReactNode;
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface FilterPopoverProps extends Pick<PopoverProps, "align" | "defaultOpen" | "modal" | "onOpenChange" | "open" | "side"> {
|
|
24
|
+
groups: FilterPopoverGroup[];
|
|
25
|
+
selectedCount?: number;
|
|
26
|
+
trigger?: React.ReactElement;
|
|
27
|
+
triggerLabel?: React.ReactNode;
|
|
28
|
+
groupListLabel?: React.ReactNode;
|
|
29
|
+
resetLabel?: React.ReactNode;
|
|
30
|
+
emptyLabel?: React.ReactNode;
|
|
31
|
+
onReset?: () => void;
|
|
32
|
+
contentClassName?: string;
|
|
33
|
+
maxHeight?: string;
|
|
34
|
+
}
|
|
35
|
+
declare function getFilterSelectionLabel(count: number): string;
|
|
36
|
+
declare function FilterPopover({ align, contentClassName, defaultOpen, emptyLabel, groupListLabel, groups, maxHeight, modal, onOpenChange, onReset, open, resetLabel, selectedCount, side, trigger, triggerLabel, }: FilterPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export { FilterPopover, getFilterSelectionLabel };
|