@rebasepro/ui 0.17.3 → 0.18.1

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.
Files changed (127) hide show
  1. package/README.md +4 -0
  2. package/dist/components/MultiSelect.d.ts +10 -0
  3. package/dist/index.es.js +2 -1
  4. package/dist/index.es.js.map +1 -1
  5. package/package.json +32 -23
  6. package/src/components/Alert.tsx +0 -118
  7. package/src/components/Autocomplete.tsx +0 -93
  8. package/src/components/Avatar.tsx +0 -73
  9. package/src/components/Badge.tsx +0 -44
  10. package/src/components/BooleanSwitch.tsx +0 -116
  11. package/src/components/BooleanSwitchWithLabel.tsx +0 -120
  12. package/src/components/Button.tsx +0 -110
  13. package/src/components/Card.tsx +0 -43
  14. package/src/components/CenteredView.tsx +0 -35
  15. package/src/components/Checkbox.tsx +0 -129
  16. package/src/components/Chip.tsx +0 -131
  17. package/src/components/CircularProgress.tsx +0 -53
  18. package/src/components/CircularProgressCenter.tsx +0 -27
  19. package/src/components/Collapse.tsx +0 -66
  20. package/src/components/ColorPicker.tsx +0 -140
  21. package/src/components/Container.tsx +0 -51
  22. package/src/components/DateTimeField.tsx +0 -363
  23. package/src/components/DebouncedTextField.tsx +0 -76
  24. package/src/components/Dialog.tsx +0 -159
  25. package/src/components/DialogActions.tsx +0 -29
  26. package/src/components/DialogContent.tsx +0 -27
  27. package/src/components/DialogTitle.tsx +0 -41
  28. package/src/components/ErrorBoundary.tsx +0 -236
  29. package/src/components/ExpandablePanel.tsx +0 -130
  30. package/src/components/FileUpload.tsx +0 -103
  31. package/src/components/FilterChip.tsx +0 -89
  32. package/src/components/IconButton.tsx +0 -101
  33. package/src/components/InfoLabel.tsx +0 -23
  34. package/src/components/InputLabel.tsx +0 -40
  35. package/src/components/Label.tsx +0 -32
  36. package/src/components/LoadingButton.tsx +0 -32
  37. package/src/components/Markdown.tsx +0 -55
  38. package/src/components/Menu.tsx +0 -114
  39. package/src/components/Menubar.tsx +0 -355
  40. package/src/components/MultiSelect.tsx +0 -433
  41. package/src/components/Paper.tsx +0 -23
  42. package/src/components/Popover.tsx +0 -162
  43. package/src/components/RadioGroup.tsx +0 -74
  44. package/src/components/ResizablePanels.tsx +0 -189
  45. package/src/components/SearchBar.tsx +0 -136
  46. package/src/components/Select.tsx +0 -326
  47. package/src/components/Separator.tsx +0 -28
  48. package/src/components/Sheet.tsx +0 -135
  49. package/src/components/Skeleton.tsx +0 -54
  50. package/src/components/Slider.tsx +0 -110
  51. package/src/components/Table.tsx +0 -178
  52. package/src/components/Tabs.tsx +0 -185
  53. package/src/components/TextField.tsx +0 -270
  54. package/src/components/TextareaAutosize.tsx +0 -368
  55. package/src/components/ToggleButtonGroup.tsx +0 -69
  56. package/src/components/Tooltip.tsx +0 -153
  57. package/src/components/Typography.tsx +0 -132
  58. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
  59. package/src/components/VirtualTable/VirtualTable.tsx +0 -700
  60. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
  61. package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
  62. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
  63. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
  64. package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
  65. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
  66. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
  67. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
  68. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
  69. package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
  70. package/src/components/VirtualTable/index.tsx +0 -10
  71. package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
  72. package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
  73. package/src/components/VirtualTable/types.tsx +0 -47
  74. package/src/components/common/SelectInputLabel.tsx +0 -23
  75. package/src/components/index.tsx +0 -62
  76. package/src/hooks/PortalContainerContext.tsx +0 -50
  77. package/src/hooks/index.ts +0 -7
  78. package/src/hooks/useDebounceCallback.tsx +0 -21
  79. package/src/hooks/useDebounceValue.tsx +0 -20
  80. package/src/hooks/useDebouncedCallback.ts +0 -39
  81. package/src/hooks/useInjectStyles.tsx +0 -31
  82. package/src/hooks/useOutsideAlerter.tsx +0 -46
  83. package/src/icons/GitHubIcon.tsx +0 -21
  84. package/src/icons/HandleIcon.tsx +0 -23
  85. package/src/icons/Icon.tsx +0 -33
  86. package/src/icons/LucideIconByName.tsx +0 -117
  87. package/src/icons/cool_icon_keys.ts +0 -12
  88. package/src/icons/icon_keys.ts +0 -1936
  89. package/src/icons/index.ts +0 -179
  90. package/src/index.css +0 -169
  91. package/src/index.ts +0 -47
  92. package/src/scripts/fixIconSizes.cjs +0 -92
  93. package/src/scripts/fixIconSizesToConst.cjs +0 -106
  94. package/src/scripts/generateIconKeys.ts +0 -41
  95. package/src/scripts/generateIcons.ts +0 -4
  96. package/src/scripts/saveIconFiles.ts +0 -89
  97. package/src/styles.ts +0 -141
  98. package/src/theme.css +0 -168
  99. package/src/util/chip_colors.ts +0 -242
  100. package/src/util/cls.ts +0 -8
  101. package/src/util/debounce.ts +0 -28
  102. package/src/util/hash.ts +0 -11
  103. package/src/util/index.ts +0 -4
  104. package/src/util/key_to_icon_component.ts +0 -11
  105. package/src/util/lazy_chunk.ts +0 -101
  106. package/src/views/CardView.tsx +0 -281
  107. package/src/views/CollectionView/CollectionCardView.tsx +0 -265
  108. package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
  109. package/src/views/CollectionView/CollectionListView.tsx +0 -240
  110. package/src/views/CollectionView/CollectionTableView.tsx +0 -229
  111. package/src/views/CollectionView/CollectionView.tsx +0 -450
  112. package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
  113. package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
  114. package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
  115. package/src/views/CollectionView/index.ts +0 -39
  116. package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
  117. package/src/views/CollectionView/utils.ts +0 -14
  118. package/src/views/Kanban/Board.tsx +0 -384
  119. package/src/views/Kanban/BoardColumn.tsx +0 -138
  120. package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
  121. package/src/views/Kanban/BoardSortableList.tsx +0 -182
  122. package/src/views/Kanban/board_types.ts +0 -80
  123. package/src/views/Kanban/index.tsx +0 -5
  124. package/src/views/Kanban/placement.ts +0 -62
  125. package/src/views/KanbanView.tsx +0 -32
  126. package/src/views/ListView.tsx +0 -296
  127. package/src/views/index.ts +0 -5
@@ -1,182 +0,0 @@
1
- import React, { memo, useEffect, useMemo, useRef } from "react";
2
- import { useDroppable } from "@dnd-kit/core";
3
- import { useSortable, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
4
- import { CSS } from "@dnd-kit/utilities";
5
- import { CircularProgress } from "../../components";
6
- import { cls } from "../../util";
7
- import { BoardItem, BoardItemViewProps } from "./board_types";
8
-
9
- interface BoardSortableListProps<T> {
10
- columnId: string;
11
- items: BoardItem<T>[];
12
- ItemComponent: React.ComponentType<BoardItemViewProps<T>>;
13
- isDragging: boolean;
14
- isDragOverColumn: boolean;
15
- loading?: boolean;
16
- hasMore?: boolean;
17
- error?: Error;
18
- onLoadMore?: () => void;
19
- }
20
-
21
- export function BoardSortableList<T>({
22
- columnId,
23
- items,
24
- ItemComponent,
25
- isDragging,
26
- isDragOverColumn,
27
- loading = false,
28
- hasMore = false,
29
- error,
30
- onLoadMore
31
- }: BoardSortableListProps<T>) {
32
- const {
33
- setNodeRef
34
- } = useDroppable({
35
- id: columnId,
36
- data: { type: "ITEM-LIST" }
37
- });
38
-
39
- const sentinelRef = useRef<HTMLDivElement>(null);
40
- const isLoadingRef = useRef(loading);
41
- isLoadingRef.current = loading;
42
- const lastLoadTimeRef = useRef(0);
43
-
44
- useEffect(() => {
45
- if (!sentinelRef.current || !hasMore || !onLoadMore) return;
46
-
47
- const sentinel = sentinelRef.current;
48
-
49
- const observer = new IntersectionObserver(
50
- (entries) => {
51
- const now = Date.now();
52
- if (
53
- entries[0].isIntersecting &&
54
- hasMore &&
55
- !isLoadingRef.current &&
56
- now - lastLoadTimeRef.current > 500
57
- ) {
58
- lastLoadTimeRef.current = now;
59
- onLoadMore();
60
- }
61
- },
62
- { threshold: 0.1 }
63
- );
64
-
65
- observer.observe(sentinel);
66
-
67
- const rect = sentinel.getBoundingClientRect();
68
- const containerRect = sentinel.parentElement?.getBoundingClientRect();
69
- if (containerRect && rect.top < containerRect.bottom && rect.bottom > containerRect.top) {
70
- const now = Date.now();
71
- if (hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
72
- lastLoadTimeRef.current = now;
73
- onLoadMore();
74
- }
75
- }
76
-
77
- return () => observer.disconnect();
78
- }, [hasMore, onLoadMore]);
79
-
80
- const containerClassName = useMemo(() => cls(
81
- // The scrollbar stays: this is the only thing on the page that scrolls
82
- // vertically, and hiding it left a column of cards with no sign that
83
- // there were more below the fold.
84
- "flex flex-col p-2 transition-opacity duration-100 transition-bg ease-linear w-full overflow-y-auto flex-1 min-h-0 rounded-md",
85
- isDragging && isDragOverColumn
86
- ? "bg-surface-accent-200 dark:bg-surface-900"
87
- : isDragging
88
- ? "bg-surface-50 dark:bg-surface-900 hover:bg-surface-accent-100 dark:hover:bg-surface-800"
89
- : "bg-surface-50 dark:bg-surface-900"
90
- ), [isDragging, isDragOverColumn]);
91
-
92
- return (
93
- <div
94
- ref={setNodeRef}
95
- className={containerClassName}
96
- style={{ minHeight: 80 }}
97
- >
98
- <SortableContext
99
- items={items.map(i => i.id)}
100
- strategy={verticalListSortingStrategy}
101
- >
102
- {items.length === 0 && !loading ? (
103
- <div className="flex-1 flex items-center justify-center px-3 text-center">
104
- {/* "No items" over a header counting eleven of them is
105
- the wrong thing to say when the read failed. */}
106
- <span className={cls(
107
- "text-xs",
108
- error
109
- ? "text-red-600 dark:text-red-400"
110
- : "text-surface-400 dark:text-surface-500"
111
- )}>
112
- {error ? "Could not load this column" : "No items"}
113
- </span>
114
- </div>
115
- ) : (
116
- <>
117
- {items.map((item, index) => (
118
- <SortableItem
119
- key={item.id}
120
- item={item}
121
- index={index}
122
- columnId={columnId}
123
- ItemComponent={ItemComponent}
124
- />
125
- ))}
126
- {(loading || hasMore) && (
127
- <div ref={sentinelRef} className="flex items-center justify-center py-2 min-h-6">
128
- {loading && <CircularProgress size="smallest"/>}
129
- </div>
130
- )}
131
- </>
132
- )}
133
- </SortableContext>
134
- </div>
135
- );
136
- }
137
-
138
- interface SortableItemProps<T> {
139
- item: BoardItem<T>;
140
- index: number;
141
- columnId: string;
142
- ItemComponent: React.ComponentType<BoardItemViewProps<T>>;
143
- }
144
-
145
- const SortableItem = memo(function SortableItem<T>({
146
- item,
147
- index,
148
- columnId,
149
- ItemComponent
150
- }: SortableItemProps<T>) {
151
- const {
152
- setNodeRef,
153
- attributes,
154
- listeners,
155
- isDragging: isItemBeingDragged,
156
- transform,
157
- transition
158
- } = useSortable({
159
- id: item.id,
160
- data: {
161
- type: "ITEM",
162
- columnId
163
- }
164
- });
165
-
166
- const sortableStyle = useMemo(() => ({
167
- transform: CSS.Transform.toString(transform),
168
- transition,
169
- zIndex: isItemBeingDragged ? 2 : 1,
170
- opacity: isItemBeingDragged ? 0 : 1
171
- }), [transform, transition, isItemBeingDragged]);
172
-
173
- return (
174
- <div ref={setNodeRef} style={sortableStyle} {...attributes} {...listeners}>
175
- <ItemComponent
176
- item={item}
177
- isDragging={isItemBeingDragged}
178
- index={index}
179
- />
180
- </div>
181
- );
182
- }) as <T>(props: SortableItemProps<T>) => React.ReactElement;
@@ -1,80 +0,0 @@
1
- import { CSSProperties } from "react";
2
-
3
- export type ChipColorKey = "primary" | "secondary" | "success" | "warning" | "error" | "info" | "neutral" | string;
4
- export type ChipColorScheme = "filled" | "outlined" | "tinted" | string;
5
-
6
- /**
7
- * Item wrapper for elements in the Board component
8
- */
9
- export interface BoardItem<T = any> {
10
- id: string;
11
- data: T;
12
- }
13
-
14
- /**
15
- * Map of column keys to arrays of board items
16
- */
17
- export interface BoardItemMap<T = any> {
18
- [columnKey: string]: BoardItem<T>[];
19
- }
20
-
21
- /**
22
- * Props passed to custom item render components
23
- */
24
- export interface BoardItemViewProps<T = any> {
25
- item: BoardItem<T>;
26
- isDragging: boolean;
27
- isClone?: boolean;
28
- isGroupedOver?: boolean;
29
- style?: CSSProperties;
30
- index?: number;
31
- }
32
-
33
- /**
34
- * Per-column loading state
35
- */
36
- export interface ColumnLoadingState {
37
- [columnKey: string]: {
38
- loading: boolean;
39
- hasMore: boolean;
40
- itemCount: number;
41
- totalCount?: number;
42
- /** Set when the column could not be read at all. */
43
- error?: Error;
44
- };
45
- }
46
-
47
- /**
48
- * Props for the Board component
49
- */
50
- export interface BoardProps<T, COLUMN extends string> {
51
- data: BoardItem<T>[];
52
- columns: COLUMN[];
53
- columnLabels?: Record<COLUMN, string>;
54
- columnColors?: Record<COLUMN, any>;
55
- className?: string;
56
- assignColumn: (item: BoardItem<T>) => COLUMN;
57
- allowColumnReorder?: boolean;
58
- onColumnReorder?: (columns: COLUMN[]) => void;
59
- /**
60
- * A card was dropped.
61
- *
62
- * `items` is the target column's contents in their new order — the moved
63
- * card included — so the neighbours of the moved card are the cards it
64
- * actually landed between. `sourceColumn` is where the card was picked up,
65
- * which is not where it now sits whenever the two differ.
66
- */
67
- onItemsReorder?: (
68
- items: BoardItem<T>[],
69
- moveInfo?: {
70
- itemId: string;
71
- sourceColumn: COLUMN;
72
- targetColumn: COLUMN;
73
- }
74
- ) => void;
75
- ItemComponent: React.ComponentType<BoardItemViewProps<T>>;
76
- columnLoadingState?: ColumnLoadingState;
77
- onLoadMoreColumn?: (column: COLUMN) => void;
78
- onAddItemToColumn?: (column: COLUMN) => void;
79
- AddColumnComponent?: React.ReactNode;
80
- }
@@ -1,5 +0,0 @@
1
- export * from "./board_types";
2
- export * from "./BoardColumnTitle";
3
- export * from "./BoardSortableList";
4
- export * from "./BoardColumn";
5
- export * from "./Board";
@@ -1,62 +0,0 @@
1
- import { arrayMove } from "@dnd-kit/sortable";
2
- import { BoardItem } from "./board_types";
3
-
4
- export interface PlaceDroppedCardParams<T> {
5
- /**
6
- * The destination column as it stands at the moment of the drop. When the
7
- * card came from another column it is normally already in here:
8
- * `handleDragOver` moves it while the pointer is still down, which is what
9
- * opens the gap under the cursor.
10
- */
11
- targetItems: BoardItem<T>[];
12
- /** The card being dropped. */
13
- movedItem: BoardItem<T>;
14
- /** What the pointer was over — a card id, or the column's own droppable id. */
15
- overId: string;
16
- /** Whether the card was picked up in a different column than it landed in. */
17
- changedColumn: boolean;
18
- }
19
-
20
- /**
21
- * Where a dropped card ends up in its destination column.
22
- *
23
- * Pulled out of the drop handler because it is the part that was wrong twice:
24
- * once appending a card that had been dropped into the middle of a column, and
25
- * once refusing to place a card at all when it was released over an empty
26
- * column rather than over another card. It is pure, so it can be checked
27
- * without driving a pointer.
28
- */
29
- export function placeDroppedCard<T>({
30
- targetItems,
31
- movedItem,
32
- overId,
33
- changedColumn
34
- }: PlaceDroppedCardParams<T>): BoardItem<T>[] {
35
-
36
- const indexInTarget = targetItems.findIndex(i => i.id === movedItem.id);
37
- const overIndex = targetItems.findIndex(i => i.id === overId);
38
-
39
- // Not placed during the drag — drop straight into the slot. `-1` means the
40
- // pointer was over the column itself (an empty one, or the space below the
41
- // last card), which appends.
42
- if (indexInTarget === -1) {
43
- const placed = [...targetItems];
44
- placed.splice(overIndex === -1 ? targetItems.length : overIndex, 0, movedItem);
45
- return placed;
46
- }
47
-
48
- // Released over another card: that card's slot is the one the user chose.
49
- if (overIndex !== -1) {
50
- return arrayMove(targetItems, indexInTarget, overIndex);
51
- }
52
-
53
- // Released over the column rather than a card. The card is already sitting
54
- // in the gap the drag opened, so its position is the one on screen —
55
- // appending here is what used to send it to the bottom.
56
- if (changedColumn) {
57
- return targetItems;
58
- }
59
-
60
- // Same column, released below the last card.
61
- return arrayMove(targetItems, indexInTarget, targetItems.length - 1);
62
- }
@@ -1,32 +0,0 @@
1
- import React from "react";
2
- import { Board } from "./Kanban/Board";
3
- import { BoardItem, BoardItemViewProps, ColumnLoadingState } from "./Kanban/board_types";
4
-
5
- export type KanbanViewProps<T, COLUMN extends string> = {
6
- columns: COLUMN[];
7
- columnLabels?: Record<COLUMN, string>;
8
- columnColors?: Record<COLUMN, any>;
9
- className?: string;
10
- assignColumn: (item: BoardItem<T>) => COLUMN;
11
- allowColumnReorder?: boolean;
12
- onColumnReorder?: (columns: COLUMN[]) => void;
13
- onItemsReorder?: (
14
- items: BoardItem<T>[],
15
- moveInfo?: {
16
- itemId: string;
17
- sourceColumn: COLUMN;
18
- targetColumn: COLUMN;
19
- }
20
- ) => void;
21
- ItemComponent: React.ComponentType<BoardItemViewProps<T>>;
22
- columnLoadingState?: ColumnLoadingState;
23
- onLoadMoreColumn?: (column: COLUMN) => void;
24
- onAddItemToColumn?: (column: COLUMN) => void;
25
- AddColumnComponent?: React.ReactNode;
26
- data: BoardItem<T>[];
27
- };
28
-
29
- export function KanbanView<T, COLUMN extends string>(props: KanbanViewProps<T, COLUMN>) {
30
- return <Board {...props} />;
31
- }
32
- export type { BoardItem, BoardItemViewProps, ColumnLoadingState };
@@ -1,296 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import {
3
- CircularProgress,
4
- Typography
5
- } from "../components";
6
- import { cls } from "../util";
7
-
8
- export type CollectionSize = "xs" | "s" | "m" | "l" | "xl";
9
-
10
- export type ListViewColumnDef<T> = {
11
- key: string;
12
- label: string;
13
- align?: "left" | "center" | "right";
14
- width?: string;
15
- renderCell: (item: T) => React.ReactNode;
16
- isTitle?: boolean;
17
- };
18
-
19
- export type ListViewProps<T> = {
20
- data: T[];
21
- dataLoading?: boolean;
22
- noMoreToLoad?: boolean;
23
- dataLoadingError?: Error;
24
- itemCount?: number;
25
- setItemCount?: (itemCount: number) => void;
26
- pageSize?: number;
27
- paginationEnabled?: boolean;
28
-
29
- onItemClick?: (item: T) => void;
30
- selectedIds?: Set<string | number>;
31
- highlightedIds?: Set<string | number>;
32
- selectionEnabled?: boolean;
33
- onSelectionChange?: (item: T, selected: boolean) => void;
34
- emptyComponent?: React.ReactNode;
35
-
36
- size?: CollectionSize;
37
- selectedEntityId?: string | number;
38
-
39
- /**
40
- * Rendered above the rows, inside the list's own frame, and only when there
41
- * are rows to head. A column header over an empty state or a spinner labels
42
- * nothing.
43
- */
44
- header?: React.ReactNode;
45
-
46
- renderRow: (params: {
47
- item: T;
48
- index: number;
49
- style: React.CSSProperties;
50
- className: string;
51
- selected: boolean;
52
- highlighted: boolean;
53
- isLast: boolean;
54
- onClick: (e: React.MouseEvent) => void;
55
- onSelectionChange: (selected: boolean) => void;
56
- }) => React.ReactNode;
57
- };
58
-
59
- function getEstimatedRowHeight(size: CollectionSize): number {
60
- switch (size) {
61
- case "xs": return 44;
62
- case "s": return 52;
63
- case "m": return 64;
64
- case "l": return 76;
65
- case "xl": return 88;
66
- default: return 64;
67
- }
68
- }
69
-
70
- const OVERSCAN_COUNT = 8;
71
- const LOAD_MORE_THRESHOLD = 400;
72
-
73
- function getScrollParent(element: HTMLElement | null): HTMLElement | null {
74
- let parent = element?.parentElement ?? null;
75
- while (parent) {
76
- const style = getComputedStyle(parent);
77
- if (
78
- style.overflowY === "auto" || style.overflowY === "scroll" ||
79
- style.overflow === "auto" || style.overflow === "scroll"
80
- ) {
81
- return parent;
82
- }
83
- parent = parent.parentElement;
84
- }
85
- return document.documentElement;
86
- }
87
-
88
- export function ListView<T>({
89
- data,
90
- dataLoading = false,
91
- noMoreToLoad = false,
92
- dataLoadingError,
93
- itemCount,
94
- setItemCount,
95
- pageSize = 50,
96
- paginationEnabled = true,
97
-
98
- onItemClick,
99
- selectedIds,
100
- highlightedIds,
101
- selectionEnabled = true,
102
- onSelectionChange,
103
- emptyComponent,
104
-
105
- size = "m",
106
- selectedEntityId,
107
- header,
108
- renderRow
109
- }: ListViewProps<T>) {
110
- const containerRef = useRef<HTMLDivElement>(null);
111
- // The rows' own box, which a header shifts down from the container's top.
112
- // Virtualization measures against it rather than against the container, so
113
- // the window of rendered rows stays the one the viewport is actually over.
114
- const rowsRef = useRef<HTMLDivElement>(null);
115
- const isLoadingMore = useRef(false);
116
-
117
- // Reset loading gate when loading stops
118
- useEffect(() => {
119
- if (!dataLoading) isLoadingMore.current = false;
120
- }, [dataLoading]);
121
-
122
- // Virtualization scroll states
123
- const estimatedRowHeight = getEstimatedRowHeight(size);
124
- const [effectiveScrollTop, setEffectiveScrollTop] = useState(0);
125
- const [viewportHeight, setViewportHeight] = useState(800);
126
-
127
- const paginationStateRef = useRef({ paginationEnabled, noMoreToLoad, itemCount, pageSize });
128
- useEffect(() => {
129
- paginationStateRef.current = { paginationEnabled, noMoreToLoad, itemCount, pageSize };
130
- }, [paginationEnabled, noMoreToLoad, itemCount, pageSize]);
131
-
132
- useEffect(() => {
133
- const el = containerRef.current;
134
- if (!el) return;
135
- const scrollEl = getScrollParent(el);
136
- if (!scrollEl) return;
137
-
138
- let rafId: number | null = null;
139
-
140
- const update = () => {
141
- rafId = null;
142
- const scrollRect = scrollEl.getBoundingClientRect();
143
- const listRect = (rowsRef.current ?? el).getBoundingClientRect();
144
-
145
- const listTopRelative = listRect.top - scrollRect.top;
146
- setEffectiveScrollTop(Math.max(0, -listTopRelative));
147
- setViewportHeight(scrollRect.height);
148
-
149
- const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
150
- if (
151
- pe &&
152
- !nm &&
153
- !isLoadingMore.current &&
154
- scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < LOAD_MORE_THRESHOLD
155
- ) {
156
- isLoadingMore.current = true;
157
- setItemCount?.((ic ?? ps) + ps);
158
- }
159
- };
160
-
161
- const onScroll = () => {
162
- if (rafId === null) rafId = requestAnimationFrame(update);
163
- };
164
-
165
- scrollEl.addEventListener("scroll", onScroll, { passive: true });
166
- const ro = new ResizeObserver(() => update());
167
- ro.observe(scrollEl);
168
- update();
169
-
170
- return () => {
171
- scrollEl.removeEventListener("scroll", onScroll);
172
- ro.disconnect();
173
- if (rafId !== null) cancelAnimationFrame(rafId);
174
- };
175
- }, [setItemCount]);
176
-
177
- const totalHeight = data.length * estimatedRowHeight;
178
- const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
179
- const endIndex = Math.min(
180
- data.length,
181
- Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT
182
- );
183
- const visibleData = data.slice(startIndex, endIndex);
184
- const offsetY = startIndex * estimatedRowHeight;
185
-
186
- const footerHeight = dataLoading ? 48 : (!dataLoading && noMoreToLoad && data.length > 0) ? 32 : 0;
187
-
188
- const isInitialLoading = dataLoading && data.length === 0;
189
- const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
190
-
191
- const getItemId = useCallback((item: T): string | number => {
192
- if (item && typeof item === "object" && "id" in item) {
193
- const id = (item as Record<"id", unknown>).id;
194
- return typeof id === "number" ? id : String(id);
195
- }
196
- return String(item);
197
- }, []);
198
-
199
- const borderMixinClass = "border-surface-200 dark:border-surface-800";
200
-
201
- return (
202
- <div
203
- ref={containerRef}
204
- className={cls(
205
- "w-full",
206
- selectedEntityId === undefined && `rounded-lg overflow-hidden border ${borderMixinClass}`
207
- )}
208
- >
209
- {dataLoadingError && data.length === 0 ? (
210
- <div className="flex items-center justify-center p-8">
211
- <Typography className="text-red-500">
212
- Error loading data: {dataLoadingError.message}
213
- </Typography>
214
- </div>
215
- ) : isInitialLoading ? (
216
- <div className="flex items-center justify-center py-12 px-8">
217
- <CircularProgress size="small" />
218
- </div>
219
- ) : isEmpty ? (
220
- <div className="w-full flex items-center justify-center py-12 px-8">
221
- {emptyComponent ?? (
222
- <Typography variant="label" color="secondary">
223
- No entries found
224
- </Typography>
225
- )}
226
- </div>
227
- ) : (
228
- <>
229
- {header}
230
- <div ref={rowsRef} style={{ height: totalHeight + footerHeight, position: "relative" }}>
231
- <div style={{ position: "absolute", top: offsetY, left: 0, right: 0 }}>
232
- {visibleData.map((item, i) => {
233
- const actualIndex = startIndex + i;
234
- const isLast = actualIndex === data.length - 1;
235
- const id = getItemId(item);
236
- const selected = selectedIds?.has(id) ?? false;
237
- const highlighted = highlightedIds?.has(id) ?? false;
238
-
239
- const handleClick = (e: React.MouseEvent) => {
240
- if ((e.metaKey || e.ctrlKey) && selectionEnabled) {
241
- e.preventDefault();
242
- onSelectionChange?.(item, !selected);
243
- return;
244
- }
245
- onItemClick?.(item);
246
- };
247
-
248
- const handleSelectionChange = (val: boolean) => {
249
- onSelectionChange?.(item, val);
250
- };
251
-
252
- return (
253
- <React.Fragment key={id || actualIndex}>
254
- {renderRow({
255
- item,
256
- index: actualIndex,
257
- style: { height: estimatedRowHeight, overflow: "hidden" },
258
- className: cls(
259
- !isLast && "border-b",
260
- !isLast && borderMixinClass
261
- ),
262
- selected,
263
- highlighted,
264
- isLast,
265
- onClick: handleClick,
266
- onSelectionChange: handleSelectionChange
267
- })}
268
- </React.Fragment>
269
- );
270
- })}
271
- </div>
272
-
273
- {dataLoading && (
274
- <div
275
- className="flex items-center justify-center py-3"
276
- style={{ position: "absolute", top: totalHeight, left: 0, right: 0 }}
277
- >
278
- <CircularProgress size="small" />
279
- </div>
280
- )}
281
- {!dataLoading && noMoreToLoad && data.length > 0 && (
282
- <div
283
- className="flex items-center justify-center py-2 dark:bg-surface-900"
284
- style={{ position: "absolute", top: totalHeight, left: 0, right: 0 }}
285
- >
286
- <Typography variant="caption" color="secondary">
287
- All {data.length} entries loaded
288
- </Typography>
289
- </div>
290
- )}
291
- </div>
292
- </>
293
- )}
294
- </div>
295
- );
296
- }
@@ -1,5 +0,0 @@
1
- export * from "./ListView";
2
- export * from "./CardView";
3
- export * from "./KanbanView";
4
- export * from "./CollectionView";
5
- // ./TableView was a pure alias of VirtualTable — import VirtualTable instead.