@rebasepro/ui 0.17.3 → 0.18.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/README.md +4 -0
- package/dist/components/MultiSelect.d.ts +10 -0
- package/dist/index.es.js +2 -1
- package/dist/index.es.js.map +1 -1
- package/package.json +32 -23
- package/src/components/Alert.tsx +0 -118
- package/src/components/Autocomplete.tsx +0 -93
- package/src/components/Avatar.tsx +0 -73
- package/src/components/Badge.tsx +0 -44
- package/src/components/BooleanSwitch.tsx +0 -116
- package/src/components/BooleanSwitchWithLabel.tsx +0 -120
- package/src/components/Button.tsx +0 -110
- package/src/components/Card.tsx +0 -43
- package/src/components/CenteredView.tsx +0 -35
- package/src/components/Checkbox.tsx +0 -129
- package/src/components/Chip.tsx +0 -131
- package/src/components/CircularProgress.tsx +0 -53
- package/src/components/CircularProgressCenter.tsx +0 -27
- package/src/components/Collapse.tsx +0 -66
- package/src/components/ColorPicker.tsx +0 -140
- package/src/components/Container.tsx +0 -51
- package/src/components/DateTimeField.tsx +0 -363
- package/src/components/DebouncedTextField.tsx +0 -76
- package/src/components/Dialog.tsx +0 -159
- package/src/components/DialogActions.tsx +0 -29
- package/src/components/DialogContent.tsx +0 -27
- package/src/components/DialogTitle.tsx +0 -41
- package/src/components/ErrorBoundary.tsx +0 -236
- package/src/components/ExpandablePanel.tsx +0 -130
- package/src/components/FileUpload.tsx +0 -103
- package/src/components/FilterChip.tsx +0 -89
- package/src/components/IconButton.tsx +0 -101
- package/src/components/InfoLabel.tsx +0 -23
- package/src/components/InputLabel.tsx +0 -40
- package/src/components/Label.tsx +0 -32
- package/src/components/LoadingButton.tsx +0 -32
- package/src/components/Markdown.tsx +0 -55
- package/src/components/Menu.tsx +0 -114
- package/src/components/Menubar.tsx +0 -355
- package/src/components/MultiSelect.tsx +0 -433
- package/src/components/Paper.tsx +0 -23
- package/src/components/Popover.tsx +0 -162
- package/src/components/RadioGroup.tsx +0 -74
- package/src/components/ResizablePanels.tsx +0 -189
- package/src/components/SearchBar.tsx +0 -136
- package/src/components/Select.tsx +0 -326
- package/src/components/Separator.tsx +0 -28
- package/src/components/Sheet.tsx +0 -135
- package/src/components/Skeleton.tsx +0 -54
- package/src/components/Slider.tsx +0 -110
- package/src/components/Table.tsx +0 -178
- package/src/components/Tabs.tsx +0 -185
- package/src/components/TextField.tsx +0 -270
- package/src/components/TextareaAutosize.tsx +0 -368
- package/src/components/ToggleButtonGroup.tsx +0 -69
- package/src/components/Tooltip.tsx +0 -153
- package/src/components/Typography.tsx +0 -132
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
- package/src/components/VirtualTable/VirtualTable.tsx +0 -700
- package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
- package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
- package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
- package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
- package/src/components/VirtualTable/index.tsx +0 -10
- package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
- package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
- package/src/components/VirtualTable/types.tsx +0 -47
- package/src/components/common/SelectInputLabel.tsx +0 -23
- package/src/components/index.tsx +0 -62
- package/src/hooks/PortalContainerContext.tsx +0 -50
- package/src/hooks/index.ts +0 -7
- package/src/hooks/useDebounceCallback.tsx +0 -21
- package/src/hooks/useDebounceValue.tsx +0 -20
- package/src/hooks/useDebouncedCallback.ts +0 -39
- package/src/hooks/useInjectStyles.tsx +0 -31
- package/src/hooks/useOutsideAlerter.tsx +0 -46
- package/src/icons/GitHubIcon.tsx +0 -21
- package/src/icons/HandleIcon.tsx +0 -23
- package/src/icons/Icon.tsx +0 -33
- package/src/icons/LucideIconByName.tsx +0 -117
- package/src/icons/cool_icon_keys.ts +0 -12
- package/src/icons/icon_keys.ts +0 -1936
- package/src/icons/index.ts +0 -179
- package/src/index.css +0 -169
- package/src/index.ts +0 -47
- package/src/scripts/fixIconSizes.cjs +0 -92
- package/src/scripts/fixIconSizesToConst.cjs +0 -106
- package/src/scripts/generateIconKeys.ts +0 -41
- package/src/scripts/generateIcons.ts +0 -4
- package/src/scripts/saveIconFiles.ts +0 -89
- package/src/styles.ts +0 -141
- package/src/theme.css +0 -168
- package/src/util/chip_colors.ts +0 -242
- package/src/util/cls.ts +0 -8
- package/src/util/debounce.ts +0 -28
- package/src/util/hash.ts +0 -11
- package/src/util/index.ts +0 -4
- package/src/util/key_to_icon_component.ts +0 -11
- package/src/util/lazy_chunk.ts +0 -101
- package/src/views/CardView.tsx +0 -281
- package/src/views/CollectionView/CollectionCardView.tsx +0 -265
- package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
- package/src/views/CollectionView/CollectionListView.tsx +0 -240
- package/src/views/CollectionView/CollectionTableView.tsx +0 -229
- package/src/views/CollectionView/CollectionView.tsx +0 -450
- package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
- package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
- package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
- package/src/views/CollectionView/index.ts +0 -39
- package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
- package/src/views/CollectionView/utils.ts +0 -14
- package/src/views/Kanban/Board.tsx +0 -384
- package/src/views/Kanban/BoardColumn.tsx +0 -138
- package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
- package/src/views/Kanban/BoardSortableList.tsx +0 -182
- package/src/views/Kanban/board_types.ts +0 -80
- package/src/views/Kanban/index.tsx +0 -5
- package/src/views/Kanban/placement.ts +0 -62
- package/src/views/KanbanView.tsx +0 -32
- package/src/views/ListView.tsx +0 -296
- 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,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
|
-
}
|
package/src/views/KanbanView.tsx
DELETED
|
@@ -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 };
|
package/src/views/ListView.tsx
DELETED
|
@@ -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
|
-
}
|