@rebasepro/ui 0.6.1 → 0.8.0
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/components/VirtualTable/fields/VirtualTableDateField.d.ts +13 -0
- package/dist/components/VirtualTable/fields/VirtualTableInput.d.ts +10 -0
- package/dist/components/VirtualTable/fields/VirtualTableNumberInput.d.ts +9 -0
- package/dist/components/VirtualTable/fields/VirtualTableSelect.d.ts +17 -0
- package/dist/components/VirtualTable/fields/VirtualTableSwitch.d.ts +8 -0
- package/dist/components/VirtualTable/index.d.ts +7 -0
- package/dist/components/VirtualTable/selection/SelectionContext.d.ts +11 -0
- package/dist/components/VirtualTable/selection/SelectionStore.d.ts +14 -0
- package/dist/icons/index.d.ts +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.es.js +2322 -23
- package/dist/index.es.js.map +1 -1
- package/dist/styles.d.ts +1 -1
- package/dist/views/CardView.d.ts +32 -0
- package/dist/views/CollectionView/CollectionCardView.d.ts +30 -0
- package/dist/views/CollectionView/CollectionKanbanView.d.ts +31 -0
- package/dist/views/CollectionView/CollectionListView.d.ts +30 -0
- package/dist/views/CollectionView/CollectionTableView.d.ts +37 -0
- package/dist/views/CollectionView/CollectionView.d.ts +121 -0
- package/dist/views/CollectionView/CollectionViewToolbar.d.ts +31 -0
- package/dist/views/CollectionView/CollectionViewTypes.d.ts +106 -0
- package/dist/views/CollectionView/DefaultCellRenderer.d.ts +9 -0
- package/dist/views/CollectionView/index.d.ts +24 -0
- package/dist/views/CollectionView/utils.d.ts +5 -0
- package/dist/views/Kanban/Board.d.ts +3 -0
- package/dist/views/Kanban/BoardColumn.d.ts +20 -0
- package/dist/views/Kanban/BoardColumnTitle.d.ts +8 -0
- package/dist/views/Kanban/BoardSortableList.d.ts +14 -0
- package/dist/views/Kanban/board_types.d.ts +61 -0
- package/dist/views/Kanban/index.d.ts +5 -0
- package/dist/views/KanbanView.d.ts +24 -0
- package/dist/views/ListView.d.ts +40 -0
- package/dist/views/TableView.d.ts +6 -0
- package/dist/views/index.d.ts +5 -0
- package/package.json +1 -1
- package/src/components/Sheet.tsx +8 -4
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +32 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +82 -0
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +71 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +107 -0
- package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +28 -0
- package/src/components/VirtualTable/index.tsx +7 -0
- package/src/components/VirtualTable/selection/SelectionContext.tsx +36 -0
- package/src/components/VirtualTable/selection/SelectionStore.ts +54 -0
- package/src/icons/index.ts +3 -0
- package/src/index.ts +43 -3
- package/src/styles.ts +1 -1
- package/src/views/CardView.tsx +281 -0
- package/src/views/CollectionView/CollectionCardView.tsx +270 -0
- package/src/views/CollectionView/CollectionKanbanView.tsx +251 -0
- package/src/views/CollectionView/CollectionListView.tsx +245 -0
- package/src/views/CollectionView/CollectionTableView.tsx +229 -0
- package/src/views/CollectionView/CollectionView.tsx +388 -0
- package/src/views/CollectionView/CollectionViewToolbar.tsx +229 -0
- package/src/views/CollectionView/CollectionViewTypes.ts +137 -0
- package/src/views/CollectionView/DefaultCellRenderer.tsx +404 -0
- package/src/views/CollectionView/index.ts +44 -0
- package/src/views/CollectionView/utils.ts +14 -0
- package/src/views/Kanban/Board.tsx +421 -0
- package/src/views/Kanban/BoardColumn.tsx +135 -0
- package/src/views/Kanban/BoardColumnTitle.tsx +47 -0
- package/src/views/Kanban/BoardSortableList.tsx +170 -0
- package/src/views/Kanban/board_types.ts +70 -0
- package/src/views/Kanban/index.tsx +5 -0
- package/src/views/KanbanView.tsx +32 -0
- package/src/views/ListView.tsx +281 -0
- package/src/views/TableView.tsx +7 -0
- package/src/views/index.ts +5 -0
package/dist/index.es.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React$1 from "react";
|
|
3
|
-
import React, { Children, createContext, createRef, forwardRef, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import React, { Children, createContext, createRef, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { clsx } from "clsx";
|
|
5
5
|
import { twMerge } from "tailwind-merge";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
8
|
-
import { AlertCircleIcon, AlertCircleIcon as AlertCircleIcon$1, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownToLineIcon, ArrowLeftIcon, ArrowLeftIcon as ArrowLeftIcon$1, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightToLineIcon, ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon, CalendarIcon as CalendarIcon$1, CheckCircleIcon, CheckIcon, CheckIcon as CheckIcon$1, CheckSquareIcon, ChevronDownIcon, ChevronDownIcon as ChevronDownIcon$1, ChevronLeftIcon, ChevronLeftIcon as ChevronLeftIcon$1, ChevronRightIcon, ChevronRightIcon as ChevronRightIcon$1, ChevronUpIcon, ChevronUpIcon as ChevronUpIcon$1, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon, FilterIcon as FilterIcon$1, FilterXIcon, FlagIcon, FolderIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, LinkIcon, ListIcon, ListOrderedIcon, LoaderIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MinusCircleIcon, MinusIcon, MinusIcon as MinusIcon$1, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftIcon, PauseIcon, PencilIcon, PhoneIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon, RefreshCwIcon as RefreshCwIcon$1, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon, SearchIcon as SearchIcon$1, SendIcon, SettingsIcon, ShieldAlertIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TypeIcon, UnderlineIcon, UndoIcon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, VideoIcon, VoteIcon, Wand2Icon, XCircleIcon, XIcon, XIcon as XIcon$1, icons as lucideIcons } from "lucide-react";
|
|
8
|
+
import { AlertCircleIcon, AlertCircleIcon as AlertCircleIcon$1, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownToLineIcon, ArrowLeftIcon, ArrowLeftIcon as ArrowLeftIcon$1, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightToLineIcon, ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon, CalendarIcon as CalendarIcon$1, CheckCircleIcon, CheckIcon, CheckIcon as CheckIcon$1, CheckSquareIcon, ChevronDownIcon, ChevronDownIcon as ChevronDownIcon$1, ChevronLeftIcon, ChevronLeftIcon as ChevronLeftIcon$1, ChevronRightIcon, ChevronRightIcon as ChevronRightIcon$1, ChevronUpIcon, ChevronUpIcon as ChevronUpIcon$1, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon, FilterIcon as FilterIcon$1, FilterXIcon, FlagIcon, FolderIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon, ImageIcon as ImageIcon$1, ImageOffIcon, InfoIcon, ItalicIcon, Kanban, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGrid, LayoutGridIcon, LayoutList, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, LoaderIcon, LockIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MinusCircleIcon, MinusIcon, MinusIcon as MinusIcon$1, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftIcon, PauseIcon, PencilIcon, PhoneIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon, RefreshCwIcon as RefreshCwIcon$1, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon, SearchIcon as SearchIcon$1, SendIcon, Settings2, SettingsIcon, ShieldAlertIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StrikethroughIcon, SunIcon, SunMoonIcon, Table2, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TypeIcon, UnderlineIcon, UndoIcon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, VideoIcon, VoteIcon, Wand2Icon, XCircleIcon, XIcon, XIcon as XIcon$1, icons as lucideIcons } from "lucide-react";
|
|
9
9
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
10
10
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
11
11
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
@@ -23,13 +23,15 @@ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
|
23
23
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
24
24
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
25
25
|
import * as ReactDOM from "react-dom";
|
|
26
|
+
import { createPortal } from "react-dom";
|
|
26
27
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
27
28
|
import { FixedSizeList } from "react-window";
|
|
28
29
|
import useMeasure from "react-use-measure";
|
|
29
|
-
import { SortableContext, arrayMove, horizontalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
|
|
30
|
-
import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
30
|
+
import { SortableContext, arrayMove, horizontalListSortingStrategy, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
31
|
+
import { DndContext, DragOverlay, PointerSensor, closestCenter, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
31
32
|
import * as Portal from "@radix-ui/react-portal";
|
|
32
33
|
import { Slot } from "@radix-ui/react-slot";
|
|
34
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
33
35
|
//#region src/util/cls.ts
|
|
34
36
|
function cls(...classes) {
|
|
35
37
|
return twMerge(clsx(classes));
|
|
@@ -169,13 +171,6 @@ function getColorSchemeForSeed(seed) {
|
|
|
169
171
|
return CHIP_COLORS[colorKeys[hash % colorKeys.length]];
|
|
170
172
|
}
|
|
171
173
|
//#endregion
|
|
172
|
-
//#region src/util/key_to_icon_component.ts
|
|
173
|
-
function keyToIconComponent(key) {
|
|
174
|
-
return (key.match(/^\d/) ? "_" : "") + key.split("_").map((word) => {
|
|
175
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
176
|
-
}).join("") + "Icon";
|
|
177
|
-
}
|
|
178
|
-
//#endregion
|
|
179
174
|
//#region src/components/common/SelectInputLabel.tsx
|
|
180
175
|
function SelectInputLabel({ children, error }) {
|
|
181
176
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -192,7 +187,7 @@ var fieldBackgroundMixin = "bg-surface-accent-200/50 dark:bg-black/30";
|
|
|
192
187
|
var fieldBackgroundInvisibleMixin = "bg-surface-accent-200/0 dark:bg-black/0";
|
|
193
188
|
var fieldBackgroundDisabledMixin = "bg-surface-accent-200/50 dark:bg-black/20";
|
|
194
189
|
var fieldBackgroundHoverMixin = "hover:bg-surface-accent-200/70 hover:dark:bg-black/50";
|
|
195
|
-
var defaultBorderMixin = "border-surface-200 dark:border-surface-700 ";
|
|
190
|
+
var defaultBorderMixin = "border-surface-200 dark:border-surface-700/60 ";
|
|
196
191
|
var paperMixin = "bg-white rounded-lg dark:bg-surface-900 border border-surface-200 dark:border-surface-700";
|
|
197
192
|
var cardMixin = "bg-white dark:bg-surface-900 rounded-lg border border-surface-200 dark:border-surface-700";
|
|
198
193
|
var cardClickableMixin = "hover:bg-primary/5 dark:hover:bg-primary/5 cursor-pointer transition-colors duration-150";
|
|
@@ -1609,7 +1604,7 @@ function ExpandablePanel({ title, children, invisible = false, expanded, onExpan
|
|
|
1609
1604
|
if (expanded !== void 0) setOpen(expanded);
|
|
1610
1605
|
}, [expanded]);
|
|
1611
1606
|
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Collapsible.Root, {
|
|
1612
|
-
className: cls(!invisible && "border-surface-200 dark:border-surface-700 border", "rounded-lg", "w-full", className),
|
|
1607
|
+
className: cls(!invisible && "border-surface-200 dark:border-surface-700/60 border", "rounded-lg", "w-full", className),
|
|
1613
1608
|
open,
|
|
1614
1609
|
onOpenChange: (updatedOpen) => {
|
|
1615
1610
|
onExpandedChange?.(updatedOpen);
|
|
@@ -1618,7 +1613,7 @@ function ExpandablePanel({ title, children, invisible = false, expanded, onExpan
|
|
|
1618
1613
|
children: [/* @__PURE__ */ jsx(Collapsible.Trigger, {
|
|
1619
1614
|
asChild: true,
|
|
1620
1615
|
children: /* @__PURE__ */ jsxs("div", {
|
|
1621
|
-
className: cls("rounded-t flex items-center justify-between w-full min-h-[52px]", "hover:bg-surface-accent-200 hover:bg-opacity-40 hover:bg-surface-accent-200/40 dark:hover:bg-surface-800 dark:hover:bg-opacity-40 dark:hover:bg-surface-800/40 active:bg-surface-accent-300/50 dark:active:bg-surface-700/50", invisible ? "border-b px-2" : "p-4", open ? "py-6" : "py-4", "transition-all duration-200", invisible && "border-surface-200 dark:border-surface-700 ", asField && "bg-surface-accent-200/50 dark:bg-black/30", titleClassName, "cursor-pointer"),
|
|
1616
|
+
className: cls("rounded-t flex items-center justify-between w-full min-h-[52px]", "hover:bg-surface-accent-200 hover:bg-opacity-40 hover:bg-surface-accent-200/40 dark:hover:bg-surface-800 dark:hover:bg-opacity-40 dark:hover:bg-surface-800/40 active:bg-surface-accent-300/50 dark:active:bg-surface-700/50", invisible ? "border-b px-2" : "p-4", open ? "py-6" : "py-4", "transition-all duration-200", invisible && "border-surface-200 dark:border-surface-700/60 ", asField && "bg-surface-accent-200/50 dark:bg-black/30", titleClassName, "cursor-pointer"),
|
|
1622
1617
|
role: "button",
|
|
1623
1618
|
tabIndex: 0,
|
|
1624
1619
|
"aria-expanded": open,
|
|
@@ -2292,7 +2287,7 @@ function ResizablePanels({ firstPanel, secondPanel, showFirstPanel = true, showS
|
|
|
2292
2287
|
children: firstPanel
|
|
2293
2288
|
}),
|
|
2294
2289
|
!stacked && showFirstPanel && showSecondPanel && /* @__PURE__ */ jsx("div", {
|
|
2295
|
-
className: cls("relative z-10 flex flex-shrink-0 items-center justify-center", "border-surface-200 dark:border-surface-700 ", isHorizontal ? "border-l w-px h-full cursor-col-resize" : "border-t h-px w-full cursor-row-resize"),
|
|
2290
|
+
className: cls("relative z-10 flex flex-shrink-0 items-center justify-center", "border-surface-200 dark:border-surface-700/60 ", isHorizontal ? "border-l w-px h-full cursor-col-resize" : "border-t h-px w-full cursor-row-resize"),
|
|
2296
2291
|
onMouseDown: handleResizeStart,
|
|
2297
2292
|
children: /* @__PURE__ */ jsx("div", {
|
|
2298
2293
|
className: cls("absolute flex items-center justify-center group", isHorizontal ? "w-4 h-full cursor-col-resize top-0" : "h-4 w-full cursor-row-resize left-0"),
|
|
@@ -2574,6 +2569,7 @@ Slider.displayName = "Slider";
|
|
|
2574
2569
|
//#region src/components/Sheet.tsx
|
|
2575
2570
|
var Sheet = ({ children, side = "right", title, modal = true, includeBackgroundOverlay = true, open, onOpenChange, onPointerDownOutside, onInteractOutside, transparent, className, style, overlayClassName, overlayZIndex = "z-50", overlayStyle, portalContainer, ...props }) => {
|
|
2576
2571
|
const [displayed, setDisplayed] = useState(false);
|
|
2572
|
+
const [contentEl, setContentEl] = useState(null);
|
|
2577
2573
|
const contextContainer = usePortalContainer();
|
|
2578
2574
|
const finalContainer = portalContainer ?? contextContainer ?? void 0;
|
|
2579
2575
|
useEffect(() => {
|
|
@@ -2605,17 +2601,19 @@ var Sheet = ({ children, side = "right", title, modal = true, includeBackgroundO
|
|
|
2605
2601
|
style: overlayStyle
|
|
2606
2602
|
}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
|
|
2607
2603
|
...props,
|
|
2608
|
-
|
|
2604
|
+
ref: setContentEl,
|
|
2605
|
+
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
2609
2606
|
onPointerDownOutside,
|
|
2610
2607
|
onInteractOutside,
|
|
2611
2608
|
className: cls("outline-none", borderClass[side], defaultBorderMixin, "transform-gpu", "will-change-transform", "text-surface-accent-900 dark:text-white", "fixed transform z-50 transition-[transform,opacity] ease-in-out", !displayed ? "duration-150" : "duration-100", "outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus:ring-0", transparent ? "" : "shadow-md bg-white dark:bg-surface-900", side === "top" || side === "bottom" ? "w-full" : "h-full", side === "left" || side === "top" ? "left-0 top-0" : "right-0 bottom-0", displayed && open ? "opacity-100" : "opacity-50", !displayed || !open ? transformValue[side] : "", className),
|
|
2612
2609
|
style,
|
|
2613
2610
|
children: [/* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
2614
|
-
autoFocus: true,
|
|
2615
|
-
tabIndex: 0,
|
|
2616
2611
|
className: "sr-only outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus:ring-0",
|
|
2617
2612
|
children: title ?? "Sheet"
|
|
2618
|
-
}),
|
|
2613
|
+
}), /* @__PURE__ */ jsx(PortalContainerProvider, {
|
|
2614
|
+
container: contentEl,
|
|
2615
|
+
children
|
|
2616
|
+
})]
|
|
2619
2617
|
})]
|
|
2620
2618
|
})
|
|
2621
2619
|
});
|
|
@@ -2936,7 +2934,7 @@ function Tabs({ value, onValueChange, className, innerClassName, variant = "stan
|
|
|
2936
2934
|
disabled: !showLeftScroll,
|
|
2937
2935
|
"aria-label": "Scroll tabs left",
|
|
2938
2936
|
onClick: () => scroll("left"),
|
|
2939
|
-
className: cls("absolute left-0 top-1/2 -translate-y-1/2 z-10 flex items-center justify-center rounded-md transition-all h-8 w-6", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400", !showLeftScroll ? "pointer-events-none opacity-0" : "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800", "bg-surface-50 dark:bg-surface-900 border shadow-sm", "border-surface-200 dark:border-surface-700 "),
|
|
2937
|
+
className: cls("absolute left-0 top-1/2 -translate-y-1/2 z-10 flex items-center justify-center rounded-md transition-all h-8 w-6", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400", !showLeftScroll ? "pointer-events-none opacity-0" : "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800", "bg-surface-50 dark:bg-surface-900 border shadow-sm", "border-surface-200 dark:border-surface-700/60 "),
|
|
2940
2938
|
children: /* @__PURE__ */ jsx(ChevronLeftIcon$1, { size: iconSize.smallest })
|
|
2941
2939
|
}),
|
|
2942
2940
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -2948,7 +2946,7 @@ function Tabs({ value, onValueChange, className, innerClassName, variant = "stan
|
|
|
2948
2946
|
msOverflowStyle: "none"
|
|
2949
2947
|
},
|
|
2950
2948
|
children: /* @__PURE__ */ jsx(TabsPrimitive.List, {
|
|
2951
|
-
className: cls(variant === "standard" && "inline-flex h-9 items-center justify-start rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400 gap-2 border", variant === "standard" && "border-surface-200 dark:border-surface-700 ", variant === "boxy" && "flex items-center h-full", variant === "pill" && "flex items-center gap-0.5", innerClassName),
|
|
2949
|
+
className: cls(variant === "standard" && "inline-flex h-9 items-center justify-start rounded-md bg-surface-50 p-1 text-surface-600 dark:bg-surface-900 dark:text-surface-400 gap-2 border", variant === "standard" && "border-surface-200 dark:border-surface-700/60 ", variant === "boxy" && "flex items-center h-full", variant === "pill" && "flex items-center gap-0.5", innerClassName),
|
|
2952
2950
|
children
|
|
2953
2951
|
})
|
|
2954
2952
|
}),
|
|
@@ -2957,7 +2955,7 @@ function Tabs({ value, onValueChange, className, innerClassName, variant = "stan
|
|
|
2957
2955
|
disabled: !showRightScroll,
|
|
2958
2956
|
"aria-label": "Scroll tabs right",
|
|
2959
2957
|
onClick: () => scroll("right"),
|
|
2960
|
-
className: cls("absolute right-0 top-1/2 -translate-y-1/2 z-10 flex items-center justify-center rounded-md transition-all h-8 w-6", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400", !showRightScroll ? "pointer-events-none opacity-0" : "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800", "bg-surface-50 dark:bg-surface-900 border shadow-sm", "border-surface-200 dark:border-surface-700 "),
|
|
2958
|
+
className: cls("absolute right-0 top-1/2 -translate-y-1/2 z-10 flex items-center justify-center rounded-md transition-all h-8 w-6", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400", !showRightScroll ? "pointer-events-none opacity-0" : "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800", "bg-surface-50 dark:bg-surface-900 border shadow-sm", "border-surface-200 dark:border-surface-700/60 "),
|
|
2961
2959
|
children: /* @__PURE__ */ jsx(ChevronRightIcon$1, { size: iconSize.smallest })
|
|
2962
2960
|
})
|
|
2963
2961
|
]
|
|
@@ -4038,6 +4036,750 @@ var SafeLinkRenderer = ({ text }) => {
|
|
|
4038
4036
|
});
|
|
4039
4037
|
};
|
|
4040
4038
|
//#endregion
|
|
4039
|
+
//#region src/components/VirtualTable/selection/SelectionStore.ts
|
|
4040
|
+
function createVirtualTableSelectionStore() {
|
|
4041
|
+
let selectedCell = void 0;
|
|
4042
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4043
|
+
function getSnapshot() {
|
|
4044
|
+
return selectedCell;
|
|
4045
|
+
}
|
|
4046
|
+
function subscribe(listener) {
|
|
4047
|
+
listeners.add(listener);
|
|
4048
|
+
return () => listeners.delete(listener);
|
|
4049
|
+
}
|
|
4050
|
+
function select(cell) {
|
|
4051
|
+
selectedCell = cell;
|
|
4052
|
+
listeners.forEach((l) => l());
|
|
4053
|
+
}
|
|
4054
|
+
return {
|
|
4055
|
+
getSnapshot,
|
|
4056
|
+
subscribe,
|
|
4057
|
+
select
|
|
4058
|
+
};
|
|
4059
|
+
}
|
|
4060
|
+
function useVirtualTableCellSelected(store, columnKey, rowId) {
|
|
4061
|
+
const selectorRef = useRef({
|
|
4062
|
+
columnKey,
|
|
4063
|
+
rowId
|
|
4064
|
+
});
|
|
4065
|
+
selectorRef.current = {
|
|
4066
|
+
columnKey,
|
|
4067
|
+
rowId
|
|
4068
|
+
};
|
|
4069
|
+
const getSnapshot = useCallback(() => {
|
|
4070
|
+
const cell = store.getSnapshot();
|
|
4071
|
+
if (!cell) return false;
|
|
4072
|
+
const s = selectorRef.current;
|
|
4073
|
+
return cell.columnKey === s.columnKey && cell.rowId === s.rowId;
|
|
4074
|
+
}, [store]);
|
|
4075
|
+
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
4076
|
+
}
|
|
4077
|
+
//#endregion
|
|
4078
|
+
//#region src/components/VirtualTable/selection/SelectionContext.tsx
|
|
4079
|
+
var SelectionContext = createContext(null);
|
|
4080
|
+
var VirtualTableSelectionProvider = ({ store, children }) => {
|
|
4081
|
+
const value = React.useMemo(() => ({
|
|
4082
|
+
selectionStore: store,
|
|
4083
|
+
select: store.select
|
|
4084
|
+
}), [store]);
|
|
4085
|
+
return /* @__PURE__ */ jsx(SelectionContext.Provider, {
|
|
4086
|
+
value,
|
|
4087
|
+
children
|
|
4088
|
+
});
|
|
4089
|
+
};
|
|
4090
|
+
var useVirtualTableSelection = () => {
|
|
4091
|
+
const context = useContext(SelectionContext);
|
|
4092
|
+
if (!context) throw new Error("useVirtualTableSelection must be used within a VirtualTableSelectionProvider");
|
|
4093
|
+
return context;
|
|
4094
|
+
};
|
|
4095
|
+
//#endregion
|
|
4096
|
+
//#region src/components/VirtualTable/fields/VirtualTableInput.tsx
|
|
4097
|
+
function VirtualTableInput(props) {
|
|
4098
|
+
const ref = React.useRef(null);
|
|
4099
|
+
const { disabled, value, multiline = false, updateValue, focused } = props;
|
|
4100
|
+
const prevValue = useRef(value);
|
|
4101
|
+
const [internalValue, setInternalValue] = useState(value);
|
|
4102
|
+
const focusedState = useRef(false);
|
|
4103
|
+
useEffect(() => {
|
|
4104
|
+
if (prevValue.current !== value && value !== internalValue) setInternalValue(value);
|
|
4105
|
+
prevValue.current = value;
|
|
4106
|
+
}, [value]);
|
|
4107
|
+
const doUpdate = React.useCallback(() => {
|
|
4108
|
+
if (!value && !internalValue) return;
|
|
4109
|
+
if (internalValue !== value && internalValue !== prevValue.current) {
|
|
4110
|
+
prevValue.current = internalValue;
|
|
4111
|
+
updateValue(internalValue);
|
|
4112
|
+
}
|
|
4113
|
+
}, [
|
|
4114
|
+
internalValue,
|
|
4115
|
+
updateValue,
|
|
4116
|
+
value
|
|
4117
|
+
]);
|
|
4118
|
+
useDebouncedCallback(internalValue, doUpdate, !focused, 400);
|
|
4119
|
+
useEffect(() => {
|
|
4120
|
+
if (ref.current && focused && !focusedState.current) {
|
|
4121
|
+
focusedState.current = true;
|
|
4122
|
+
ref.current.focus({ preventScroll: true });
|
|
4123
|
+
ref.current.selectionStart = ref.current.value.length;
|
|
4124
|
+
ref.current.selectionEnd = ref.current.value.length;
|
|
4125
|
+
} else focusedState.current = focused;
|
|
4126
|
+
}, [focused, ref]);
|
|
4127
|
+
return /* @__PURE__ */ jsx(TextareaAutosize, {
|
|
4128
|
+
className: focusedDisabled,
|
|
4129
|
+
ref,
|
|
4130
|
+
style: {
|
|
4131
|
+
padding: 0,
|
|
4132
|
+
margin: 0,
|
|
4133
|
+
width: "100%",
|
|
4134
|
+
color: "unset",
|
|
4135
|
+
fontWeight: "unset",
|
|
4136
|
+
fontSize: "unset",
|
|
4137
|
+
fontFamily: "unset",
|
|
4138
|
+
background: "unset",
|
|
4139
|
+
border: "unset",
|
|
4140
|
+
resize: "none",
|
|
4141
|
+
outline: "none"
|
|
4142
|
+
},
|
|
4143
|
+
value: internalValue ?? "",
|
|
4144
|
+
onChange: (evt) => {
|
|
4145
|
+
const newValue = evt.target.value;
|
|
4146
|
+
if (multiline || !newValue.endsWith("\n")) setInternalValue(newValue);
|
|
4147
|
+
},
|
|
4148
|
+
onFocus: () => {
|
|
4149
|
+
focusedState.current = true;
|
|
4150
|
+
},
|
|
4151
|
+
onBlur: () => {
|
|
4152
|
+
focusedState.current = false;
|
|
4153
|
+
doUpdate();
|
|
4154
|
+
if (props.onBlur) props.onBlur();
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
//#endregion
|
|
4159
|
+
//#region src/components/VirtualTable/fields/VirtualTableNumberInput.tsx
|
|
4160
|
+
function VirtualTableNumberInput(props) {
|
|
4161
|
+
const { align = "left", value, updateValue, focused } = props;
|
|
4162
|
+
const propStringValue = value && typeof value === "number" ? value.toString() : "";
|
|
4163
|
+
const [internalValue, setInternalValue] = useState(propStringValue);
|
|
4164
|
+
const prevValue = useRef(value);
|
|
4165
|
+
useEffect(() => {
|
|
4166
|
+
if (prevValue.current !== value && String(value) !== internalValue) setInternalValue(value ? value.toString() : null);
|
|
4167
|
+
prevValue.current = value;
|
|
4168
|
+
}, [value]);
|
|
4169
|
+
useDebouncedCallback(internalValue, React.useCallback(() => {
|
|
4170
|
+
if (internalValue !== propStringValue) if (internalValue !== void 0 && internalValue !== null) {
|
|
4171
|
+
const numberValue = parseFloat(internalValue);
|
|
4172
|
+
if (isNaN(numberValue)) return;
|
|
4173
|
+
updateValue(numberValue);
|
|
4174
|
+
} else updateValue(null);
|
|
4175
|
+
}, [internalValue, value]), !focused, 400);
|
|
4176
|
+
useEffect(() => {
|
|
4177
|
+
if (!focused && propStringValue !== internalValue) setInternalValue(value !== void 0 && value !== null ? value.toString() : null);
|
|
4178
|
+
}, [value, focused]);
|
|
4179
|
+
const inputRef = React.useRef(null);
|
|
4180
|
+
useEffect(() => {
|
|
4181
|
+
if (inputRef.current && focused) inputRef.current.focus({ preventScroll: true });
|
|
4182
|
+
}, [focused, inputRef]);
|
|
4183
|
+
const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
|
|
4184
|
+
return /* @__PURE__ */ jsx(TextField, {
|
|
4185
|
+
inputRef,
|
|
4186
|
+
invisible: true,
|
|
4187
|
+
size: "small",
|
|
4188
|
+
className: "w-full",
|
|
4189
|
+
inputClassName: cls("p-0 m-0 bg-transparent border-none outline-hidden font-normal leading-normal text-unset", focusedDisabled),
|
|
4190
|
+
inputStyle: { textAlign: align },
|
|
4191
|
+
value: internalValue ?? "",
|
|
4192
|
+
onChange: (evt) => {
|
|
4193
|
+
const newValue = evt.target.value.replace(",", ".");
|
|
4194
|
+
if (newValue.length === 0) setInternalValue(null);
|
|
4195
|
+
if (regexp.test(newValue) || newValue.startsWith("-")) setInternalValue(newValue);
|
|
4196
|
+
}
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
//#endregion
|
|
4200
|
+
//#region src/components/VirtualTable/fields/VirtualTableSwitch.tsx
|
|
4201
|
+
function VirtualTableSwitch(props) {
|
|
4202
|
+
const { internalValue, updateValue, focused } = props;
|
|
4203
|
+
const ref = React.useRef(null);
|
|
4204
|
+
useEffect(() => {
|
|
4205
|
+
if (ref.current && focused) ref.current.focus({ preventScroll: true });
|
|
4206
|
+
}, [focused, ref]);
|
|
4207
|
+
return /* @__PURE__ */ jsx(BooleanSwitch, {
|
|
4208
|
+
ref,
|
|
4209
|
+
size: "small",
|
|
4210
|
+
value: Boolean(internalValue),
|
|
4211
|
+
onValueChange: updateValue
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
//#endregion
|
|
4215
|
+
//#region src/components/VirtualTable/fields/VirtualTableDateField.tsx
|
|
4216
|
+
function VirtualTableDateField(props) {
|
|
4217
|
+
const { disabled, error, mode, timezone, internalValue, updateValue, locale } = props;
|
|
4218
|
+
return /* @__PURE__ */ jsx(DateTimeField, {
|
|
4219
|
+
value: internalValue ?? void 0,
|
|
4220
|
+
onChange: (dateValue) => updateValue(dateValue ?? null),
|
|
4221
|
+
invisible: true,
|
|
4222
|
+
inputClassName: cls("w-full h-full", focusedDisabled),
|
|
4223
|
+
className: cls("w-full h-full", focusedDisabled),
|
|
4224
|
+
mode,
|
|
4225
|
+
timezone,
|
|
4226
|
+
locale
|
|
4227
|
+
});
|
|
4228
|
+
}
|
|
4229
|
+
//#endregion
|
|
4230
|
+
//#region src/components/VirtualTable/fields/VirtualTableSelect.tsx
|
|
4231
|
+
function VirtualTableSelect(props) {
|
|
4232
|
+
const { options, value, disabled, small = false, focused, updateValue, multiple = false } = props;
|
|
4233
|
+
const validValue = Array.isArray(value) && multiple || !Array.isArray(value) && !multiple;
|
|
4234
|
+
const ref = useRef(null);
|
|
4235
|
+
useEffect(() => {
|
|
4236
|
+
if (ref.current && focused) ref.current?.focus({ preventScroll: true });
|
|
4237
|
+
}, [focused, ref]);
|
|
4238
|
+
const onChange = useCallback((updatedValue) => {
|
|
4239
|
+
if (multiple) {
|
|
4240
|
+
const isNumber = options.some((o) => typeof o.value === "number");
|
|
4241
|
+
const newValue = updatedValue.map((v) => {
|
|
4242
|
+
return typeof options.find((o) => String(o.value) === v)?.value === "number" ? parseFloat(v) : v;
|
|
4243
|
+
});
|
|
4244
|
+
if (isNumber) updateValue(newValue);
|
|
4245
|
+
else updateValue(newValue);
|
|
4246
|
+
} else updateValue(typeof options.find((o) => String(o.value) === updatedValue)?.value === "number" ? parseFloat(updatedValue) : updatedValue || null);
|
|
4247
|
+
}, [
|
|
4248
|
+
multiple,
|
|
4249
|
+
updateValue,
|
|
4250
|
+
options
|
|
4251
|
+
]);
|
|
4252
|
+
const renderValue = (val) => {
|
|
4253
|
+
const option = options.find((o) => o.value === val);
|
|
4254
|
+
return /* @__PURE__ */ jsx(Chip, {
|
|
4255
|
+
size: small ? "small" : "medium",
|
|
4256
|
+
children: option ? option.label : String(val)
|
|
4257
|
+
});
|
|
4258
|
+
};
|
|
4259
|
+
const handleOpenChange = useCallback((open) => {
|
|
4260
|
+
if (!open && ref.current) setTimeout(() => {
|
|
4261
|
+
ref.current?.focus({ preventScroll: true });
|
|
4262
|
+
}, 0);
|
|
4263
|
+
}, []);
|
|
4264
|
+
return multiple ? /* @__PURE__ */ jsx(MultiSelect, {
|
|
4265
|
+
inputRef: ref,
|
|
4266
|
+
className: "w-full h-full p-0 bg-transparent outline-none",
|
|
4267
|
+
position: "item-aligned",
|
|
4268
|
+
disabled,
|
|
4269
|
+
includeClear: false,
|
|
4270
|
+
useChips: false,
|
|
4271
|
+
value: validValue ? value.map((v) => String(v)) : [],
|
|
4272
|
+
onValueChange: onChange,
|
|
4273
|
+
onOpenChange: handleOpenChange,
|
|
4274
|
+
children: options.map((opt) => /* @__PURE__ */ jsx(MultiSelectItem, {
|
|
4275
|
+
value: String(opt.value),
|
|
4276
|
+
children: /* @__PURE__ */ jsx(Chip, {
|
|
4277
|
+
size: small ? "small" : "medium",
|
|
4278
|
+
children: opt.label
|
|
4279
|
+
})
|
|
4280
|
+
}, String(opt.value)))
|
|
4281
|
+
}) : /* @__PURE__ */ jsx(Select, {
|
|
4282
|
+
inputRef: ref,
|
|
4283
|
+
fullWidth: true,
|
|
4284
|
+
className: "w-full h-full p-0 bg-transparent outline-none [&_button]:ring-0 [&_button]:ring-offset-0",
|
|
4285
|
+
inputClassName: "ring-0 ring-offset-0 focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-offset-0",
|
|
4286
|
+
position: "item-aligned",
|
|
4287
|
+
disabled,
|
|
4288
|
+
padding: false,
|
|
4289
|
+
value: validValue ? String(value) : "",
|
|
4290
|
+
onValueChange: onChange,
|
|
4291
|
+
onOpenChange: handleOpenChange,
|
|
4292
|
+
renderValue,
|
|
4293
|
+
children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, {
|
|
4294
|
+
value: String(opt.value),
|
|
4295
|
+
children: /* @__PURE__ */ jsx(Chip, {
|
|
4296
|
+
size: small ? "small" : "medium",
|
|
4297
|
+
children: opt.label
|
|
4298
|
+
})
|
|
4299
|
+
}, String(opt.value)))
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
//#endregion
|
|
4303
|
+
//#region src/views/ListView.tsx
|
|
4304
|
+
function getEstimatedRowHeight(size) {
|
|
4305
|
+
switch (size) {
|
|
4306
|
+
case "xs": return 44;
|
|
4307
|
+
case "s": return 52;
|
|
4308
|
+
case "m": return 64;
|
|
4309
|
+
case "l": return 76;
|
|
4310
|
+
case "xl": return 88;
|
|
4311
|
+
default: return 64;
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
var OVERSCAN_COUNT = 8;
|
|
4315
|
+
var LOAD_MORE_THRESHOLD = 400;
|
|
4316
|
+
function getScrollParent$1(element) {
|
|
4317
|
+
let parent = element?.parentElement ?? null;
|
|
4318
|
+
while (parent) {
|
|
4319
|
+
const style = getComputedStyle(parent);
|
|
4320
|
+
if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") return parent;
|
|
4321
|
+
parent = parent.parentElement;
|
|
4322
|
+
}
|
|
4323
|
+
return document.documentElement;
|
|
4324
|
+
}
|
|
4325
|
+
function ListView({ data, dataLoading = false, noMoreToLoad = false, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled = true, onItemClick, selectedIds, highlightedIds, selectionEnabled = true, onSelectionChange, emptyComponent, size = "m", selectedEntityId, renderRow }) {
|
|
4326
|
+
const containerRef = useRef(null);
|
|
4327
|
+
const isLoadingMore = useRef(false);
|
|
4328
|
+
useEffect(() => {
|
|
4329
|
+
if (!dataLoading) isLoadingMore.current = false;
|
|
4330
|
+
}, [dataLoading]);
|
|
4331
|
+
const estimatedRowHeight = getEstimatedRowHeight(size);
|
|
4332
|
+
const [effectiveScrollTop, setEffectiveScrollTop] = useState(0);
|
|
4333
|
+
const [viewportHeight, setViewportHeight] = useState(800);
|
|
4334
|
+
const paginationStateRef = useRef({
|
|
4335
|
+
paginationEnabled,
|
|
4336
|
+
noMoreToLoad,
|
|
4337
|
+
itemCount,
|
|
4338
|
+
pageSize
|
|
4339
|
+
});
|
|
4340
|
+
useEffect(() => {
|
|
4341
|
+
paginationStateRef.current = {
|
|
4342
|
+
paginationEnabled,
|
|
4343
|
+
noMoreToLoad,
|
|
4344
|
+
itemCount,
|
|
4345
|
+
pageSize
|
|
4346
|
+
};
|
|
4347
|
+
}, [
|
|
4348
|
+
paginationEnabled,
|
|
4349
|
+
noMoreToLoad,
|
|
4350
|
+
itemCount,
|
|
4351
|
+
pageSize
|
|
4352
|
+
]);
|
|
4353
|
+
useEffect(() => {
|
|
4354
|
+
const el = containerRef.current;
|
|
4355
|
+
if (!el) return;
|
|
4356
|
+
const scrollEl = getScrollParent$1(el);
|
|
4357
|
+
if (!scrollEl) return;
|
|
4358
|
+
let rafId = null;
|
|
4359
|
+
const update = () => {
|
|
4360
|
+
rafId = null;
|
|
4361
|
+
const scrollRect = scrollEl.getBoundingClientRect();
|
|
4362
|
+
const listTopRelative = el.getBoundingClientRect().top - scrollRect.top;
|
|
4363
|
+
setEffectiveScrollTop(Math.max(0, -listTopRelative));
|
|
4364
|
+
setViewportHeight(scrollRect.height);
|
|
4365
|
+
const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
|
|
4366
|
+
if (pe && !nm && !isLoadingMore.current && scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < LOAD_MORE_THRESHOLD) {
|
|
4367
|
+
isLoadingMore.current = true;
|
|
4368
|
+
setItemCount?.((ic ?? ps) + ps);
|
|
4369
|
+
}
|
|
4370
|
+
};
|
|
4371
|
+
const onScroll = () => {
|
|
4372
|
+
if (rafId === null) rafId = requestAnimationFrame(update);
|
|
4373
|
+
};
|
|
4374
|
+
scrollEl.addEventListener("scroll", onScroll, { passive: true });
|
|
4375
|
+
const ro = new ResizeObserver(() => update());
|
|
4376
|
+
ro.observe(scrollEl);
|
|
4377
|
+
update();
|
|
4378
|
+
return () => {
|
|
4379
|
+
scrollEl.removeEventListener("scroll", onScroll);
|
|
4380
|
+
ro.disconnect();
|
|
4381
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
4382
|
+
};
|
|
4383
|
+
}, [setItemCount]);
|
|
4384
|
+
const totalHeight = data.length * estimatedRowHeight;
|
|
4385
|
+
const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
|
|
4386
|
+
const endIndex = Math.min(data.length, Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT);
|
|
4387
|
+
const visibleData = data.slice(startIndex, endIndex);
|
|
4388
|
+
const offsetY = startIndex * estimatedRowHeight;
|
|
4389
|
+
const footerHeight = dataLoading ? 48 : !dataLoading && noMoreToLoad && data.length > 0 ? 32 : 0;
|
|
4390
|
+
const isInitialLoading = dataLoading && data.length === 0;
|
|
4391
|
+
const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
|
|
4392
|
+
const getItemId = useCallback((item) => {
|
|
4393
|
+
if (item && typeof item === "object" && "id" in item) {
|
|
4394
|
+
const id = item.id;
|
|
4395
|
+
return typeof id === "number" ? id : String(id);
|
|
4396
|
+
}
|
|
4397
|
+
return String(item);
|
|
4398
|
+
}, []);
|
|
4399
|
+
const borderMixinClass = "border-surface-200 dark:border-surface-800";
|
|
4400
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4401
|
+
ref: containerRef,
|
|
4402
|
+
className: cls("w-full", selectedEntityId === void 0 && `rounded-lg overflow-hidden border ${borderMixinClass}`),
|
|
4403
|
+
children: dataLoadingError && data.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
4404
|
+
className: "flex items-center justify-center p-8",
|
|
4405
|
+
children: /* @__PURE__ */ jsxs(Typography, {
|
|
4406
|
+
className: "text-red-500",
|
|
4407
|
+
children: ["Error loading data: ", dataLoadingError.message]
|
|
4408
|
+
})
|
|
4409
|
+
}) : isInitialLoading ? /* @__PURE__ */ jsx("div", {
|
|
4410
|
+
className: "flex items-center justify-center py-12 px-8",
|
|
4411
|
+
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
4412
|
+
}) : isEmpty ? /* @__PURE__ */ jsx("div", {
|
|
4413
|
+
className: "w-full flex items-center justify-center py-12 px-8",
|
|
4414
|
+
children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
|
|
4415
|
+
variant: "label",
|
|
4416
|
+
color: "secondary",
|
|
4417
|
+
children: "No entries found"
|
|
4418
|
+
})
|
|
4419
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
4420
|
+
style: {
|
|
4421
|
+
height: totalHeight + footerHeight,
|
|
4422
|
+
position: "relative"
|
|
4423
|
+
},
|
|
4424
|
+
children: [
|
|
4425
|
+
/* @__PURE__ */ jsx("div", {
|
|
4426
|
+
style: {
|
|
4427
|
+
position: "absolute",
|
|
4428
|
+
top: offsetY,
|
|
4429
|
+
left: 0,
|
|
4430
|
+
right: 0
|
|
4431
|
+
},
|
|
4432
|
+
children: visibleData.map((item, i) => {
|
|
4433
|
+
const actualIndex = startIndex + i;
|
|
4434
|
+
const isLast = actualIndex === data.length - 1;
|
|
4435
|
+
const id = getItemId(item);
|
|
4436
|
+
const selected = selectedIds?.has(id) ?? false;
|
|
4437
|
+
const highlighted = highlightedIds?.has(id) ?? false;
|
|
4438
|
+
const handleClick = (e) => {
|
|
4439
|
+
if ((e.metaKey || e.ctrlKey) && selectionEnabled) {
|
|
4440
|
+
e.preventDefault();
|
|
4441
|
+
onSelectionChange?.(item, !selected);
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
onItemClick?.(item);
|
|
4445
|
+
};
|
|
4446
|
+
const handleSelectionChange = (val) => {
|
|
4447
|
+
onSelectionChange?.(item, val);
|
|
4448
|
+
};
|
|
4449
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: renderRow({
|
|
4450
|
+
item,
|
|
4451
|
+
index: actualIndex,
|
|
4452
|
+
style: { height: estimatedRowHeight },
|
|
4453
|
+
className: cls(!isLast && "border-b", !isLast && borderMixinClass),
|
|
4454
|
+
selected,
|
|
4455
|
+
highlighted,
|
|
4456
|
+
isLast,
|
|
4457
|
+
onClick: handleClick,
|
|
4458
|
+
onSelectionChange: handleSelectionChange
|
|
4459
|
+
}) }, id || actualIndex);
|
|
4460
|
+
})
|
|
4461
|
+
}),
|
|
4462
|
+
dataLoading && /* @__PURE__ */ jsx("div", {
|
|
4463
|
+
className: "flex items-center justify-center py-3",
|
|
4464
|
+
style: {
|
|
4465
|
+
position: "absolute",
|
|
4466
|
+
top: totalHeight,
|
|
4467
|
+
left: 0,
|
|
4468
|
+
right: 0
|
|
4469
|
+
},
|
|
4470
|
+
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
4471
|
+
}),
|
|
4472
|
+
!dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
4473
|
+
className: "flex items-center justify-center py-2 dark:bg-surface-900",
|
|
4474
|
+
style: {
|
|
4475
|
+
position: "absolute",
|
|
4476
|
+
top: totalHeight,
|
|
4477
|
+
left: 0,
|
|
4478
|
+
right: 0
|
|
4479
|
+
},
|
|
4480
|
+
children: /* @__PURE__ */ jsxs(Typography, {
|
|
4481
|
+
variant: "caption",
|
|
4482
|
+
color: "secondary",
|
|
4483
|
+
children: [
|
|
4484
|
+
"All ",
|
|
4485
|
+
data.length,
|
|
4486
|
+
" entries loaded"
|
|
4487
|
+
]
|
|
4488
|
+
})
|
|
4489
|
+
})
|
|
4490
|
+
]
|
|
4491
|
+
})
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
//#endregion
|
|
4495
|
+
//#region src/views/CardView.tsx
|
|
4496
|
+
function getGridColumnsClass(size) {
|
|
4497
|
+
switch (size) {
|
|
4498
|
+
case "xs": return "grid-cols-4 sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10";
|
|
4499
|
+
case "s": return "grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8";
|
|
4500
|
+
case "m": return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
|
|
4501
|
+
case "l": return "grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4";
|
|
4502
|
+
case "xl": return "grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3";
|
|
4503
|
+
default: return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
function getScrollParent(element) {
|
|
4507
|
+
if (!element) return null;
|
|
4508
|
+
let parent = element.parentElement;
|
|
4509
|
+
while (parent) {
|
|
4510
|
+
const overflowY = window.getComputedStyle(parent).overflowY;
|
|
4511
|
+
if (overflowY === "auto" || overflowY === "scroll") return parent;
|
|
4512
|
+
parent = parent.parentElement;
|
|
4513
|
+
}
|
|
4514
|
+
return document.documentElement;
|
|
4515
|
+
}
|
|
4516
|
+
function CardView({ data, dataLoading = false, noMoreToLoad = false, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled = true, onItemClick, selectedIds, highlightedIds, selectionEnabled = true, onSelectionChange, onScroll, initialScroll, size = "m", renderCard, emptyComponent }) {
|
|
4517
|
+
const containerRef = useRef(null);
|
|
4518
|
+
const hasRestoredScroll = useRef(false);
|
|
4519
|
+
const isLoadingMore = useRef(false);
|
|
4520
|
+
const paginationStateRef = useRef({
|
|
4521
|
+
paginationEnabled,
|
|
4522
|
+
noMoreToLoad,
|
|
4523
|
+
itemCount,
|
|
4524
|
+
pageSize
|
|
4525
|
+
});
|
|
4526
|
+
useEffect(() => {
|
|
4527
|
+
paginationStateRef.current = {
|
|
4528
|
+
paginationEnabled,
|
|
4529
|
+
noMoreToLoad,
|
|
4530
|
+
itemCount,
|
|
4531
|
+
pageSize
|
|
4532
|
+
};
|
|
4533
|
+
}, [
|
|
4534
|
+
paginationEnabled,
|
|
4535
|
+
noMoreToLoad,
|
|
4536
|
+
itemCount,
|
|
4537
|
+
pageSize
|
|
4538
|
+
]);
|
|
4539
|
+
useEffect(() => {
|
|
4540
|
+
if (!dataLoading) isLoadingMore.current = false;
|
|
4541
|
+
}, [dataLoading]);
|
|
4542
|
+
useEffect(() => {
|
|
4543
|
+
const el = containerRef.current;
|
|
4544
|
+
if (!el) return;
|
|
4545
|
+
const scrollEl = getScrollParent(el);
|
|
4546
|
+
if (!scrollEl) return;
|
|
4547
|
+
let rafId = null;
|
|
4548
|
+
const update = () => {
|
|
4549
|
+
rafId = null;
|
|
4550
|
+
const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
|
|
4551
|
+
if (pe && !nm && !isLoadingMore.current && scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < 400) {
|
|
4552
|
+
isLoadingMore.current = true;
|
|
4553
|
+
setItemCount?.((ic ?? ps) + ps);
|
|
4554
|
+
}
|
|
4555
|
+
};
|
|
4556
|
+
const onScrollEvent = () => {
|
|
4557
|
+
if (rafId === null) rafId = requestAnimationFrame(update);
|
|
4558
|
+
};
|
|
4559
|
+
scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
4560
|
+
const ro = new ResizeObserver(() => update());
|
|
4561
|
+
ro.observe(scrollEl);
|
|
4562
|
+
update();
|
|
4563
|
+
return () => {
|
|
4564
|
+
scrollEl.removeEventListener("scroll", onScrollEvent);
|
|
4565
|
+
ro.disconnect();
|
|
4566
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
4567
|
+
};
|
|
4568
|
+
}, [setItemCount]);
|
|
4569
|
+
useEffect(() => {
|
|
4570
|
+
if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
|
|
4571
|
+
const scrollEl = getScrollParent(containerRef.current);
|
|
4572
|
+
if (!scrollEl) return;
|
|
4573
|
+
let attempts = 0;
|
|
4574
|
+
const maxAttempts = 5;
|
|
4575
|
+
const tryRestore = () => {
|
|
4576
|
+
if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
|
|
4577
|
+
scrollEl.scrollTop = initialScroll;
|
|
4578
|
+
hasRestoredScroll.current = true;
|
|
4579
|
+
} else {
|
|
4580
|
+
attempts++;
|
|
4581
|
+
requestAnimationFrame(tryRestore);
|
|
4582
|
+
}
|
|
4583
|
+
};
|
|
4584
|
+
requestAnimationFrame(tryRestore);
|
|
4585
|
+
}, [initialScroll, data.length]);
|
|
4586
|
+
const lastScrollOffset = useRef(0);
|
|
4587
|
+
useEffect(() => {
|
|
4588
|
+
const el = containerRef.current;
|
|
4589
|
+
if (!el || !onScroll) return;
|
|
4590
|
+
const scrollEl = getScrollParent(el);
|
|
4591
|
+
if (!scrollEl) return;
|
|
4592
|
+
const handleScroll = () => {
|
|
4593
|
+
const currentOffset = scrollEl.scrollTop;
|
|
4594
|
+
const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
|
|
4595
|
+
lastScrollOffset.current = currentOffset;
|
|
4596
|
+
onScroll({
|
|
4597
|
+
scrollDirection: direction,
|
|
4598
|
+
scrollOffset: currentOffset,
|
|
4599
|
+
scrollUpdateWasRequested: false
|
|
4600
|
+
});
|
|
4601
|
+
};
|
|
4602
|
+
scrollEl.addEventListener("scroll", handleScroll, { passive: true });
|
|
4603
|
+
return () => scrollEl.removeEventListener("scroll", handleScroll);
|
|
4604
|
+
}, [onScroll]);
|
|
4605
|
+
const getItemId = useCallback((item) => {
|
|
4606
|
+
if (item && typeof item === "object" && "id" in item) {
|
|
4607
|
+
const id = item.id;
|
|
4608
|
+
return typeof id === "number" ? id : String(id);
|
|
4609
|
+
}
|
|
4610
|
+
return String(item);
|
|
4611
|
+
}, []);
|
|
4612
|
+
const gridColumnsClass = getGridColumnsClass(size);
|
|
4613
|
+
const isInitialLoading = dataLoading && data.length === 0 && !dataLoadingError;
|
|
4614
|
+
const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
|
|
4615
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4616
|
+
ref: containerRef,
|
|
4617
|
+
className: "w-full p-4",
|
|
4618
|
+
children: dataLoadingError && data.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
4619
|
+
className: "h-full flex items-center justify-center p-8",
|
|
4620
|
+
children: /* @__PURE__ */ jsxs(Typography, {
|
|
4621
|
+
className: "text-red-500",
|
|
4622
|
+
children: ["Error loading data: ", dataLoadingError.message]
|
|
4623
|
+
})
|
|
4624
|
+
}) : isInitialLoading ? /* @__PURE__ */ jsx("div", {
|
|
4625
|
+
className: "flex items-center justify-center py-12 px-8",
|
|
4626
|
+
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
4627
|
+
}) : isEmpty ? /* @__PURE__ */ jsx("div", {
|
|
4628
|
+
className: "w-full flex items-center justify-center py-12 px-8",
|
|
4629
|
+
children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
|
|
4630
|
+
variant: "label",
|
|
4631
|
+
color: "secondary",
|
|
4632
|
+
children: "No entries found"
|
|
4633
|
+
})
|
|
4634
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
4635
|
+
className: "max-w-7xl mx-auto",
|
|
4636
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
4637
|
+
className: cls("grid gap-4", gridColumnsClass),
|
|
4638
|
+
children: data.map((item, index) => {
|
|
4639
|
+
const id = getItemId(item);
|
|
4640
|
+
const selected = selectedIds?.has(id) ?? false;
|
|
4641
|
+
const highlighted = highlightedIds?.has(id) ?? false;
|
|
4642
|
+
const handleClick = (e) => {
|
|
4643
|
+
if ((e.metaKey || e.ctrlKey) && selectionEnabled) {
|
|
4644
|
+
e.preventDefault();
|
|
4645
|
+
onSelectionChange?.(item, !selected);
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
onItemClick?.(item);
|
|
4649
|
+
};
|
|
4650
|
+
const handleSelectionChange = (val) => {
|
|
4651
|
+
onSelectionChange?.(item, val);
|
|
4652
|
+
};
|
|
4653
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: renderCard(item, {
|
|
4654
|
+
selected,
|
|
4655
|
+
highlighted,
|
|
4656
|
+
onSelectionChange: handleSelectionChange,
|
|
4657
|
+
onClick: handleClick
|
|
4658
|
+
}) }, id || index);
|
|
4659
|
+
})
|
|
4660
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
4661
|
+
className: "flex items-center justify-center py-8",
|
|
4662
|
+
children: [dataLoading && /* @__PURE__ */ jsx(CircularProgress, { size: "small" }), !dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsxs(Typography, {
|
|
4663
|
+
variant: "caption",
|
|
4664
|
+
color: "secondary",
|
|
4665
|
+
children: [
|
|
4666
|
+
"All ",
|
|
4667
|
+
data.length,
|
|
4668
|
+
" entries loaded"
|
|
4669
|
+
]
|
|
4670
|
+
})]
|
|
4671
|
+
})]
|
|
4672
|
+
})
|
|
4673
|
+
});
|
|
4674
|
+
}
|
|
4675
|
+
//#endregion
|
|
4676
|
+
//#region src/views/Kanban/BoardSortableList.tsx
|
|
4677
|
+
function BoardSortableList({ columnId, items, ItemComponent, isDragging, isDragOverColumn, loading = false, hasMore = false, onLoadMore }) {
|
|
4678
|
+
const { setNodeRef } = useDroppable({
|
|
4679
|
+
id: columnId,
|
|
4680
|
+
data: { type: "ITEM-LIST" }
|
|
4681
|
+
});
|
|
4682
|
+
const sentinelRef = useRef(null);
|
|
4683
|
+
const isLoadingRef = useRef(loading);
|
|
4684
|
+
isLoadingRef.current = loading;
|
|
4685
|
+
const lastLoadTimeRef = useRef(0);
|
|
4686
|
+
useEffect(() => {
|
|
4687
|
+
if (!sentinelRef.current || !hasMore || !onLoadMore) return;
|
|
4688
|
+
const sentinel = sentinelRef.current;
|
|
4689
|
+
const observer = new IntersectionObserver((entries) => {
|
|
4690
|
+
const now = Date.now();
|
|
4691
|
+
if (entries[0].isIntersecting && hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
|
|
4692
|
+
lastLoadTimeRef.current = now;
|
|
4693
|
+
onLoadMore();
|
|
4694
|
+
}
|
|
4695
|
+
}, { threshold: .1 });
|
|
4696
|
+
observer.observe(sentinel);
|
|
4697
|
+
const rect = sentinel.getBoundingClientRect();
|
|
4698
|
+
const containerRect = sentinel.parentElement?.getBoundingClientRect();
|
|
4699
|
+
if (containerRect && rect.top < containerRect.bottom && rect.bottom > containerRect.top) {
|
|
4700
|
+
const now = Date.now();
|
|
4701
|
+
if (hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
|
|
4702
|
+
lastLoadTimeRef.current = now;
|
|
4703
|
+
onLoadMore();
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
return () => observer.disconnect();
|
|
4707
|
+
}, [hasMore, onLoadMore]);
|
|
4708
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4709
|
+
ref: setNodeRef,
|
|
4710
|
+
className: useMemo(() => cls("flex flex-col p-2 transition-opacity duration-100 transition-bg ease-linear w-full overflow-y-auto no-scrollbar flex-1 rounded-md", isDragging && isDragOverColumn ? "bg-surface-accent-200 dark:bg-surface-900" : isDragging ? "bg-surface-50 dark:bg-surface-900 hover:bg-surface-accent-100 dark:hover:bg-surface-800" : "bg-surface-50 dark:bg-surface-900"), [isDragging, isDragOverColumn]),
|
|
4711
|
+
style: { minHeight: 80 },
|
|
4712
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
4713
|
+
items: items.map((i) => i.id),
|
|
4714
|
+
strategy: verticalListSortingStrategy,
|
|
4715
|
+
children: items.length === 0 && !loading ? /* @__PURE__ */ jsx("div", {
|
|
4716
|
+
className: "flex-1 flex items-center justify-center",
|
|
4717
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
4718
|
+
className: "text-xs text-surface-400 dark:text-surface-500",
|
|
4719
|
+
children: "No items"
|
|
4720
|
+
})
|
|
4721
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [items.map((item, index) => /* @__PURE__ */ jsx(SortableItem, {
|
|
4722
|
+
item,
|
|
4723
|
+
index,
|
|
4724
|
+
columnId,
|
|
4725
|
+
ItemComponent
|
|
4726
|
+
}, item.id)), (loading || hasMore) && /* @__PURE__ */ jsx("div", {
|
|
4727
|
+
ref: sentinelRef,
|
|
4728
|
+
className: "flex items-center justify-center py-2 min-h-6",
|
|
4729
|
+
children: loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
|
|
4730
|
+
})] })
|
|
4731
|
+
})
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
4734
|
+
var SortableItem = memo(function SortableItem({ item, index, columnId, ItemComponent }) {
|
|
4735
|
+
const { setNodeRef, attributes, listeners, isDragging: isItemBeingDragged, transform, transition } = useSortable({
|
|
4736
|
+
id: item.id,
|
|
4737
|
+
data: {
|
|
4738
|
+
type: "ITEM",
|
|
4739
|
+
columnId
|
|
4740
|
+
}
|
|
4741
|
+
});
|
|
4742
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4743
|
+
ref: setNodeRef,
|
|
4744
|
+
style: useMemo(() => ({
|
|
4745
|
+
transform: CSS.Transform.toString(transform),
|
|
4746
|
+
transition,
|
|
4747
|
+
zIndex: isItemBeingDragged ? 2 : 1,
|
|
4748
|
+
opacity: isItemBeingDragged ? 0 : 1
|
|
4749
|
+
}), [
|
|
4750
|
+
transform,
|
|
4751
|
+
transition,
|
|
4752
|
+
isItemBeingDragged
|
|
4753
|
+
]),
|
|
4754
|
+
...attributes,
|
|
4755
|
+
...listeners,
|
|
4756
|
+
children: /* @__PURE__ */ jsx(ItemComponent, {
|
|
4757
|
+
item,
|
|
4758
|
+
isDragging: isItemBeingDragged,
|
|
4759
|
+
index
|
|
4760
|
+
})
|
|
4761
|
+
});
|
|
4762
|
+
});
|
|
4763
|
+
//#endregion
|
|
4764
|
+
//#region src/views/Kanban/BoardColumnTitle.tsx
|
|
4765
|
+
function BoardColumnTitle({ children, className, color, ...props }) {
|
|
4766
|
+
const colorScheme = useMemo(() => {
|
|
4767
|
+
if (!color) return void 0;
|
|
4768
|
+
if (typeof color === "string") return getColorSchemeForKey(color);
|
|
4769
|
+
return color;
|
|
4770
|
+
}, [color]);
|
|
4771
|
+
return /* @__PURE__ */ jsxs(Typography, {
|
|
4772
|
+
variant: "subtitle2",
|
|
4773
|
+
component: "h4",
|
|
4774
|
+
className: cls("py-3 px-3 transition-colors duration-200 flex-grow select-none relative outline-none focus:outline focus:outline-2 focus:outline-offset-2 flex items-center gap-3", className),
|
|
4775
|
+
...props,
|
|
4776
|
+
children: [colorScheme && /* @__PURE__ */ jsx("div", {
|
|
4777
|
+
className: "w-3 h-3 rounded-full flex-shrink-0",
|
|
4778
|
+
style: { backgroundColor: colorScheme.darkColor ?? colorScheme.color }
|
|
4779
|
+
}), children]
|
|
4780
|
+
});
|
|
4781
|
+
}
|
|
4782
|
+
//#endregion
|
|
4041
4783
|
//#region src/icons/icon_keys.ts
|
|
4042
4784
|
var iconKeys = [
|
|
4043
4785
|
"AArrowDown",
|
|
@@ -6089,6 +6831,1563 @@ function HandleIcon() {
|
|
|
6089
6831
|
});
|
|
6090
6832
|
}
|
|
6091
6833
|
//#endregion
|
|
6092
|
-
|
|
6834
|
+
//#region src/views/Kanban/BoardColumn.tsx
|
|
6835
|
+
var BoardColumn = memo(function BoardColumn({ id, title, items, ItemComponent, isDragging, isDragOverColumn, allowReorder = false, loading = false, hasMore = false, onLoadMore, onAddItem, totalCount, color, style }) {
|
|
6836
|
+
const { setNodeRef, attributes, listeners, isDragging: isColumnBeingDragged, transform, transition } = useSortable({
|
|
6837
|
+
id,
|
|
6838
|
+
data: { type: "COLUMN" },
|
|
6839
|
+
disabled: !allowReorder
|
|
6840
|
+
});
|
|
6841
|
+
const combinedStyle = useMemo(() => ({
|
|
6842
|
+
...style,
|
|
6843
|
+
transform: CSS.Translate.toString(transform),
|
|
6844
|
+
transition,
|
|
6845
|
+
zIndex: isColumnBeingDragged ? 2 : 1
|
|
6846
|
+
}), [
|
|
6847
|
+
style,
|
|
6848
|
+
transform,
|
|
6849
|
+
transition,
|
|
6850
|
+
isColumnBeingDragged
|
|
6851
|
+
]);
|
|
6852
|
+
const dragListeners = allowReorder ? listeners : {};
|
|
6853
|
+
const columnClassName = useMemo(() => cls("border h-full w-80 min-w-80 mx-2 flex flex-col rounded-md border-surface-200 dark:border-surface-800", isColumnBeingDragged ? "ring-2 ring-primary" : ""), [isColumnBeingDragged]);
|
|
6854
|
+
const headerClassName = useMemo(() => cls("flex items-center justify-between px-2 rounded-t-md transition-colors duration-200 ease-in-out", isColumnBeingDragged ? "bg-surface-100 dark:bg-surface-700" : "bg-surface-50 hover:bg-surface-100 dark:bg-surface-800 dark:hover:bg-surface-700", allowReorder ? "cursor-grab" : ""), [isColumnBeingDragged, allowReorder]);
|
|
6855
|
+
const itemIds = useMemo(() => items.map((i) => i.id), [items]);
|
|
6856
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6857
|
+
ref: setNodeRef,
|
|
6858
|
+
style: combinedStyle,
|
|
6859
|
+
...attributes,
|
|
6860
|
+
className: columnClassName,
|
|
6861
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
6862
|
+
...dragListeners,
|
|
6863
|
+
className: headerClassName,
|
|
6864
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
6865
|
+
className: "flex items-center gap-2",
|
|
6866
|
+
children: [/* @__PURE__ */ jsx(BoardColumnTitle, {
|
|
6867
|
+
"aria-label": `${title} item list`,
|
|
6868
|
+
color,
|
|
6869
|
+
children: title
|
|
6870
|
+
}), totalCount !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
6871
|
+
className: "text-xs text-surface-500 dark:text-surface-400",
|
|
6872
|
+
children: totalCount
|
|
6873
|
+
})]
|
|
6874
|
+
}), onAddItem && /* @__PURE__ */ jsx(IconButton, {
|
|
6875
|
+
size: "small",
|
|
6876
|
+
onClick: (e) => {
|
|
6877
|
+
e.stopPropagation();
|
|
6878
|
+
onAddItem();
|
|
6879
|
+
},
|
|
6880
|
+
className: "opacity-60 hover:opacity-100",
|
|
6881
|
+
children: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.small })
|
|
6882
|
+
})]
|
|
6883
|
+
}), /* @__PURE__ */ jsx(SortableContext, {
|
|
6884
|
+
items: itemIds,
|
|
6885
|
+
strategy: verticalListSortingStrategy,
|
|
6886
|
+
children: /* @__PURE__ */ jsx(BoardSortableList, {
|
|
6887
|
+
columnId: id,
|
|
6888
|
+
items,
|
|
6889
|
+
ItemComponent,
|
|
6890
|
+
isDragging,
|
|
6891
|
+
isDragOverColumn,
|
|
6892
|
+
loading,
|
|
6893
|
+
hasMore,
|
|
6894
|
+
onLoadMore
|
|
6895
|
+
})
|
|
6896
|
+
})]
|
|
6897
|
+
});
|
|
6898
|
+
});
|
|
6899
|
+
//#endregion
|
|
6900
|
+
//#region src/views/Kanban/Board.tsx
|
|
6901
|
+
function Board({ data, columns: columnsProp, columnLabels, columnColors, className, assignColumn, allowColumnReorder = false, onColumnReorder, onItemsReorder, ItemComponent, columnLoadingState, onLoadMoreColumn, onAddItemToColumn, AddColumnComponent }) {
|
|
6902
|
+
const [activeItem, setActiveItem] = useState(null);
|
|
6903
|
+
const [activeColumn, setActiveColumn] = useState(null);
|
|
6904
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
6905
|
+
const [dragOverColumnId, setDragOverColumnId] = useState(null);
|
|
6906
|
+
const grabOffsetRef = useRef({
|
|
6907
|
+
x: 0,
|
|
6908
|
+
y: 0
|
|
6909
|
+
});
|
|
6910
|
+
const [overlayPos, setOverlayPos] = useState(null);
|
|
6911
|
+
const handleMouseMove = useCallback((e) => {
|
|
6912
|
+
setOverlayPos({
|
|
6913
|
+
x: e.clientX - grabOffsetRef.current.x,
|
|
6914
|
+
y: e.clientY - grabOffsetRef.current.y
|
|
6915
|
+
});
|
|
6916
|
+
}, []);
|
|
6917
|
+
useEffect(() => {
|
|
6918
|
+
return () => {
|
|
6919
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
6920
|
+
window.removeEventListener("pointermove", handleMouseMove);
|
|
6921
|
+
};
|
|
6922
|
+
}, [handleMouseMove]);
|
|
6923
|
+
const [itemMapState, setItemMapState] = useState(() => {
|
|
6924
|
+
const dataColumnMap = data.reduce((prev, item) => ({
|
|
6925
|
+
...prev,
|
|
6926
|
+
[item.id]: assignColumn(item)
|
|
6927
|
+
}), {});
|
|
6928
|
+
return columnsProp.reduce((previous, column) => ({
|
|
6929
|
+
...previous,
|
|
6930
|
+
[column]: data.filter((item) => dataColumnMap[item.id] === column)
|
|
6931
|
+
}), {});
|
|
6932
|
+
});
|
|
6933
|
+
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
|
|
6934
|
+
useEffect(() => {
|
|
6935
|
+
if (isDragging) return;
|
|
6936
|
+
const dataColumnMap = data.reduce((prev, item) => ({
|
|
6937
|
+
...prev,
|
|
6938
|
+
[item.id]: assignColumn(item)
|
|
6939
|
+
}), {});
|
|
6940
|
+
const newItemMap = columnsProp.reduce((previous, column) => ({
|
|
6941
|
+
...previous,
|
|
6942
|
+
[column]: data.filter((item) => dataColumnMap[item.id] === column)
|
|
6943
|
+
}), {});
|
|
6944
|
+
setItemMapState((prevMap) => {
|
|
6945
|
+
let changed = false;
|
|
6946
|
+
for (const col of columnsProp) {
|
|
6947
|
+
const prevItems = prevMap[col] ?? [];
|
|
6948
|
+
const newItems = newItemMap[col] ?? [];
|
|
6949
|
+
if (prevItems.length !== newItems.length) {
|
|
6950
|
+
changed = true;
|
|
6951
|
+
continue;
|
|
6952
|
+
}
|
|
6953
|
+
for (let i = 0; i < prevItems.length; i++) if (prevItems[i].id !== newItems[i].id || prevItems[i].data !== newItems[i].data) {
|
|
6954
|
+
changed = true;
|
|
6955
|
+
break;
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
if (!changed) return prevMap;
|
|
6959
|
+
const updated = {};
|
|
6960
|
+
for (const col of columnsProp) {
|
|
6961
|
+
const prevItems = prevMap[col] ?? [];
|
|
6962
|
+
const newItems = newItemMap[col] ?? [];
|
|
6963
|
+
const prevById = /* @__PURE__ */ new Map();
|
|
6964
|
+
for (const item of prevItems) prevById.set(item.id, item);
|
|
6965
|
+
updated[col] = newItems.map((newItem) => {
|
|
6966
|
+
const prev = prevById.get(newItem.id);
|
|
6967
|
+
if (prev && prev.data === newItem.data) return prev;
|
|
6968
|
+
return newItem;
|
|
6969
|
+
});
|
|
6970
|
+
}
|
|
6971
|
+
return updated;
|
|
6972
|
+
});
|
|
6973
|
+
}, [
|
|
6974
|
+
data,
|
|
6975
|
+
columnsProp,
|
|
6976
|
+
assignColumn
|
|
6977
|
+
]);
|
|
6978
|
+
const findColumnByItemId = (id) => {
|
|
6979
|
+
return Object.keys(itemMapState).find((col) => itemMapState[col]?.some((i) => i.id === id));
|
|
6980
|
+
};
|
|
6981
|
+
const handleDragStart = (event) => {
|
|
6982
|
+
setIsDragging(true);
|
|
6983
|
+
setDragOverColumnId(null);
|
|
6984
|
+
const { active } = event;
|
|
6985
|
+
const activatorEvt = event.activatorEvent;
|
|
6986
|
+
if (activatorEvt) {
|
|
6987
|
+
const target = activatorEvt.target;
|
|
6988
|
+
const rect = (target.closest("[role='button']") ?? target).getBoundingClientRect();
|
|
6989
|
+
grabOffsetRef.current = {
|
|
6990
|
+
x: activatorEvt.clientX - rect.left,
|
|
6991
|
+
y: activatorEvt.clientY - rect.top
|
|
6992
|
+
};
|
|
6993
|
+
setOverlayPos({
|
|
6994
|
+
x: rect.left,
|
|
6995
|
+
y: rect.top
|
|
6996
|
+
});
|
|
6997
|
+
}
|
|
6998
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
6999
|
+
window.addEventListener("pointermove", handleMouseMove);
|
|
7000
|
+
if (active.data.current?.type === "COLUMN") {
|
|
7001
|
+
const columnId = active.id;
|
|
7002
|
+
const column = columnsProp.find((col) => String(col) === columnId);
|
|
7003
|
+
if (column) setActiveColumn(column);
|
|
7004
|
+
} else if (active.data.current?.type === "ITEM") {
|
|
7005
|
+
const columnId = findColumnByItemId(active.id);
|
|
7006
|
+
if (columnId) {
|
|
7007
|
+
const item = itemMapState[columnId]?.find((i) => i.id === active.id);
|
|
7008
|
+
setActiveItem(item || null);
|
|
7009
|
+
}
|
|
7010
|
+
}
|
|
7011
|
+
};
|
|
7012
|
+
const handleDragOver = (event) => {
|
|
7013
|
+
const { active, over } = event;
|
|
7014
|
+
if (!over) {
|
|
7015
|
+
setDragOverColumnId(null);
|
|
7016
|
+
return;
|
|
7017
|
+
}
|
|
7018
|
+
let currentHoveredColumnId = null;
|
|
7019
|
+
const overId = over.id;
|
|
7020
|
+
const overDataType = over.data.current?.type;
|
|
7021
|
+
if (overDataType === "ITEM-LIST" || overDataType === "COLUMN") currentHoveredColumnId = overId;
|
|
7022
|
+
else if (overDataType === "ITEM") currentHoveredColumnId = findColumnByItemId(overId) || null;
|
|
7023
|
+
else if (columnsProp.includes(overId)) currentHoveredColumnId = overId;
|
|
7024
|
+
setDragOverColumnId(currentHoveredColumnId);
|
|
7025
|
+
if (active.data.current?.type !== "ITEM") return;
|
|
7026
|
+
const activeId = active.id;
|
|
7027
|
+
const activeColumn = findColumnByItemId(activeId);
|
|
7028
|
+
let overColumnForMove = findColumnByItemId(overId);
|
|
7029
|
+
if (!overColumnForMove && overDataType === "ITEM-LIST") overColumnForMove = overId;
|
|
7030
|
+
if (!overColumnForMove && columnsProp.includes(overId)) overColumnForMove = overId;
|
|
7031
|
+
if (!activeColumn || !overColumnForMove) return;
|
|
7032
|
+
if (activeColumn === overColumnForMove) return;
|
|
7033
|
+
if (itemMapState[overColumnForMove]?.some((i) => i.id === activeId)) return;
|
|
7034
|
+
setItemMapState((currentMap) => {
|
|
7035
|
+
const activeItems = [...currentMap[activeColumn] || []];
|
|
7036
|
+
const overItems = [...currentMap[overColumnForMove] || []];
|
|
7037
|
+
const activeIndex = activeItems.findIndex((i) => i.id === activeId);
|
|
7038
|
+
if (activeIndex === -1) return currentMap;
|
|
7039
|
+
let overIndex;
|
|
7040
|
+
if (overDataType === "ITEM-LIST" || columnsProp.includes(overId) && !findColumnByItemId(overId)) overIndex = overItems.length;
|
|
7041
|
+
else {
|
|
7042
|
+
overIndex = overItems.findIndex((i) => i.id === overId);
|
|
7043
|
+
if (overIndex !== -1) {
|
|
7044
|
+
const modifier = (active.rect.current.translated?.top ?? 0) + (active.rect.current.translated?.height ?? 0) / 2 > (over?.rect.top ?? 0) + (over?.rect.height ?? 0) / 2 ? 1 : 0;
|
|
7045
|
+
overIndex = overIndex >= 0 ? overIndex + modifier : overItems.length;
|
|
7046
|
+
} else overIndex = overItems.length;
|
|
7047
|
+
}
|
|
7048
|
+
const newItemMap = { ...currentMap };
|
|
7049
|
+
const [moved] = activeItems.splice(activeIndex, 1);
|
|
7050
|
+
overItems.splice(overIndex, 0, moved);
|
|
7051
|
+
newItemMap[activeColumn] = activeItems;
|
|
7052
|
+
newItemMap[overColumnForMove] = overItems;
|
|
7053
|
+
return newItemMap;
|
|
7054
|
+
});
|
|
7055
|
+
};
|
|
7056
|
+
const handleDragEnd = (event) => {
|
|
7057
|
+
const { active, over } = event;
|
|
7058
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
7059
|
+
window.removeEventListener("pointermove", handleMouseMove);
|
|
7060
|
+
setOverlayPos(null);
|
|
7061
|
+
setIsDragging(false);
|
|
7062
|
+
setActiveItem(null);
|
|
7063
|
+
setActiveColumn(null);
|
|
7064
|
+
setDragOverColumnId(null);
|
|
7065
|
+
if (!over) return;
|
|
7066
|
+
const activeId = active.id;
|
|
7067
|
+
const overId = over.id;
|
|
7068
|
+
if (active.data.current?.type === "COLUMN") {
|
|
7069
|
+
if (activeId !== overId) {
|
|
7070
|
+
const oldIndex = columnsProp.indexOf(activeId);
|
|
7071
|
+
const newIndex = columnsProp.indexOf(overId);
|
|
7072
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
7073
|
+
const reordered = arrayMove(columnsProp, oldIndex, newIndex);
|
|
7074
|
+
onColumnReorder?.(reordered);
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
return;
|
|
7078
|
+
}
|
|
7079
|
+
const activeCol = findColumnByItemId(activeId);
|
|
7080
|
+
let overCol = findColumnByItemId(overId);
|
|
7081
|
+
if (!overCol) {
|
|
7082
|
+
if (over.data.current?.type === "ITEM-LIST" || columnsProp.includes(overId)) overCol = overId;
|
|
7083
|
+
}
|
|
7084
|
+
if (!activeCol || !overCol) return;
|
|
7085
|
+
const isSameColumn = activeCol === overCol;
|
|
7086
|
+
const activeItems = itemMapState[activeCol] || [];
|
|
7087
|
+
const overItems = itemMapState[overCol] || [];
|
|
7088
|
+
const activeIndex = activeItems.findIndex((i) => i.id === activeId);
|
|
7089
|
+
let overIndex;
|
|
7090
|
+
if (over.id === overCol) overIndex = overItems.length;
|
|
7091
|
+
else overIndex = overItems.findIndex((i) => i.id === overId);
|
|
7092
|
+
if (activeIndex === -1 || overIndex === -1) return;
|
|
7093
|
+
let finalItems = [];
|
|
7094
|
+
if (isSameColumn) finalItems = arrayMove(activeItems, activeIndex, overIndex);
|
|
7095
|
+
else {
|
|
7096
|
+
const newActiveItems = [...activeItems];
|
|
7097
|
+
const newOverItems = [...overItems];
|
|
7098
|
+
const [moved] = newActiveItems.splice(activeIndex, 1);
|
|
7099
|
+
newOverItems.splice(overIndex, 0, moved);
|
|
7100
|
+
finalItems = [...newActiveItems, ...newOverItems];
|
|
7101
|
+
}
|
|
7102
|
+
const fullFlattenedList = [];
|
|
7103
|
+
columnsProp.forEach((col) => {
|
|
7104
|
+
if (col === activeCol) if (isSameColumn) fullFlattenedList.push(...finalItems);
|
|
7105
|
+
else fullFlattenedList.push(...activeItems.filter((i) => i.id !== activeId));
|
|
7106
|
+
else if (col === overCol) {
|
|
7107
|
+
const newOverItems = [...overItems];
|
|
7108
|
+
const [moved] = [...activeItems].splice(activeIndex, 1);
|
|
7109
|
+
newOverItems.splice(overIndex, 0, moved);
|
|
7110
|
+
fullFlattenedList.push(...newOverItems);
|
|
7111
|
+
} else fullFlattenedList.push(...itemMapState[col] || []);
|
|
7112
|
+
});
|
|
7113
|
+
onItemsReorder?.(fullFlattenedList, {
|
|
7114
|
+
itemId: activeId,
|
|
7115
|
+
sourceColumn: activeCol,
|
|
7116
|
+
targetColumn: overCol
|
|
7117
|
+
});
|
|
7118
|
+
};
|
|
7119
|
+
return /* @__PURE__ */ jsxs(DndContext, {
|
|
7120
|
+
sensors,
|
|
7121
|
+
onDragStart: handleDragStart,
|
|
7122
|
+
onDragOver: handleDragOver,
|
|
7123
|
+
onDragEnd: handleDragEnd,
|
|
7124
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7125
|
+
className: cls("flex flex-row h-full w-full overflow-x-auto p-4 select-none items-start", className),
|
|
7126
|
+
children: [/* @__PURE__ */ jsx(SortableContext, {
|
|
7127
|
+
items: columnsProp,
|
|
7128
|
+
children: columnsProp.map((col, index) => {
|
|
7129
|
+
const colItems = itemMapState[col] || [];
|
|
7130
|
+
const loadingState = columnLoadingState?.[col];
|
|
7131
|
+
return /* @__PURE__ */ jsx(BoardColumn, {
|
|
7132
|
+
id: String(col),
|
|
7133
|
+
title: columnLabels?.[col] ?? String(col),
|
|
7134
|
+
items: colItems,
|
|
7135
|
+
index,
|
|
7136
|
+
ItemComponent,
|
|
7137
|
+
isDragging,
|
|
7138
|
+
isDragOverColumn: dragOverColumnId === String(col),
|
|
7139
|
+
allowReorder: allowColumnReorder,
|
|
7140
|
+
loading: loadingState?.loading,
|
|
7141
|
+
hasMore: loadingState?.hasMore,
|
|
7142
|
+
totalCount: loadingState?.totalCount,
|
|
7143
|
+
color: columnColors?.[col],
|
|
7144
|
+
onLoadMore: onLoadMoreColumn ? () => onLoadMoreColumn(col) : void 0,
|
|
7145
|
+
onAddItem: onAddItemToColumn ? () => onAddItemToColumn(col) : void 0
|
|
7146
|
+
}, String(col));
|
|
7147
|
+
})
|
|
7148
|
+
}), AddColumnComponent]
|
|
7149
|
+
}), typeof document !== "undefined" && createPortal(/* @__PURE__ */ jsx(DragOverlay, {
|
|
7150
|
+
dropAnimation: null,
|
|
7151
|
+
children: isDragging && activeItem && /* @__PURE__ */ jsx("div", {
|
|
7152
|
+
style: {
|
|
7153
|
+
position: "fixed",
|
|
7154
|
+
left: overlayPos?.x ?? 0,
|
|
7155
|
+
top: overlayPos?.y ?? 0,
|
|
7156
|
+
pointerEvents: "none",
|
|
7157
|
+
zIndex: 9999
|
|
7158
|
+
},
|
|
7159
|
+
children: /* @__PURE__ */ jsx(ItemComponent, {
|
|
7160
|
+
item: activeItem,
|
|
7161
|
+
isDragging: true,
|
|
7162
|
+
isClone: true
|
|
7163
|
+
})
|
|
7164
|
+
})
|
|
7165
|
+
}), document.body)]
|
|
7166
|
+
});
|
|
7167
|
+
}
|
|
7168
|
+
//#endregion
|
|
7169
|
+
//#region src/views/KanbanView.tsx
|
|
7170
|
+
function KanbanView(props) {
|
|
7171
|
+
return /* @__PURE__ */ jsx(Board, { ...props });
|
|
7172
|
+
}
|
|
7173
|
+
//#endregion
|
|
7174
|
+
//#region src/views/CollectionView/CollectionViewToolbar.tsx
|
|
7175
|
+
var VIEW_MODE_ICONS = {
|
|
7176
|
+
list: /* @__PURE__ */ jsx(LayoutList, { size: iconSize.small }),
|
|
7177
|
+
table: /* @__PURE__ */ jsx(Table2, { size: iconSize.small }),
|
|
7178
|
+
cards: /* @__PURE__ */ jsx(LayoutGrid, { size: iconSize.small }),
|
|
7179
|
+
kanban: /* @__PURE__ */ jsx(Kanban, { size: iconSize.small })
|
|
7180
|
+
};
|
|
7181
|
+
var VIEW_MODE_LABELS = {
|
|
7182
|
+
list: "List",
|
|
7183
|
+
table: "Table",
|
|
7184
|
+
cards: "Cards",
|
|
7185
|
+
kanban: "Board"
|
|
7186
|
+
};
|
|
7187
|
+
var SIZE_LABELS = {
|
|
7188
|
+
xs: "Extra Small",
|
|
7189
|
+
s: "Small",
|
|
7190
|
+
m: "Medium",
|
|
7191
|
+
l: "Large",
|
|
7192
|
+
xl: "Extra Large"
|
|
7193
|
+
};
|
|
7194
|
+
/**
|
|
7195
|
+
* Toolbar for the headless CollectionView.
|
|
7196
|
+
*
|
|
7197
|
+
* Renders a search bar, view mode toggle (popover with toggle button group),
|
|
7198
|
+
* optional size selector, optional kanban property selector, and action slots.
|
|
7199
|
+
*
|
|
7200
|
+
* @group Collection View
|
|
7201
|
+
*/
|
|
7202
|
+
function CollectionViewToolbar({ viewMode, onViewModeChange, enabledViews = [
|
|
7203
|
+
"list",
|
|
7204
|
+
"table",
|
|
7205
|
+
"cards",
|
|
7206
|
+
"kanban"
|
|
7207
|
+
], size = "m", onSizeChange, searchString, onSearchChange, loading, actionsStart, actionsEnd, compact, kanbanPropertyOptions, selectedKanbanProperty, onKanbanPropertyChange }) {
|
|
7208
|
+
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
7209
|
+
const viewOptions = enabledViews.map((mode) => ({
|
|
7210
|
+
value: mode,
|
|
7211
|
+
label: VIEW_MODE_LABELS[mode],
|
|
7212
|
+
icon: VIEW_MODE_ICONS[mode]
|
|
7213
|
+
}));
|
|
7214
|
+
const handleViewModeChange = useCallback((mode) => {
|
|
7215
|
+
onViewModeChange?.(mode);
|
|
7216
|
+
}, [onViewModeChange]);
|
|
7217
|
+
const handleSizeChange = useCallback((value) => {
|
|
7218
|
+
onSizeChange?.(value);
|
|
7219
|
+
}, [onSizeChange]);
|
|
7220
|
+
const handleKanbanPropertyChange = useCallback((value) => {
|
|
7221
|
+
onKanbanPropertyChange?.(value);
|
|
7222
|
+
}, [onKanbanPropertyChange]);
|
|
7223
|
+
const showViewToggle = onViewModeChange && enabledViews.length > 1;
|
|
7224
|
+
const showSizeSelector = onSizeChange && viewMode !== "kanban";
|
|
7225
|
+
const showKanbanSelector = kanbanPropertyOptions && kanbanPropertyOptions.length > 0 && viewMode === "kanban" && onKanbanPropertyChange;
|
|
7226
|
+
const showPopover = showViewToggle || showSizeSelector || showKanbanSelector;
|
|
7227
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7228
|
+
className: cls("flex items-center gap-2 px-2 flex-shrink-0", "border-b border-surface-200 dark:border-surface-800", compact ? "py-0.5 gap-1" : "py-1"),
|
|
7229
|
+
children: [
|
|
7230
|
+
actionsStart,
|
|
7231
|
+
onSearchChange && /* @__PURE__ */ jsx(SearchBar, {
|
|
7232
|
+
onTextSearch: onSearchChange,
|
|
7233
|
+
size: "small",
|
|
7234
|
+
initialValue: searchString,
|
|
7235
|
+
loading,
|
|
7236
|
+
expandable: true
|
|
7237
|
+
}),
|
|
7238
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
7239
|
+
actionsEnd,
|
|
7240
|
+
showPopover && /* @__PURE__ */ jsx(Popover, {
|
|
7241
|
+
open: popoverOpen,
|
|
7242
|
+
onOpenChange: setPopoverOpen,
|
|
7243
|
+
side: "bottom",
|
|
7244
|
+
align: "end",
|
|
7245
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
7246
|
+
size: "small",
|
|
7247
|
+
onClick: () => setPopoverOpen((prev) => !prev),
|
|
7248
|
+
children: /* @__PURE__ */ jsx(Settings2, { size: iconSize.small })
|
|
7249
|
+
}),
|
|
7250
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
7251
|
+
className: "p-3 min-w-[200px] flex flex-col gap-3",
|
|
7252
|
+
children: [
|
|
7253
|
+
showViewToggle && /* @__PURE__ */ jsxs("div", {
|
|
7254
|
+
className: "flex flex-col gap-1",
|
|
7255
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
7256
|
+
variant: "caption",
|
|
7257
|
+
color: "secondary",
|
|
7258
|
+
className: "font-medium uppercase tracking-wider",
|
|
7259
|
+
children: "View"
|
|
7260
|
+
}), /* @__PURE__ */ jsx(ToggleButtonGroup, {
|
|
7261
|
+
value: viewMode,
|
|
7262
|
+
onValueChange: handleViewModeChange,
|
|
7263
|
+
options: viewOptions
|
|
7264
|
+
})]
|
|
7265
|
+
}),
|
|
7266
|
+
showSizeSelector && /* @__PURE__ */ jsxs("div", {
|
|
7267
|
+
className: "flex flex-col gap-1",
|
|
7268
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
7269
|
+
variant: "caption",
|
|
7270
|
+
color: "secondary",
|
|
7271
|
+
className: "font-medium uppercase tracking-wider",
|
|
7272
|
+
children: "Size"
|
|
7273
|
+
}), /* @__PURE__ */ jsx(Select, {
|
|
7274
|
+
value: size,
|
|
7275
|
+
onValueChange: handleSizeChange,
|
|
7276
|
+
size: "small",
|
|
7277
|
+
children: Object.keys(SIZE_LABELS).map((sizeKey) => /* @__PURE__ */ jsx(SelectItem, {
|
|
7278
|
+
value: sizeKey,
|
|
7279
|
+
children: SIZE_LABELS[sizeKey]
|
|
7280
|
+
}, sizeKey))
|
|
7281
|
+
})]
|
|
7282
|
+
}),
|
|
7283
|
+
showKanbanSelector && /* @__PURE__ */ jsxs("div", {
|
|
7284
|
+
className: "flex flex-col gap-1",
|
|
7285
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
7286
|
+
variant: "caption",
|
|
7287
|
+
color: "secondary",
|
|
7288
|
+
className: "font-medium uppercase tracking-wider",
|
|
7289
|
+
children: "Group by"
|
|
7290
|
+
}), /* @__PURE__ */ jsx(Select, {
|
|
7291
|
+
value: selectedKanbanProperty,
|
|
7292
|
+
onValueChange: handleKanbanPropertyChange,
|
|
7293
|
+
size: "small",
|
|
7294
|
+
children: kanbanPropertyOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
7295
|
+
value: option.key,
|
|
7296
|
+
children: option.label
|
|
7297
|
+
}, option.key))
|
|
7298
|
+
})]
|
|
7299
|
+
})
|
|
7300
|
+
]
|
|
7301
|
+
})
|
|
7302
|
+
})
|
|
7303
|
+
]
|
|
7304
|
+
});
|
|
7305
|
+
}
|
|
7306
|
+
//#endregion
|
|
7307
|
+
//#region src/views/CollectionView/DefaultCellRenderer.tsx
|
|
7308
|
+
/**
|
|
7309
|
+
* Resolve an enum key to its display label and optional color.
|
|
7310
|
+
*/
|
|
7311
|
+
function resolveEnumLabel(enumValues, value) {
|
|
7312
|
+
if (!enumValues) return void 0;
|
|
7313
|
+
const key = String(value);
|
|
7314
|
+
if (enumValues instanceof Map) {
|
|
7315
|
+
const config = enumValues.get(value);
|
|
7316
|
+
if (!config) return void 0;
|
|
7317
|
+
if (typeof config === "string") return { label: config };
|
|
7318
|
+
return {
|
|
7319
|
+
label: config.label,
|
|
7320
|
+
color: config.color
|
|
7321
|
+
};
|
|
7322
|
+
}
|
|
7323
|
+
const config = enumValues[key];
|
|
7324
|
+
if (!config) return void 0;
|
|
7325
|
+
if (typeof config === "string") return { label: config };
|
|
7326
|
+
return {
|
|
7327
|
+
label: config.label,
|
|
7328
|
+
color: config.color
|
|
7329
|
+
};
|
|
7330
|
+
}
|
|
7331
|
+
/**
|
|
7332
|
+
* Format a date value using Intl.DateTimeFormat.
|
|
7333
|
+
*/
|
|
7334
|
+
function formatDate(value, includeTime) {
|
|
7335
|
+
if (value == null) return "—";
|
|
7336
|
+
const date = value instanceof Date ? value : new Date(String(value));
|
|
7337
|
+
if (isNaN(date.getTime())) return String(value);
|
|
7338
|
+
const options = includeTime ? {
|
|
7339
|
+
year: "numeric",
|
|
7340
|
+
month: "short",
|
|
7341
|
+
day: "numeric",
|
|
7342
|
+
hour: "2-digit",
|
|
7343
|
+
minute: "2-digit"
|
|
7344
|
+
} : {
|
|
7345
|
+
year: "numeric",
|
|
7346
|
+
month: "short",
|
|
7347
|
+
day: "numeric"
|
|
7348
|
+
};
|
|
7349
|
+
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
7350
|
+
}
|
|
7351
|
+
/**
|
|
7352
|
+
* Type guard for geopoint-like objects.
|
|
7353
|
+
*/
|
|
7354
|
+
function isGeoPoint(value) {
|
|
7355
|
+
if (typeof value !== "object" || value === null) return false;
|
|
7356
|
+
const obj = value;
|
|
7357
|
+
return typeof obj.lat === "number" && typeof obj.lng === "number";
|
|
7358
|
+
}
|
|
7359
|
+
/**
|
|
7360
|
+
* Default cell renderer for the headless CollectionView.
|
|
7361
|
+
* Handles all built-in property types without depending on any Rebase core types.
|
|
7362
|
+
*
|
|
7363
|
+
* @group Preview components
|
|
7364
|
+
*/
|
|
7365
|
+
function DefaultCellRenderer({ row, propertyKey, property, value, size, width, height }) {
|
|
7366
|
+
if (value === null || value === void 0) return /* @__PURE__ */ jsx(Typography, {
|
|
7367
|
+
variant: "body2",
|
|
7368
|
+
color: "secondary",
|
|
7369
|
+
className: "px-1",
|
|
7370
|
+
children: "—"
|
|
7371
|
+
});
|
|
7372
|
+
if (property.Preview) {
|
|
7373
|
+
const PreviewComponent = property.Preview;
|
|
7374
|
+
return /* @__PURE__ */ jsx(PreviewComponent, {
|
|
7375
|
+
row,
|
|
7376
|
+
propertyKey,
|
|
7377
|
+
property,
|
|
7378
|
+
value,
|
|
7379
|
+
size,
|
|
7380
|
+
width,
|
|
7381
|
+
height
|
|
7382
|
+
});
|
|
7383
|
+
}
|
|
7384
|
+
switch (property.type) {
|
|
7385
|
+
case "string": return renderString(property, value);
|
|
7386
|
+
case "number": return renderNumber(property, value);
|
|
7387
|
+
case "boolean": return renderBoolean(value);
|
|
7388
|
+
case "date": return renderDate(property, value);
|
|
7389
|
+
case "array": return renderArray(property, value);
|
|
7390
|
+
case "map": return renderMap(value);
|
|
7391
|
+
case "reference":
|
|
7392
|
+
case "relation": return renderReference(value);
|
|
7393
|
+
case "geopoint": return renderGeopoint(value);
|
|
7394
|
+
default: return /* @__PURE__ */ jsx(Typography, {
|
|
7395
|
+
variant: "body2",
|
|
7396
|
+
color: "secondary",
|
|
7397
|
+
noWrap: true,
|
|
7398
|
+
className: "px-1",
|
|
7399
|
+
children: JSON.stringify(value)
|
|
7400
|
+
});
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
function renderString(property, value) {
|
|
7404
|
+
const strValue = String(value);
|
|
7405
|
+
if (property.enum) {
|
|
7406
|
+
const resolved = resolveEnumLabel(property.enum, value);
|
|
7407
|
+
return /* @__PURE__ */ jsx(Chip, {
|
|
7408
|
+
size: "small",
|
|
7409
|
+
colorScheme: resolved?.color ? {
|
|
7410
|
+
color: resolved.color,
|
|
7411
|
+
text: "#fff"
|
|
7412
|
+
} : void 0,
|
|
7413
|
+
children: resolved?.label ?? strValue
|
|
7414
|
+
});
|
|
7415
|
+
}
|
|
7416
|
+
if (property.previewAsTag) return /* @__PURE__ */ jsx(Chip, {
|
|
7417
|
+
size: "small",
|
|
7418
|
+
children: strValue
|
|
7419
|
+
});
|
|
7420
|
+
if (property.url) return /* @__PURE__ */ jsx(Typography, {
|
|
7421
|
+
variant: "body2",
|
|
7422
|
+
noWrap: true,
|
|
7423
|
+
component: "a",
|
|
7424
|
+
href: typeof property.url === "string" ? property.url : strValue,
|
|
7425
|
+
target: "_blank",
|
|
7426
|
+
rel: "noopener noreferrer",
|
|
7427
|
+
className: cls("px-1 text-primary hover:underline cursor-pointer"),
|
|
7428
|
+
children: strValue
|
|
7429
|
+
});
|
|
7430
|
+
if (property.email) return /* @__PURE__ */ jsx(Typography, {
|
|
7431
|
+
variant: "body2",
|
|
7432
|
+
noWrap: true,
|
|
7433
|
+
component: "a",
|
|
7434
|
+
href: `mailto:${strValue}`,
|
|
7435
|
+
className: cls("px-1 text-primary hover:underline cursor-pointer"),
|
|
7436
|
+
children: strValue
|
|
7437
|
+
});
|
|
7438
|
+
if (property.markdown) return /* @__PURE__ */ jsx("div", {
|
|
7439
|
+
className: "px-1 max-h-[100px] overflow-hidden",
|
|
7440
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
7441
|
+
source: strValue,
|
|
7442
|
+
size: "small"
|
|
7443
|
+
})
|
|
7444
|
+
});
|
|
7445
|
+
if (property.storage) return /* @__PURE__ */ jsxs("div", {
|
|
7446
|
+
className: cls("px-1 flex items-center justify-center", "w-[40px] h-[40px] rounded overflow-hidden"),
|
|
7447
|
+
children: [strValue ? /* @__PURE__ */ jsx("img", {
|
|
7448
|
+
src: strValue,
|
|
7449
|
+
alt: "",
|
|
7450
|
+
className: "w-full h-full object-cover",
|
|
7451
|
+
onError: (e) => {
|
|
7452
|
+
const target = e.currentTarget;
|
|
7453
|
+
target.style.display = "none";
|
|
7454
|
+
const fallback = target.nextElementSibling;
|
|
7455
|
+
if (fallback instanceof HTMLElement) fallback.style.display = "flex";
|
|
7456
|
+
}
|
|
7457
|
+
}) : null, /* @__PURE__ */ jsx("div", {
|
|
7458
|
+
className: cls("w-full h-full items-center justify-center rounded", "bg-surface-100 dark:bg-surface-800 text-text-secondary dark:text-text-secondary-dark", strValue ? "hidden" : "flex"),
|
|
7459
|
+
children: /* @__PURE__ */ jsx(ImageIcon$1, { size: iconSize.small })
|
|
7460
|
+
})]
|
|
7461
|
+
});
|
|
7462
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
7463
|
+
title: strValue.length > 50 ? strValue : void 0,
|
|
7464
|
+
side: "bottom",
|
|
7465
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
7466
|
+
variant: "body2",
|
|
7467
|
+
noWrap: true,
|
|
7468
|
+
className: "px-1",
|
|
7469
|
+
children: strValue
|
|
7470
|
+
})
|
|
7471
|
+
});
|
|
7472
|
+
}
|
|
7473
|
+
function renderNumber(property, value) {
|
|
7474
|
+
const numStr = String(value);
|
|
7475
|
+
if (property.enum) {
|
|
7476
|
+
const resolved = resolveEnumLabel(property.enum, value);
|
|
7477
|
+
return /* @__PURE__ */ jsx(Chip, {
|
|
7478
|
+
size: "small",
|
|
7479
|
+
colorScheme: resolved?.color ? {
|
|
7480
|
+
color: resolved.color,
|
|
7481
|
+
text: "#fff"
|
|
7482
|
+
} : void 0,
|
|
7483
|
+
children: resolved?.label ?? numStr
|
|
7484
|
+
});
|
|
7485
|
+
}
|
|
7486
|
+
return /* @__PURE__ */ jsx(Typography, {
|
|
7487
|
+
variant: "body2",
|
|
7488
|
+
noWrap: true,
|
|
7489
|
+
align: "right",
|
|
7490
|
+
className: "px-1 tabular-nums",
|
|
7491
|
+
children: numStr
|
|
7492
|
+
});
|
|
7493
|
+
}
|
|
7494
|
+
function renderBoolean(value) {
|
|
7495
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7496
|
+
className: "px-1 flex items-center",
|
|
7497
|
+
children: /* @__PURE__ */ jsx(BooleanSwitch, {
|
|
7498
|
+
value: !!value,
|
|
7499
|
+
size: "small",
|
|
7500
|
+
disabled: true
|
|
7501
|
+
})
|
|
7502
|
+
});
|
|
7503
|
+
}
|
|
7504
|
+
function renderDate(property, value) {
|
|
7505
|
+
return /* @__PURE__ */ jsx(Typography, {
|
|
7506
|
+
variant: "body2",
|
|
7507
|
+
noWrap: true,
|
|
7508
|
+
className: "px-1 tabular-nums",
|
|
7509
|
+
children: formatDate(value, property.mode === "date_time")
|
|
7510
|
+
});
|
|
7511
|
+
}
|
|
7512
|
+
function renderArray(property, value) {
|
|
7513
|
+
if (!Array.isArray(value)) return /* @__PURE__ */ jsx(Typography, {
|
|
7514
|
+
variant: "body2",
|
|
7515
|
+
color: "secondary",
|
|
7516
|
+
className: "px-1",
|
|
7517
|
+
children: "—"
|
|
7518
|
+
});
|
|
7519
|
+
if (property.of?.enum && value.length > 0) return /* @__PURE__ */ jsx("div", {
|
|
7520
|
+
className: "px-1 flex flex-wrap gap-1 overflow-hidden",
|
|
7521
|
+
children: value.map((item, i) => {
|
|
7522
|
+
const resolved = resolveEnumLabel(property.of.enum, item);
|
|
7523
|
+
return /* @__PURE__ */ jsx(Chip, {
|
|
7524
|
+
size: "smallest",
|
|
7525
|
+
colorScheme: resolved?.color ? {
|
|
7526
|
+
color: resolved.color,
|
|
7527
|
+
text: "#fff"
|
|
7528
|
+
} : void 0,
|
|
7529
|
+
children: resolved?.label ?? String(item)
|
|
7530
|
+
}, i);
|
|
7531
|
+
})
|
|
7532
|
+
});
|
|
7533
|
+
if (value.length > 0 && value.every((item) => typeof item === "string")) return /* @__PURE__ */ jsx(Typography, {
|
|
7534
|
+
variant: "body2",
|
|
7535
|
+
noWrap: true,
|
|
7536
|
+
className: "px-1",
|
|
7537
|
+
children: value.join(", ")
|
|
7538
|
+
});
|
|
7539
|
+
return /* @__PURE__ */ jsxs(Typography, {
|
|
7540
|
+
variant: "body2",
|
|
7541
|
+
color: "secondary",
|
|
7542
|
+
className: "px-1",
|
|
7543
|
+
children: [
|
|
7544
|
+
value.length,
|
|
7545
|
+
" ",
|
|
7546
|
+
value.length === 1 ? "item" : "items"
|
|
7547
|
+
]
|
|
7548
|
+
});
|
|
7549
|
+
}
|
|
7550
|
+
function renderMap(value) {
|
|
7551
|
+
if (typeof value !== "object" || value === null) return /* @__PURE__ */ jsx(Typography, {
|
|
7552
|
+
variant: "body2",
|
|
7553
|
+
color: "secondary",
|
|
7554
|
+
className: "px-1",
|
|
7555
|
+
children: "—"
|
|
7556
|
+
});
|
|
7557
|
+
const fieldCount = Object.keys(value).length;
|
|
7558
|
+
return /* @__PURE__ */ jsxs(Typography, {
|
|
7559
|
+
variant: "body2",
|
|
7560
|
+
color: "secondary",
|
|
7561
|
+
className: "px-1",
|
|
7562
|
+
children: [
|
|
7563
|
+
fieldCount,
|
|
7564
|
+
" ",
|
|
7565
|
+
fieldCount === 1 ? "field" : "fields"
|
|
7566
|
+
]
|
|
7567
|
+
});
|
|
7568
|
+
}
|
|
7569
|
+
function renderReference(value) {
|
|
7570
|
+
return /* @__PURE__ */ jsx(Typography, {
|
|
7571
|
+
variant: "body2",
|
|
7572
|
+
noWrap: true,
|
|
7573
|
+
className: "px-1",
|
|
7574
|
+
children: String(value)
|
|
7575
|
+
});
|
|
7576
|
+
}
|
|
7577
|
+
function renderGeopoint(value) {
|
|
7578
|
+
if (isGeoPoint(value)) return /* @__PURE__ */ jsxs(Typography, {
|
|
7579
|
+
variant: "body2",
|
|
7580
|
+
noWrap: true,
|
|
7581
|
+
className: "px-1 tabular-nums",
|
|
7582
|
+
children: [
|
|
7583
|
+
value.lat.toFixed(6),
|
|
7584
|
+
", ",
|
|
7585
|
+
value.lng.toFixed(6)
|
|
7586
|
+
]
|
|
7587
|
+
});
|
|
7588
|
+
return /* @__PURE__ */ jsx(Typography, {
|
|
7589
|
+
variant: "body2",
|
|
7590
|
+
color: "secondary",
|
|
7591
|
+
className: "px-1",
|
|
7592
|
+
children: String(value)
|
|
7593
|
+
});
|
|
7594
|
+
}
|
|
7595
|
+
//#endregion
|
|
7596
|
+
//#region src/views/CollectionView/utils.ts
|
|
7597
|
+
/**
|
|
7598
|
+
* Traverse an object by dot-separated path.
|
|
7599
|
+
* Returns `undefined` if any segment is missing.
|
|
7600
|
+
*/
|
|
7601
|
+
function getValueInPath(obj, path) {
|
|
7602
|
+
if (!obj) return void 0;
|
|
7603
|
+
const parts = path.split(".");
|
|
7604
|
+
let current = obj;
|
|
7605
|
+
for (const part of parts) {
|
|
7606
|
+
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
7607
|
+
current = current[part];
|
|
7608
|
+
}
|
|
7609
|
+
return current;
|
|
7610
|
+
}
|
|
7611
|
+
//#endregion
|
|
7612
|
+
//#region src/views/CollectionView/CollectionTableView.tsx
|
|
7613
|
+
var DEFAULT_COLUMN_WIDTHS = {
|
|
7614
|
+
string: 200,
|
|
7615
|
+
number: 140,
|
|
7616
|
+
date: 160,
|
|
7617
|
+
boolean: 120,
|
|
7618
|
+
array: 200,
|
|
7619
|
+
map: 200,
|
|
7620
|
+
reference: 200,
|
|
7621
|
+
relation: 200,
|
|
7622
|
+
geopoint: 180
|
|
7623
|
+
};
|
|
7624
|
+
var FALLBACK_COLUMN_WIDTH = 200;
|
|
7625
|
+
var SIZE_TO_ROW_HEIGHT = {
|
|
7626
|
+
xs: 36,
|
|
7627
|
+
s: 44,
|
|
7628
|
+
m: 52,
|
|
7629
|
+
l: 64,
|
|
7630
|
+
xl: 80
|
|
7631
|
+
};
|
|
7632
|
+
var SORTABLE_TYPES = new Set([
|
|
7633
|
+
"string",
|
|
7634
|
+
"number",
|
|
7635
|
+
"date"
|
|
7636
|
+
]);
|
|
7637
|
+
var VIEW_SIZE_TO_RENDERER_SIZE = {
|
|
7638
|
+
xs: "tiny",
|
|
7639
|
+
s: "small",
|
|
7640
|
+
m: "medium",
|
|
7641
|
+
l: "large",
|
|
7642
|
+
xl: "large"
|
|
7643
|
+
};
|
|
7644
|
+
/**
|
|
7645
|
+
* Headless table view that wraps {@link VirtualTable}.
|
|
7646
|
+
*
|
|
7647
|
+
* Converts property configurations into virtual table columns and wires up
|
|
7648
|
+
* sorting, pagination, and cell rendering without coupling to any data layer.
|
|
7649
|
+
*
|
|
7650
|
+
* @group Collection View
|
|
7651
|
+
*/
|
|
7652
|
+
function CollectionTableView({ dataController, properties, propertiesOrder, displayedColumnIds, onRowClick, cellRenderer, size = "m", emptyComponent, hoverRow = true, className, onColumnResize, onColumnsOrderChange }) {
|
|
7653
|
+
const columns = useMemo(() => {
|
|
7654
|
+
const order = propertiesOrder ?? Object.keys(properties);
|
|
7655
|
+
return (displayedColumnIds ? order.filter((key) => displayedColumnIds.includes(key)) : order.filter((key) => {
|
|
7656
|
+
const prop = properties[key];
|
|
7657
|
+
return prop && !prop.hideFromCollection;
|
|
7658
|
+
})).map((key) => {
|
|
7659
|
+
const prop = properties[key];
|
|
7660
|
+
if (!prop) return null;
|
|
7661
|
+
return {
|
|
7662
|
+
key,
|
|
7663
|
+
width: prop.columnWidth || DEFAULT_COLUMN_WIDTHS[prop.type] || FALLBACK_COLUMN_WIDTH,
|
|
7664
|
+
title: prop.name,
|
|
7665
|
+
sortable: SORTABLE_TYPES.has(prop.type),
|
|
7666
|
+
resizable: true
|
|
7667
|
+
};
|
|
7668
|
+
}).filter((col) => col !== null);
|
|
7669
|
+
}, [
|
|
7670
|
+
properties,
|
|
7671
|
+
propertiesOrder,
|
|
7672
|
+
displayedColumnIds
|
|
7673
|
+
]);
|
|
7674
|
+
const rowHeight = SIZE_TO_ROW_HEIGHT[size];
|
|
7675
|
+
const rendererSize = VIEW_SIZE_TO_RENDERER_SIZE[size];
|
|
7676
|
+
const tableCellRenderer = useCallback((params) => {
|
|
7677
|
+
if (!params.rowData) return null;
|
|
7678
|
+
const propertyConfig = properties[params.column.key];
|
|
7679
|
+
if (!propertyConfig) return null;
|
|
7680
|
+
const value = getValueInPath(params.rowData, params.column.key);
|
|
7681
|
+
const cellProps = {
|
|
7682
|
+
row: params.rowData,
|
|
7683
|
+
propertyKey: params.column.key,
|
|
7684
|
+
property: propertyConfig,
|
|
7685
|
+
value,
|
|
7686
|
+
size: rendererSize,
|
|
7687
|
+
width: params.width
|
|
7688
|
+
};
|
|
7689
|
+
if (cellRenderer) {
|
|
7690
|
+
const custom = cellRenderer(cellProps);
|
|
7691
|
+
if (custom !== void 0) return /* @__PURE__ */ jsx("div", {
|
|
7692
|
+
className: "flex items-center h-full px-1 overflow-hidden",
|
|
7693
|
+
children: custom
|
|
7694
|
+
});
|
|
7695
|
+
}
|
|
7696
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7697
|
+
className: "flex items-center h-full px-1 overflow-hidden",
|
|
7698
|
+
children: /* @__PURE__ */ jsx(DefaultCellRenderer, { ...cellProps })
|
|
7699
|
+
});
|
|
7700
|
+
}, [
|
|
7701
|
+
properties,
|
|
7702
|
+
cellRenderer,
|
|
7703
|
+
rendererSize
|
|
7704
|
+
]);
|
|
7705
|
+
const handleRowClick = useCallback(({ rowData }) => {
|
|
7706
|
+
onRowClick?.(rowData);
|
|
7707
|
+
}, [onRowClick]);
|
|
7708
|
+
const handleEndReached = useCallback(() => {
|
|
7709
|
+
if (dataController.paginationEnabled && !dataController.noMoreToLoad) {
|
|
7710
|
+
const currentCount = dataController.itemCount ?? dataController.pageSize ?? 50;
|
|
7711
|
+
const pageSize = dataController.pageSize ?? 50;
|
|
7712
|
+
dataController.setItemCount?.(currentCount + pageSize);
|
|
7713
|
+
}
|
|
7714
|
+
}, [
|
|
7715
|
+
dataController.paginationEnabled,
|
|
7716
|
+
dataController.noMoreToLoad,
|
|
7717
|
+
dataController.itemCount,
|
|
7718
|
+
dataController.pageSize,
|
|
7719
|
+
dataController.setItemCount
|
|
7720
|
+
]);
|
|
7721
|
+
const handleColumnResize = useCallback((params) => {
|
|
7722
|
+
onColumnResize?.({
|
|
7723
|
+
key: params.key,
|
|
7724
|
+
width: params.width
|
|
7725
|
+
});
|
|
7726
|
+
}, [onColumnResize]);
|
|
7727
|
+
const handleColumnsOrderChange = useCallback((newColumns) => {
|
|
7728
|
+
onColumnsOrderChange?.(newColumns.map((col) => col.key));
|
|
7729
|
+
}, [onColumnsOrderChange]);
|
|
7730
|
+
return /* @__PURE__ */ jsx(VirtualTable, {
|
|
7731
|
+
data: dataController.data,
|
|
7732
|
+
columns,
|
|
7733
|
+
cellRenderer: tableCellRenderer,
|
|
7734
|
+
rowHeight,
|
|
7735
|
+
sortBy: dataController.sortBy,
|
|
7736
|
+
onSortByUpdate: dataController.setSortBy,
|
|
7737
|
+
onRowClick: handleRowClick,
|
|
7738
|
+
onEndReached: handleEndReached,
|
|
7739
|
+
onColumnResize: handleColumnResize,
|
|
7740
|
+
onColumnsOrderChange: handleColumnsOrderChange,
|
|
7741
|
+
loading: dataController.loading,
|
|
7742
|
+
emptyComponent,
|
|
7743
|
+
error: dataController.error,
|
|
7744
|
+
hoverRow,
|
|
7745
|
+
className: cls("h-full", className),
|
|
7746
|
+
onScroll: dataController.onScroll,
|
|
7747
|
+
initialScroll: dataController.initialScroll
|
|
7748
|
+
});
|
|
7749
|
+
}
|
|
7750
|
+
//#endregion
|
|
7751
|
+
//#region src/views/CollectionView/CollectionCardView.tsx
|
|
7752
|
+
/**
|
|
7753
|
+
* Headless card grid view that wraps {@link CardView}.
|
|
7754
|
+
*
|
|
7755
|
+
* Renders each item as a card with an optional image, title,
|
|
7756
|
+
* preview properties, and selection support.
|
|
7757
|
+
*
|
|
7758
|
+
* @group Collection View
|
|
7759
|
+
*/
|
|
7760
|
+
function CollectionCardView({ dataController, properties, propertiesOrder, idProperty = "id", titleProperty, onRowClick, cellRenderer, size = "m", selectionEnabled, selectionController, highlightedItems, emptyComponent, className }) {
|
|
7761
|
+
const order = useMemo(() => propertiesOrder ?? Object.keys(properties), [propertiesOrder, properties]);
|
|
7762
|
+
const titlePropertyKey = useMemo(() => {
|
|
7763
|
+
if (titleProperty) return titleProperty;
|
|
7764
|
+
for (const key of order) {
|
|
7765
|
+
const prop = properties[key];
|
|
7766
|
+
if (prop && prop.type === "string" && !prop.hideFromCollection && !prop.storage) return key;
|
|
7767
|
+
}
|
|
7768
|
+
return idProperty;
|
|
7769
|
+
}, [
|
|
7770
|
+
titleProperty,
|
|
7771
|
+
order,
|
|
7772
|
+
properties,
|
|
7773
|
+
idProperty
|
|
7774
|
+
]);
|
|
7775
|
+
const imageProperty = useMemo(() => {
|
|
7776
|
+
for (const key of order) {
|
|
7777
|
+
const prop = properties[key];
|
|
7778
|
+
if (prop && prop.type === "string" && prop.storage) return key;
|
|
7779
|
+
}
|
|
7780
|
+
}, [order, properties]);
|
|
7781
|
+
const previewProperties = useMemo(() => {
|
|
7782
|
+
const result = [];
|
|
7783
|
+
for (const key of order) {
|
|
7784
|
+
if (key === titlePropertyKey || key === imageProperty) continue;
|
|
7785
|
+
const prop = properties[key];
|
|
7786
|
+
if (prop && !prop.hideFromCollection) {
|
|
7787
|
+
result.push({
|
|
7788
|
+
key,
|
|
7789
|
+
property: prop
|
|
7790
|
+
});
|
|
7791
|
+
if (result.length >= 3) break;
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
return result;
|
|
7795
|
+
}, [
|
|
7796
|
+
order,
|
|
7797
|
+
properties,
|
|
7798
|
+
titlePropertyKey,
|
|
7799
|
+
imageProperty
|
|
7800
|
+
]);
|
|
7801
|
+
const selectedIds = useMemo(() => {
|
|
7802
|
+
if (!selectionController?.selectedItems) return void 0;
|
|
7803
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7804
|
+
for (const item of selectionController.selectedItems) {
|
|
7805
|
+
const id = getValueInPath(item, idProperty);
|
|
7806
|
+
if (id !== void 0 && id !== null) ids.add(id);
|
|
7807
|
+
}
|
|
7808
|
+
return ids;
|
|
7809
|
+
}, [selectionController?.selectedItems, idProperty]);
|
|
7810
|
+
const highlightedIds = useMemo(() => {
|
|
7811
|
+
if (!highlightedItems) return void 0;
|
|
7812
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7813
|
+
for (const item of highlightedItems) {
|
|
7814
|
+
const id = getValueInPath(item, idProperty);
|
|
7815
|
+
if (id !== void 0 && id !== null) ids.add(id);
|
|
7816
|
+
}
|
|
7817
|
+
return ids;
|
|
7818
|
+
}, [highlightedItems, idProperty]);
|
|
7819
|
+
const handleSelectionChange = useCallback((item, selected) => {
|
|
7820
|
+
if (!selectionController) return;
|
|
7821
|
+
selectionController.setSelectedItems((prev) => {
|
|
7822
|
+
if (selected) return [...prev, item];
|
|
7823
|
+
const itemId = getValueInPath(item, idProperty);
|
|
7824
|
+
return prev.filter((existing) => {
|
|
7825
|
+
return getValueInPath(existing, idProperty) !== itemId;
|
|
7826
|
+
});
|
|
7827
|
+
});
|
|
7828
|
+
}, [selectionController, idProperty]);
|
|
7829
|
+
const renderCard = useCallback((item, extra) => {
|
|
7830
|
+
const titleValue = getValueInPath(item, titlePropertyKey);
|
|
7831
|
+
const imageValue = imageProperty ? getValueInPath(item, imageProperty) : void 0;
|
|
7832
|
+
const hasImage = typeof imageValue === "string" && imageValue.length > 0;
|
|
7833
|
+
return /* @__PURE__ */ jsxs(Card, {
|
|
7834
|
+
onClick: (e) => {
|
|
7835
|
+
if (e) extra.onClick(e);
|
|
7836
|
+
},
|
|
7837
|
+
className: cls("overflow-hidden relative", extra.selected && "bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75", extra.highlighted && !extra.selected && "ring-1 ring-primary/50"),
|
|
7838
|
+
children: [
|
|
7839
|
+
imageProperty && (hasImage ? /* @__PURE__ */ jsx("img", {
|
|
7840
|
+
src: imageValue,
|
|
7841
|
+
alt: "",
|
|
7842
|
+
className: "w-full h-32 object-cover rounded-t-lg"
|
|
7843
|
+
}) : /* @__PURE__ */ jsx("div", { className: cls("h-24 rounded-t-lg", "bg-surface-accent-200/50 dark:bg-black/30") })),
|
|
7844
|
+
selectionEnabled && /* @__PURE__ */ jsx("div", {
|
|
7845
|
+
className: "absolute top-2 right-2",
|
|
7846
|
+
onClick: (e) => e.stopPropagation(),
|
|
7847
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
7848
|
+
checked: extra.selected,
|
|
7849
|
+
onCheckedChange: extra.onSelectionChange,
|
|
7850
|
+
size: "small"
|
|
7851
|
+
})
|
|
7852
|
+
}),
|
|
7853
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
7854
|
+
variant: "subtitle2",
|
|
7855
|
+
noWrap: true,
|
|
7856
|
+
className: "px-3 pt-2 pb-1",
|
|
7857
|
+
children: String(titleValue ?? "")
|
|
7858
|
+
}),
|
|
7859
|
+
previewProperties.map(({ key, property }) => {
|
|
7860
|
+
const value = getValueInPath(item, key);
|
|
7861
|
+
if (cellRenderer) {
|
|
7862
|
+
const custom = cellRenderer({
|
|
7863
|
+
row: item,
|
|
7864
|
+
propertyKey: key,
|
|
7865
|
+
property,
|
|
7866
|
+
value,
|
|
7867
|
+
size: "small"
|
|
7868
|
+
});
|
|
7869
|
+
if (custom !== void 0) return /* @__PURE__ */ jsx("div", {
|
|
7870
|
+
className: "px-3 py-0.5",
|
|
7871
|
+
children: custom
|
|
7872
|
+
}, key);
|
|
7873
|
+
}
|
|
7874
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7875
|
+
className: "px-3 py-0.5",
|
|
7876
|
+
children: /* @__PURE__ */ jsx(DefaultCellRenderer, {
|
|
7877
|
+
row: item,
|
|
7878
|
+
propertyKey: key,
|
|
7879
|
+
property,
|
|
7880
|
+
value,
|
|
7881
|
+
size: "small"
|
|
7882
|
+
})
|
|
7883
|
+
}, key);
|
|
7884
|
+
}),
|
|
7885
|
+
/* @__PURE__ */ jsx("div", { className: "pb-2" })
|
|
7886
|
+
]
|
|
7887
|
+
});
|
|
7888
|
+
}, [
|
|
7889
|
+
titlePropertyKey,
|
|
7890
|
+
imageProperty,
|
|
7891
|
+
previewProperties,
|
|
7892
|
+
cellRenderer,
|
|
7893
|
+
selectionEnabled
|
|
7894
|
+
]);
|
|
7895
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7896
|
+
className: cls("w-full h-full overflow-auto", className),
|
|
7897
|
+
children: /* @__PURE__ */ jsx(CardView, {
|
|
7898
|
+
data: dataController.data,
|
|
7899
|
+
dataLoading: dataController.loading,
|
|
7900
|
+
noMoreToLoad: dataController.noMoreToLoad,
|
|
7901
|
+
dataLoadingError: dataController.error,
|
|
7902
|
+
itemCount: dataController.itemCount,
|
|
7903
|
+
setItemCount: dataController.setItemCount,
|
|
7904
|
+
pageSize: dataController.pageSize,
|
|
7905
|
+
paginationEnabled: dataController.paginationEnabled,
|
|
7906
|
+
onItemClick: onRowClick,
|
|
7907
|
+
selectedIds,
|
|
7908
|
+
highlightedIds,
|
|
7909
|
+
selectionEnabled: !!selectionEnabled,
|
|
7910
|
+
onSelectionChange: handleSelectionChange,
|
|
7911
|
+
size,
|
|
7912
|
+
renderCard,
|
|
7913
|
+
emptyComponent
|
|
7914
|
+
})
|
|
7915
|
+
});
|
|
7916
|
+
}
|
|
7917
|
+
//#endregion
|
|
7918
|
+
//#region src/views/CollectionView/CollectionListView.tsx
|
|
7919
|
+
/**
|
|
7920
|
+
* Headless list view that wraps {@link ListView}.
|
|
7921
|
+
*
|
|
7922
|
+
* Each row displays a title (bold), subtitle with 1–2 secondary property values,
|
|
7923
|
+
* and optional selection support.
|
|
7924
|
+
*
|
|
7925
|
+
* @group Collection View
|
|
7926
|
+
*/
|
|
7927
|
+
function CollectionListView({ dataController, properties, propertiesOrder, idProperty = "id", titleProperty, onRowClick, cellRenderer, size = "m", selectionEnabled, selectionController, highlightedItems, emptyComponent, className }) {
|
|
7928
|
+
const order = useMemo(() => propertiesOrder ?? Object.keys(properties), [propertiesOrder, properties]);
|
|
7929
|
+
const titlePropertyKey = useMemo(() => {
|
|
7930
|
+
if (titleProperty) return titleProperty;
|
|
7931
|
+
for (const key of order) {
|
|
7932
|
+
const prop = properties[key];
|
|
7933
|
+
if (prop && prop.type === "string" && !prop.hideFromCollection && !prop.storage) return key;
|
|
7934
|
+
}
|
|
7935
|
+
return idProperty;
|
|
7936
|
+
}, [
|
|
7937
|
+
titleProperty,
|
|
7938
|
+
order,
|
|
7939
|
+
properties,
|
|
7940
|
+
idProperty
|
|
7941
|
+
]);
|
|
7942
|
+
const subtitleProperties = useMemo(() => {
|
|
7943
|
+
const result = [];
|
|
7944
|
+
for (const key of order) {
|
|
7945
|
+
if (key === titlePropertyKey) continue;
|
|
7946
|
+
const prop = properties[key];
|
|
7947
|
+
if (prop && !prop.hideFromCollection) {
|
|
7948
|
+
result.push({
|
|
7949
|
+
key,
|
|
7950
|
+
property: prop
|
|
7951
|
+
});
|
|
7952
|
+
if (result.length >= 2) break;
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
return result;
|
|
7956
|
+
}, [
|
|
7957
|
+
order,
|
|
7958
|
+
properties,
|
|
7959
|
+
titlePropertyKey
|
|
7960
|
+
]);
|
|
7961
|
+
const selectedIds = useMemo(() => {
|
|
7962
|
+
if (!selectionController?.selectedItems) return void 0;
|
|
7963
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7964
|
+
for (const item of selectionController.selectedItems) {
|
|
7965
|
+
const id = getValueInPath(item, idProperty);
|
|
7966
|
+
if (id !== void 0 && id !== null) ids.add(id);
|
|
7967
|
+
}
|
|
7968
|
+
return ids;
|
|
7969
|
+
}, [selectionController?.selectedItems, idProperty]);
|
|
7970
|
+
const highlightedIds = useMemo(() => {
|
|
7971
|
+
if (!highlightedItems) return void 0;
|
|
7972
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7973
|
+
for (const item of highlightedItems) {
|
|
7974
|
+
const id = getValueInPath(item, idProperty);
|
|
7975
|
+
if (id !== void 0 && id !== null) ids.add(id);
|
|
7976
|
+
}
|
|
7977
|
+
return ids;
|
|
7978
|
+
}, [highlightedItems, idProperty]);
|
|
7979
|
+
const handleSelectionChange = useCallback((item, selected) => {
|
|
7980
|
+
if (!selectionController) return;
|
|
7981
|
+
selectionController.setSelectedItems((prev) => {
|
|
7982
|
+
if (selected) return [...prev, item];
|
|
7983
|
+
const itemId = getValueInPath(item, idProperty);
|
|
7984
|
+
return prev.filter((existing) => {
|
|
7985
|
+
return getValueInPath(existing, idProperty) !== itemId;
|
|
7986
|
+
});
|
|
7987
|
+
});
|
|
7988
|
+
}, [selectionController, idProperty]);
|
|
7989
|
+
const renderRow = useCallback((params) => {
|
|
7990
|
+
const { item, style, className: rowClassName, selected, highlighted, onClick, onSelectionChange } = params;
|
|
7991
|
+
const titleValue = getValueInPath(item, titlePropertyKey);
|
|
7992
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7993
|
+
style,
|
|
7994
|
+
className: cls("flex items-center gap-3 px-4 cursor-pointer", "hover:bg-surface-50 dark:hover:bg-surface-700", "transition-colors duration-100", selected && "bg-primary-50 dark:bg-primary-900/20", highlighted && "bg-surface-100 dark:bg-surface-800", rowClassName),
|
|
7995
|
+
onClick,
|
|
7996
|
+
children: [selectionEnabled && /* @__PURE__ */ jsx("div", {
|
|
7997
|
+
className: "flex-shrink-0",
|
|
7998
|
+
onClick: (e) => e.stopPropagation(),
|
|
7999
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
8000
|
+
checked: selected,
|
|
8001
|
+
onCheckedChange: onSelectionChange,
|
|
8002
|
+
size: "small"
|
|
8003
|
+
})
|
|
8004
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
8005
|
+
className: "flex-1 min-w-0 py-1",
|
|
8006
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
8007
|
+
variant: "subtitle2",
|
|
8008
|
+
noWrap: true,
|
|
8009
|
+
children: String(titleValue ?? "")
|
|
8010
|
+
}), subtitleProperties.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
8011
|
+
className: "flex items-center gap-2",
|
|
8012
|
+
children: subtitleProperties.map(({ key, property }) => {
|
|
8013
|
+
const value = getValueInPath(item, key);
|
|
8014
|
+
if (cellRenderer) {
|
|
8015
|
+
const custom = cellRenderer({
|
|
8016
|
+
row: item,
|
|
8017
|
+
propertyKey: key,
|
|
8018
|
+
property,
|
|
8019
|
+
value,
|
|
8020
|
+
size: "tiny"
|
|
8021
|
+
});
|
|
8022
|
+
if (custom !== void 0) return /* @__PURE__ */ jsx(React.Fragment, { children: custom }, key);
|
|
8023
|
+
}
|
|
8024
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx(DefaultCellRenderer, {
|
|
8025
|
+
row: item,
|
|
8026
|
+
propertyKey: key,
|
|
8027
|
+
property,
|
|
8028
|
+
value,
|
|
8029
|
+
size: "tiny"
|
|
8030
|
+
}) }, key);
|
|
8031
|
+
})
|
|
8032
|
+
})]
|
|
8033
|
+
})]
|
|
8034
|
+
});
|
|
8035
|
+
}, [
|
|
8036
|
+
titlePropertyKey,
|
|
8037
|
+
subtitleProperties,
|
|
8038
|
+
cellRenderer,
|
|
8039
|
+
selectionEnabled
|
|
8040
|
+
]);
|
|
8041
|
+
return /* @__PURE__ */ jsx("div", {
|
|
8042
|
+
className: cls("w-full h-full overflow-auto", className),
|
|
8043
|
+
children: /* @__PURE__ */ jsx(ListView, {
|
|
8044
|
+
data: dataController.data,
|
|
8045
|
+
dataLoading: dataController.loading,
|
|
8046
|
+
noMoreToLoad: dataController.noMoreToLoad,
|
|
8047
|
+
dataLoadingError: dataController.error,
|
|
8048
|
+
itemCount: dataController.itemCount,
|
|
8049
|
+
setItemCount: dataController.setItemCount,
|
|
8050
|
+
pageSize: dataController.pageSize,
|
|
8051
|
+
paginationEnabled: dataController.paginationEnabled,
|
|
8052
|
+
onItemClick: onRowClick,
|
|
8053
|
+
selectedIds,
|
|
8054
|
+
highlightedIds,
|
|
8055
|
+
selectionEnabled: !!selectionEnabled,
|
|
8056
|
+
onSelectionChange: handleSelectionChange,
|
|
8057
|
+
size,
|
|
8058
|
+
renderRow,
|
|
8059
|
+
emptyComponent
|
|
8060
|
+
})
|
|
8061
|
+
});
|
|
8062
|
+
}
|
|
8063
|
+
//#endregion
|
|
8064
|
+
//#region src/views/CollectionView/CollectionKanbanView.tsx
|
|
8065
|
+
function getEnumLabel(enumConfig, key) {
|
|
8066
|
+
if (!enumConfig) return key;
|
|
8067
|
+
if (enumConfig instanceof Map) {
|
|
8068
|
+
const cfg = enumConfig.get(key);
|
|
8069
|
+
if (!cfg) return key;
|
|
8070
|
+
return typeof cfg === "string" ? cfg : cfg.label;
|
|
8071
|
+
}
|
|
8072
|
+
const cfg = enumConfig[key];
|
|
8073
|
+
if (!cfg) return key;
|
|
8074
|
+
return typeof cfg === "string" ? cfg : cfg.label;
|
|
8075
|
+
}
|
|
8076
|
+
/**
|
|
8077
|
+
* Headless kanban view that wraps {@link KanbanView}.
|
|
8078
|
+
*
|
|
8079
|
+
* Groups data by a kanban property's enum values into columns.
|
|
8080
|
+
* Each card displays a title and 1–2 preview properties.
|
|
8081
|
+
*
|
|
8082
|
+
* @group Collection View
|
|
8083
|
+
*/
|
|
8084
|
+
function CollectionKanbanView({ dataController, properties, propertiesOrder, idProperty = "id", titleProperty, kanbanProperty, onRowClick, cellRenderer, emptyComponent, onRowCreate, className }) {
|
|
8085
|
+
const order = useMemo(() => propertiesOrder ?? Object.keys(properties), [propertiesOrder, properties]);
|
|
8086
|
+
const columns = useMemo(() => {
|
|
8087
|
+
const kanbanConfig = properties[kanbanProperty];
|
|
8088
|
+
if (kanbanConfig?.enum) {
|
|
8089
|
+
if (kanbanConfig.enum instanceof Map) return Array.from(kanbanConfig.enum.keys()).map(String);
|
|
8090
|
+
return Object.keys(kanbanConfig.enum);
|
|
8091
|
+
}
|
|
8092
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8093
|
+
for (const row of dataController.data) {
|
|
8094
|
+
const val = getValueInPath(row, kanbanProperty);
|
|
8095
|
+
if (val !== null && val !== void 0) seen.add(String(val));
|
|
8096
|
+
}
|
|
8097
|
+
return Array.from(seen);
|
|
8098
|
+
}, [
|
|
8099
|
+
properties,
|
|
8100
|
+
kanbanProperty,
|
|
8101
|
+
dataController.data
|
|
8102
|
+
]);
|
|
8103
|
+
const columnLabels = useMemo(() => {
|
|
8104
|
+
const kanbanConfig = properties[kanbanProperty];
|
|
8105
|
+
const labels = {};
|
|
8106
|
+
for (const col of columns) labels[col] = getEnumLabel(kanbanConfig?.enum, col);
|
|
8107
|
+
return labels;
|
|
8108
|
+
}, [
|
|
8109
|
+
properties,
|
|
8110
|
+
kanbanProperty,
|
|
8111
|
+
columns
|
|
8112
|
+
]);
|
|
8113
|
+
const titlePropertyKey = useMemo(() => {
|
|
8114
|
+
if (titleProperty) return titleProperty;
|
|
8115
|
+
for (const key of order) {
|
|
8116
|
+
const prop = properties[key];
|
|
8117
|
+
if (prop && prop.type === "string" && !prop.hideFromCollection && !prop.storage) return key;
|
|
8118
|
+
}
|
|
8119
|
+
return idProperty;
|
|
8120
|
+
}, [
|
|
8121
|
+
titleProperty,
|
|
8122
|
+
order,
|
|
8123
|
+
properties,
|
|
8124
|
+
idProperty
|
|
8125
|
+
]);
|
|
8126
|
+
const previewProperties = useMemo(() => {
|
|
8127
|
+
const result = [];
|
|
8128
|
+
for (const key of order) {
|
|
8129
|
+
if (key === titlePropertyKey || key === kanbanProperty) continue;
|
|
8130
|
+
const prop = properties[key];
|
|
8131
|
+
if (prop && !prop.hideFromCollection) {
|
|
8132
|
+
result.push({
|
|
8133
|
+
key,
|
|
8134
|
+
property: prop
|
|
8135
|
+
});
|
|
8136
|
+
if (result.length >= 2) break;
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8139
|
+
return result;
|
|
8140
|
+
}, [
|
|
8141
|
+
order,
|
|
8142
|
+
properties,
|
|
8143
|
+
titlePropertyKey,
|
|
8144
|
+
kanbanProperty
|
|
8145
|
+
]);
|
|
8146
|
+
const boardItems = useMemo(() => {
|
|
8147
|
+
return dataController.data.map((row, index) => {
|
|
8148
|
+
const id = getValueInPath(row, idProperty);
|
|
8149
|
+
return {
|
|
8150
|
+
id: id !== null && id !== void 0 ? String(id) : String(index),
|
|
8151
|
+
data: row
|
|
8152
|
+
};
|
|
8153
|
+
});
|
|
8154
|
+
}, [dataController.data, idProperty]);
|
|
8155
|
+
const assignColumn = useCallback((item) => {
|
|
8156
|
+
const val = getValueInPath(item.data, kanbanProperty);
|
|
8157
|
+
const strVal = val !== null && val !== void 0 ? String(val) : "";
|
|
8158
|
+
if (columns.includes(strVal)) return strVal;
|
|
8159
|
+
return columns[0] ?? "";
|
|
8160
|
+
}, [kanbanProperty, columns]);
|
|
8161
|
+
const ItemComponent = useMemo(() => {
|
|
8162
|
+
function KanbanCard(props) {
|
|
8163
|
+
const { item } = props;
|
|
8164
|
+
const titleValue = getValueInPath(item.data, titlePropertyKey);
|
|
8165
|
+
return /* @__PURE__ */ jsxs(Card, {
|
|
8166
|
+
onClick: () => onRowClick?.(item.data),
|
|
8167
|
+
className: cls("p-3", cardClickableMixin, props.isDragging && "bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75", props.isDragging && "shadow-lg opacity-75"),
|
|
8168
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
8169
|
+
variant: "subtitle2",
|
|
8170
|
+
noWrap: true,
|
|
8171
|
+
children: String(titleValue ?? "")
|
|
8172
|
+
}), previewProperties.map(({ key, property }) => {
|
|
8173
|
+
const value = getValueInPath(item.data, key);
|
|
8174
|
+
if (cellRenderer) {
|
|
8175
|
+
const custom = cellRenderer({
|
|
8176
|
+
row: item.data,
|
|
8177
|
+
propertyKey: key,
|
|
8178
|
+
property,
|
|
8179
|
+
value,
|
|
8180
|
+
size: "tiny"
|
|
8181
|
+
});
|
|
8182
|
+
if (custom !== void 0) return /* @__PURE__ */ jsx("div", {
|
|
8183
|
+
className: "mt-1",
|
|
8184
|
+
children: custom
|
|
8185
|
+
}, key);
|
|
8186
|
+
}
|
|
8187
|
+
return /* @__PURE__ */ jsx("div", {
|
|
8188
|
+
className: "mt-1",
|
|
8189
|
+
children: /* @__PURE__ */ jsx(DefaultCellRenderer, {
|
|
8190
|
+
row: item.data,
|
|
8191
|
+
propertyKey: key,
|
|
8192
|
+
property,
|
|
8193
|
+
value,
|
|
8194
|
+
size: "tiny"
|
|
8195
|
+
})
|
|
8196
|
+
}, key);
|
|
8197
|
+
})]
|
|
8198
|
+
});
|
|
8199
|
+
}
|
|
8200
|
+
return KanbanCard;
|
|
8201
|
+
}, [
|
|
8202
|
+
titlePropertyKey,
|
|
8203
|
+
previewProperties,
|
|
8204
|
+
cellRenderer,
|
|
8205
|
+
onRowClick
|
|
8206
|
+
]);
|
|
8207
|
+
const handleAddItemToColumn = useCallback((column) => {
|
|
8208
|
+
onRowCreate?.({ [kanbanProperty]: column });
|
|
8209
|
+
}, [onRowCreate, kanbanProperty]);
|
|
8210
|
+
if (columns.length === 0 && !dataController.loading) return /* @__PURE__ */ jsx("div", {
|
|
8211
|
+
className: "flex items-center justify-center p-8",
|
|
8212
|
+
children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
|
|
8213
|
+
variant: "body2",
|
|
8214
|
+
color: "secondary",
|
|
8215
|
+
children: "No columns available for kanban view."
|
|
8216
|
+
})
|
|
8217
|
+
});
|
|
8218
|
+
return /* @__PURE__ */ jsx(KanbanView, {
|
|
8219
|
+
columns,
|
|
8220
|
+
columnLabels,
|
|
8221
|
+
data: boardItems,
|
|
8222
|
+
assignColumn,
|
|
8223
|
+
ItemComponent,
|
|
8224
|
+
onAddItemToColumn: onRowCreate ? handleAddItemToColumn : void 0,
|
|
8225
|
+
className
|
|
8226
|
+
});
|
|
8227
|
+
}
|
|
8228
|
+
//#endregion
|
|
8229
|
+
//#region src/views/CollectionView/CollectionView.tsx
|
|
8230
|
+
var DEFAULT_ENABLED_VIEWS = [
|
|
8231
|
+
"list",
|
|
8232
|
+
"table",
|
|
8233
|
+
"cards",
|
|
8234
|
+
"kanban"
|
|
8235
|
+
];
|
|
8236
|
+
/**
|
|
8237
|
+
* A data-agnostic collection view component that orchestrates table, card,
|
|
8238
|
+
* list, and kanban views — driven entirely by props.
|
|
8239
|
+
*
|
|
8240
|
+
* Zero imports from any entity or data layer. This component lives in
|
|
8241
|
+
* `@rebasepro/ui` and can be used with any data source.
|
|
8242
|
+
*
|
|
8243
|
+
* @example
|
|
8244
|
+
* ```tsx
|
|
8245
|
+
* import { CollectionView } from "@rebasepro/ui";
|
|
8246
|
+
*
|
|
8247
|
+
* <CollectionView
|
|
8248
|
+
* dataController={{ data: myRows, loading: false, noMoreToLoad: true }}
|
|
8249
|
+
* properties={{
|
|
8250
|
+
* name: { type: "string", name: "Name" },
|
|
8251
|
+
* email: { type: "string", name: "Email" },
|
|
8252
|
+
* }}
|
|
8253
|
+
* title="Contacts"
|
|
8254
|
+
* onRowClick={(row) => openDetail(row.id)}
|
|
8255
|
+
* />
|
|
8256
|
+
* ```
|
|
8257
|
+
*
|
|
8258
|
+
* @group Collection View
|
|
8259
|
+
*/
|
|
8260
|
+
function CollectionView({ dataController, properties, propertiesOrder, displayedColumnIds, idProperty = "id", titleProperty, title, viewMode: viewModeProp, defaultViewMode = "list", enabledViews = DEFAULT_ENABLED_VIEWS, onViewModeChange: onViewModeChangeProp, size: sizeProp, defaultSize = "m", onSizeChange: onSizeChangeProp, kanbanProperty: kanbanPropertyProp, kanbanPropertyOptions, onKanbanPropertyChange: onKanbanPropertyChangeProp, onRowClick, onRowCreate, canCreate = true, selectionEnabled, selectionController, highlightedItems, onMultipleDelete, cellRenderer, emptyComponent, toolbarActionsStart, toolbarActionsEnd, hideToolbar, hoverRow = true, onColumnResize, onColumnsOrderChange, className }) {
|
|
8261
|
+
const [internalViewMode, setInternalViewMode] = useState(defaultViewMode);
|
|
8262
|
+
const viewMode = viewModeProp ?? internalViewMode;
|
|
8263
|
+
const onViewModeChange = useCallback((mode) => {
|
|
8264
|
+
setInternalViewMode(mode);
|
|
8265
|
+
onViewModeChangeProp?.(mode);
|
|
8266
|
+
}, [onViewModeChangeProp]);
|
|
8267
|
+
const [internalSize, setInternalSize] = useState(defaultSize);
|
|
8268
|
+
const size = sizeProp ?? internalSize;
|
|
8269
|
+
const onSizeChange = useCallback((s) => {
|
|
8270
|
+
setInternalSize(s);
|
|
8271
|
+
onSizeChangeProp?.(s);
|
|
8272
|
+
}, [onSizeChangeProp]);
|
|
8273
|
+
const [internalKanbanProperty, setInternalKanbanProperty] = useState(kanbanPropertyProp);
|
|
8274
|
+
const kanbanProperty = kanbanPropertyProp ?? internalKanbanProperty;
|
|
8275
|
+
const onKanbanPropertyChange = useCallback((property) => {
|
|
8276
|
+
setInternalKanbanProperty(property);
|
|
8277
|
+
onKanbanPropertyChangeProp?.(property);
|
|
8278
|
+
}, [onKanbanPropertyChangeProp]);
|
|
8279
|
+
const resolvedKanbanProperty = useMemo(() => {
|
|
8280
|
+
if (kanbanProperty) return kanbanProperty;
|
|
8281
|
+
const order = propertiesOrder ?? Object.keys(properties);
|
|
8282
|
+
for (const key of order) {
|
|
8283
|
+
const prop = properties[key];
|
|
8284
|
+
if (prop && prop.type === "string" && prop.enum) return key;
|
|
8285
|
+
}
|
|
8286
|
+
}, [
|
|
8287
|
+
kanbanProperty,
|
|
8288
|
+
properties,
|
|
8289
|
+
propertiesOrder
|
|
8290
|
+
]);
|
|
8291
|
+
const resolvedKanbanPropertyOptions = useMemo(() => {
|
|
8292
|
+
if (kanbanPropertyOptions) return kanbanPropertyOptions;
|
|
8293
|
+
const options = [];
|
|
8294
|
+
const order = propertiesOrder ?? Object.keys(properties);
|
|
8295
|
+
for (const key of order) {
|
|
8296
|
+
const prop = properties[key];
|
|
8297
|
+
if (prop && prop.type === "string" && prop.enum) options.push({
|
|
8298
|
+
key,
|
|
8299
|
+
label: prop.name
|
|
8300
|
+
});
|
|
8301
|
+
}
|
|
8302
|
+
return options;
|
|
8303
|
+
}, [
|
|
8304
|
+
kanbanPropertyOptions,
|
|
8305
|
+
properties,
|
|
8306
|
+
propertiesOrder
|
|
8307
|
+
]);
|
|
8308
|
+
const sharedProps = {
|
|
8309
|
+
dataController,
|
|
8310
|
+
properties,
|
|
8311
|
+
propertiesOrder,
|
|
8312
|
+
idProperty,
|
|
8313
|
+
titleProperty,
|
|
8314
|
+
onRowClick,
|
|
8315
|
+
cellRenderer,
|
|
8316
|
+
selectionEnabled,
|
|
8317
|
+
selectionController,
|
|
8318
|
+
highlightedItems,
|
|
8319
|
+
emptyComponent
|
|
8320
|
+
};
|
|
8321
|
+
function renderView() {
|
|
8322
|
+
switch (viewMode) {
|
|
8323
|
+
case "table": return /* @__PURE__ */ jsx(CollectionTableView, {
|
|
8324
|
+
...sharedProps,
|
|
8325
|
+
displayedColumnIds,
|
|
8326
|
+
size,
|
|
8327
|
+
hoverRow,
|
|
8328
|
+
onColumnResize,
|
|
8329
|
+
onColumnsOrderChange
|
|
8330
|
+
});
|
|
8331
|
+
case "cards": return /* @__PURE__ */ jsx(CollectionCardView, {
|
|
8332
|
+
...sharedProps,
|
|
8333
|
+
size
|
|
8334
|
+
});
|
|
8335
|
+
case "kanban": return resolvedKanbanProperty ? /* @__PURE__ */ jsx(CollectionKanbanView, {
|
|
8336
|
+
...sharedProps,
|
|
8337
|
+
kanbanProperty: resolvedKanbanProperty,
|
|
8338
|
+
onRowCreate
|
|
8339
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
8340
|
+
className: "flex items-center justify-center p-8",
|
|
8341
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
8342
|
+
variant: "body2",
|
|
8343
|
+
color: "secondary",
|
|
8344
|
+
children: "No enum property available for kanban grouping."
|
|
8345
|
+
})
|
|
8346
|
+
});
|
|
8347
|
+
default: return /* @__PURE__ */ jsx(CollectionListView, {
|
|
8348
|
+
...sharedProps,
|
|
8349
|
+
size
|
|
8350
|
+
});
|
|
8351
|
+
}
|
|
8352
|
+
}
|
|
8353
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8354
|
+
className: cls("overflow-hidden h-full w-full rounded-md flex flex-col", "dark:bg-surface-800", className),
|
|
8355
|
+
children: [
|
|
8356
|
+
dataController.error && dataController.data.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
8357
|
+
className: "flex items-center gap-4 px-4 py-2 bg-red-50 dark:bg-red-900/20 border-b border-red-200 dark:border-red-800 flex-shrink-0",
|
|
8358
|
+
children: /* @__PURE__ */ jsxs(Typography, {
|
|
8359
|
+
variant: "body2",
|
|
8360
|
+
className: "text-red-700 dark:text-red-300 flex-1",
|
|
8361
|
+
children: [
|
|
8362
|
+
/* @__PURE__ */ jsx("strong", { children: "Warning:" }),
|
|
8363
|
+
" ",
|
|
8364
|
+
dataController.error.message || "Failed to update data."
|
|
8365
|
+
]
|
|
8366
|
+
})
|
|
8367
|
+
}),
|
|
8368
|
+
!hideToolbar && /* @__PURE__ */ jsx(CollectionViewToolbar, {
|
|
8369
|
+
viewMode,
|
|
8370
|
+
onViewModeChange: enabledViews.length > 1 ? onViewModeChange : void 0,
|
|
8371
|
+
enabledViews,
|
|
8372
|
+
size,
|
|
8373
|
+
onSizeChange,
|
|
8374
|
+
searchString: dataController.searchString,
|
|
8375
|
+
onSearchChange: dataController.setSearchString,
|
|
8376
|
+
loading: dataController.loading,
|
|
8377
|
+
actionsStart: toolbarActionsStart,
|
|
8378
|
+
actionsEnd: toolbarActionsEnd,
|
|
8379
|
+
kanbanPropertyOptions: viewMode === "kanban" ? resolvedKanbanPropertyOptions : void 0,
|
|
8380
|
+
selectedKanbanProperty: resolvedKanbanProperty,
|
|
8381
|
+
onKanbanPropertyChange
|
|
8382
|
+
}),
|
|
8383
|
+
/* @__PURE__ */ jsx("div", {
|
|
8384
|
+
className: "flex-1 overflow-hidden",
|
|
8385
|
+
children: renderView()
|
|
8386
|
+
})
|
|
8387
|
+
]
|
|
8388
|
+
});
|
|
8389
|
+
}
|
|
8390
|
+
//#endregion
|
|
8391
|
+
export { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightToLineIcon, ArrowUpToLineIcon, Autocomplete, AutocompleteItem, Avatar, Badge, BoldIcon, BookOpenIcon, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircleUserIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, CollectionCardView, CollectionKanbanView, CollectionListView, CollectionTableView, CollectionView, CollectionViewToolbar, ColorPicker, ColumnsIcon, Container, CopyIcon, DatabaseIcon, DateTimeField, DebouncedTextField, DefaultCellRenderer, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HelpCircleIcon, HistoryIcon, HomeIcon, IconButton, ImageIcon, ImageOffIcon, InfoIcon, InfoLabel, InputLabel, ItalicIcon, KanbanIcon, KanbanView, KeyIcon, KeyRoundIcon, Label, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoaderIcon, LoadingButton, LockIcon, LogOutIcon, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarItemIndicator, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarSubTriggerIndicator, MenubarTrigger, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectContext, MultiSelectItem, Music2Icon, PanelLeftIcon, Paper, PauseIcon, PencilIcon, PhoneIcon, PlayIcon, PlusIcon, Popover, PopoverPrimitive, Portal, PortalContainerProvider, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SaveIcon, SearchBar, SearchIcon, Select, SelectGroup, SelectInputLabel, SelectItem, SendIcon, Separator, SettingsIcon, Sheet, ShieldIcon, ShoppingCartIcon, Skeleton, Slider, SlidersHorizontalIcon, Slot, SquareIcon, StarIcon, StrikethroughIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, VirtualTable as TableView, VirtualTable, Tabs, TagIcon, TerminalIcon, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, TypeIcon, Typography, UnderlineIcon, UndoIcon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, VideoIcon, VirtualTableDateField, VirtualTableInput, VirtualTableNumberInput, VirtualTableSelect, VirtualTableSelectionProvider, VirtualTableSwitch, VoteIcon, Wand2Icon, XCircleIcon, XIcon, cardClickableMixin, cardMixin, cardSelectedMixin, cls, colorClassesMapping, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedClasses, focusedDisabled, focusedInvisibleMixin, getColorSchemeForKey, getColorSchemeForSeed, iconKeys, iconSize, lucideIcons, paperMixin, useAutoComplete, useDebounceCallback, useDebounceValue, useDebouncedCallback, useInjectStyles, useOutsideAlerter, usePortalContainer, useVirtualTableCellSelected, useVirtualTableSelection };
|
|
6093
8392
|
|
|
6094
8393
|
//# sourceMappingURL=index.es.js.map
|