pixelize-design-library 2.4.2-beta.47 → 2.4.2-beta.49
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/CHANGELOG.md +60 -0
- package/COMPONENT-GOTCHAS.md +41 -8
- package/DESIGN-GUIDE.md +1 -1
- package/dist/Components/Common/chipTriggerSize.d.ts +10 -0
- package/dist/Components/Common/chipTriggerSize.js +15 -0
- package/dist/Components/SearchSelect/SearchSelect.js +48 -7
- package/dist/Components/SearchSelect/SearchSelectProps.d.ts +5 -1
- package/dist/Components/Select/SelectNative.d.ts +2 -0
- package/dist/Components/Select/SelectPopup.js +97 -36
- package/dist/Components/Select/SelectProps.d.ts +4 -0
- package/dist/Components/SelectV2/SelectV2.d.ts +2 -9
- package/dist/Components/SelectV2/SelectV2.js +2 -10
- package/dist/Components/Table/Table.js +6 -7
- package/dist/Components/Table/TableProps.d.ts +9 -1
- package/dist/Components/Table/components/Pagination.js +8 -5
- package/dist/Components/Table/components/TableBody.d.ts +2 -1
- package/dist/Components/Table/components/TableBody.js +82 -15
- package/dist/Components/Table/components/TableHeader.d.ts +1 -1
- package/dist/Components/Table/components/TableHeader.js +8 -9
- package/dist/Components/Table/components/rowMetrics.d.ts +0 -1
- package/dist/Components/Table/components/rowMetrics.js +1 -2
- package/dist/Components/Table/hooks/useTable.d.ts +5 -1
- package/dist/Components/Table/hooks/useTable.js +43 -7
- package/dist/Theme/tokens/primitives/controlSizes.js +2 -2
- package/dist/esm/Components/Common/chipTriggerSize.js +11 -0
- package/dist/esm/Components/SearchSelect/SearchSelect.js +48 -7
- package/dist/esm/Components/Select/SelectPopup.js +97 -36
- package/dist/esm/Components/SelectV2/SelectV2.js +2 -9
- package/dist/esm/Components/Table/Table.js +6 -7
- package/dist/esm/Components/Table/components/Pagination.js +8 -5
- package/dist/esm/Components/Table/components/TableBody.js +81 -16
- package/dist/esm/Components/Table/components/TableHeader.js +8 -9
- package/dist/esm/Components/Table/components/rowMetrics.js +0 -1
- package/dist/esm/Components/Table/hooks/useTable.js +44 -8
- package/dist/esm/Theme/tokens/primitives/controlSizes.js +2 -2
- package/package.json +1 -1
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isControlSize = exports.DEFAULT_CONTROL_SIZE = exports.CONTROL_SIZE_NAMES = exports.CONTROL_SIZES = void 0;
|
|
4
4
|
/** One height scale for fields, selects and buttons, so controls side by side line up. */
|
|
5
5
|
exports.CONTROL_SIZES = {
|
|
6
|
-
xs: { height: "1.
|
|
7
|
-
sm: { height: "
|
|
6
|
+
xs: { height: "1.75rem", fontSize: "0.75rem", paddingX: "0.5rem", iconSize: 12 },
|
|
7
|
+
sm: { height: "2.1875rem", fontSize: "0.8125rem", paddingX: "0.75rem", iconSize: 14 },
|
|
8
8
|
md: { height: "2.5rem", fontSize: "0.875rem", paddingX: "1rem", iconSize: 16 },
|
|
9
9
|
lg: { height: "3rem", fontSize: "1rem", paddingX: "1.5rem", iconSize: 18 },
|
|
10
10
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CONTROL_SIZES } from "../../Theme/tokens/primitives/controlSizes.js";
|
|
2
|
+
const CHIP_TAG_HEIGHT_PX = { xs: 20, sm: 24 };
|
|
3
|
+
const CONTROL_BORDER_PX = 2;
|
|
4
|
+
const controlHeightPx = (size) => parseFloat(CONTROL_SIZES[size].height) * 16;
|
|
5
|
+
const chipRowPaddingY = (size, chipSize) => `${(controlHeightPx(size) - CHIP_TAG_HEIGHT_PX[chipSize] - CONTROL_BORDER_PX) / 2 / 16}rem`;
|
|
6
|
+
/** Sizes a select trigger so a chip row lands on the same height as a plain one. */
|
|
7
|
+
export const chipTriggerSizeStyles = (size, hasChips, chipSize = "sm") => {
|
|
8
|
+
const spec = CONTROL_SIZES[size];
|
|
9
|
+
const base = { minH: spec.height, fontSize: spec.fontSize, px: spec.paddingX };
|
|
10
|
+
return hasChips ? { ...base, py: chipRowPaddingY(size, chipSize) } : { ...base, h: spec.height };
|
|
11
|
+
};
|
|
@@ -53,6 +53,9 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
53
53
|
const dropdownMenuRef = useRef(null);
|
|
54
54
|
const [dropdownPos, setDropdownPos] = useState({ top: 0, left: 0, width: 0 });
|
|
55
55
|
const scrollTimeoutRef = useRef(null);
|
|
56
|
+
const optionsLengthRef = useRef(options.length);
|
|
57
|
+
const lastPageStartRef = useRef(null);
|
|
58
|
+
const optionsListRef = useRef(null);
|
|
56
59
|
const [focused, setFocused] = useState(false);
|
|
57
60
|
const [customSelectOpen, setCustomSelectOpen] = useState(false);
|
|
58
61
|
const customSelectRef = useRef(null);
|
|
@@ -103,10 +106,22 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
103
106
|
return () => clearTimeout(delayDebounce);
|
|
104
107
|
}, [inputValue]);
|
|
105
108
|
useEffect(() => {
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
optionsLengthRef.current = options.length;
|
|
110
|
+
}, [options.length]);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if ((pagination === null || pagination === void 0 ? void 0 : pagination.hasMore) !== undefined) {
|
|
113
|
+
setHasMore(pagination.hasMore);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if ((pagination === null || pagination === void 0 ? void 0 : pagination.totalRecords) !== undefined) {
|
|
117
|
+
setHasMore(options.length < pagination.totalRecords);
|
|
118
|
+
return;
|
|
108
119
|
}
|
|
109
|
-
|
|
120
|
+
if (!(pagination === null || pagination === void 0 ? void 0 : pagination.onScroll))
|
|
121
|
+
return;
|
|
122
|
+
const limit = pagination.limit || 10;
|
|
123
|
+
setHasMore(options.length % limit === 0);
|
|
124
|
+
}, [options.length, pagination === null || pagination === void 0 ? void 0 : pagination.hasMore, pagination === null || pagination === void 0 ? void 0 : pagination.totalRecords, pagination === null || pagination === void 0 ? void 0 : pagination.limit, pagination === null || pagination === void 0 ? void 0 : pagination.onScroll]);
|
|
110
125
|
const handleSelect = (option) => {
|
|
111
126
|
var _a;
|
|
112
127
|
if (isMultiple && insideSelect) {
|
|
@@ -286,6 +301,13 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
286
301
|
}
|
|
287
302
|
setActiveIndex((prev) => (prev >= 0 && prev < filteredOptions.length ? prev : -1));
|
|
288
303
|
}, [isOpen, filteredOptions.length]);
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
var _a, _b;
|
|
306
|
+
if (!isOpen || activeIndex < 0 || !optionsListRef.current)
|
|
307
|
+
return;
|
|
308
|
+
(_b = (_a = optionsListRef.current
|
|
309
|
+
.querySelector(`[data-index="${activeIndex}"]`)) === null || _a === void 0 ? void 0 : _a.scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, { block: "nearest" });
|
|
310
|
+
}, [isOpen, activeIndex]);
|
|
289
311
|
// NOTE: only restores focus when a keyboard close actually unmounted Input and dropped focus to body.
|
|
290
312
|
useEffect(() => {
|
|
291
313
|
var _a, _b, _c, _d;
|
|
@@ -316,11 +338,30 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
316
338
|
clearTimeout(scrollTimeoutRef.current);
|
|
317
339
|
scrollTimeoutRef.current = setTimeout(() => {
|
|
318
340
|
var _a;
|
|
319
|
-
|
|
341
|
+
scrollTimeoutRef.current = null;
|
|
342
|
+
const startIndex = optionsLengthRef.current + 1;
|
|
343
|
+
if (lastPageStartRef.current === startIndex) {
|
|
344
|
+
setHasMore(false);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
lastPageStartRef.current = startIndex;
|
|
320
348
|
(_a = pagination.onScroll) === null || _a === void 0 ? void 0 : _a.call(pagination, startIndex, pagination.limit || 10, inputValue);
|
|
321
349
|
}, 200);
|
|
322
350
|
}
|
|
323
|
-
}, [pagination,
|
|
351
|
+
}, [pagination, inputValue, hasMore]);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if (isOpen)
|
|
354
|
+
return;
|
|
355
|
+
if (scrollTimeoutRef.current) {
|
|
356
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
357
|
+
scrollTimeoutRef.current = null;
|
|
358
|
+
}
|
|
359
|
+
lastPageStartRef.current = null;
|
|
360
|
+
}, [isOpen]);
|
|
361
|
+
useEffect(() => () => {
|
|
362
|
+
if (scrollTimeoutRef.current)
|
|
363
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
364
|
+
}, []);
|
|
324
365
|
useOutsideClick({
|
|
325
366
|
ref: customSelectRef,
|
|
326
367
|
handler: (e) => {
|
|
@@ -498,10 +539,10 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
498
539
|
else {
|
|
499
540
|
openMenu();
|
|
500
541
|
}
|
|
501
|
-
}, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: focusVisibleStyles(theme.colors), children: isOpen ? (_jsx(ChevronUp, { size: s.chevronSize })) : (_jsx(ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && (_jsx(Portal, { children: _jsxs(Box, { ref: setDropdownMenuRef, id: listboxId, role: "listbox", "aria-multiselectable": isMultiple || undefined, position: "fixed", top: `${dropdownPos.top}px`, left: `${dropdownPos.left}px`, width: `${dropdownPos.width}px`, zIndex: 1500, maxH: "20rem", borderWidth: 1, borderColor: theme.colors.boxborder[200], borderRadius: "xl", overflow: "hidden", bg: theme.colors.surfaceRaised, boxShadow: theme.shadows.lg, display: "flex", flexDirection: "column", children: [isMultiple && isSelectAll && (_jsxs(Box, { py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", onClick: handleSelectAll, display: "flex", alignItems: "center", gap: spec.gap, color: theme.colors.text[700], borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [_jsx(Checkbox, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), _jsx(Text, { fontSize: spec.fontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), _jsx(Box, { flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? (_jsxs(Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [_jsx(Spinner, { size: s.spinnerSize }), _jsx(Text, { fontSize: spec.fontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? (_jsx(Text, { p: 3, fontSize: spec.fontSize, color: theme.colors.textMuted, children: "No results found" })) : (_jsxs(_Fragment, { children: [filteredOptions.map((option, index) => {
|
|
542
|
+
}, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: focusVisibleStyles(theme.colors), children: isOpen ? (_jsx(ChevronUp, { size: s.chevronSize })) : (_jsx(ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && (_jsx(Portal, { children: _jsxs(Box, { ref: setDropdownMenuRef, id: listboxId, role: "listbox", "aria-multiselectable": isMultiple || undefined, position: "fixed", top: `${dropdownPos.top}px`, left: `${dropdownPos.left}px`, width: `${dropdownPos.width}px`, zIndex: 1500, maxH: "20rem", borderWidth: 1, borderColor: theme.colors.boxborder[200], borderRadius: "xl", overflow: "hidden", bg: theme.colors.surfaceRaised, boxShadow: theme.shadows.lg, display: "flex", flexDirection: "column", children: [isMultiple && isSelectAll && (_jsxs(Box, { py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", onClick: handleSelectAll, display: "flex", alignItems: "center", gap: spec.gap, color: theme.colors.text[700], borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [_jsx(Checkbox, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), _jsx(Text, { fontSize: spec.fontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), _jsx(Box, { ref: optionsListRef, flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? (_jsxs(Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [_jsx(Spinner, { size: s.spinnerSize }), _jsx(Text, { fontSize: spec.fontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? (_jsx(Text, { p: 3, fontSize: spec.fontSize, color: theme.colors.textMuted, children: "No results found" })) : (_jsxs(_Fragment, { children: [filteredOptions.map((option, index) => {
|
|
502
543
|
const selected = isSelected(option.id);
|
|
503
544
|
const singleSelected = selected && !isMultiple;
|
|
504
|
-
return (_jsxs(Box, { id: getOptionId(index), role: "option", "aria-selected": selected, py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", bg: selected
|
|
545
|
+
return (_jsxs(Box, { id: getOptionId(index), "data-index": index, role: "option", "aria-selected": selected, py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", bg: selected
|
|
505
546
|
? index === activeIndex
|
|
506
547
|
? rowBackgrounds.selectedActive
|
|
507
548
|
: rowBackgrounds.selected
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Checkbox, FormControl, Input, Popover, PopoverAnchor, PopoverContent, Portal, Spinner, useDisclosure, useMergeRefs, } from "@chakra-ui/react";
|
|
4
4
|
import { Check, ChevronDown, X } from "lucide-react";
|
|
5
5
|
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
@@ -11,6 +11,7 @@ import { scrollbarStyles } from "../../Theme/tokens/builders/scrollbar.js";
|
|
|
11
11
|
import { CONTROL_SIZES, DEFAULT_CONTROL_SIZE } from "../../Theme/tokens/primitives/controlSizes.js";
|
|
12
12
|
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
13
13
|
import { reducedMotionSx } from "../../Theme/chakra/motion.js";
|
|
14
|
+
import { chipTriggerSizeStyles } from "../Common/chipTriggerSize.js";
|
|
14
15
|
import { fieldControlStyles } from "../Common/fieldStyles.js";
|
|
15
16
|
import { useFieldIds } from "../Common/useFieldIds.js";
|
|
16
17
|
import FieldMessage from "../Common/FieldMessage.js";
|
|
@@ -23,14 +24,6 @@ const PAGE_STEP = 8;
|
|
|
23
24
|
const LOAD_MORE_THRESHOLD_PX = 8;
|
|
24
25
|
const DEFAULT_MAX_VISIBLE_CHIPS = 6;
|
|
25
26
|
const VIRTUAL_OVERSCAN = 4;
|
|
26
|
-
const CHIP_TAG_HEIGHT_PX = { xs: 20, sm: 24 };
|
|
27
|
-
const controlHeightPx = (size) => parseFloat(CONTROL_SIZES[size].height) * 16;
|
|
28
|
-
const chipRowPaddingY = (size, chipSize) => `${(controlHeightPx(size) - CHIP_TAG_HEIGHT_PX[chipSize]) / 2 / 16}rem`;
|
|
29
|
-
const chipTriggerSizeStyles = (size, hasChips, chipSize = "sm") => {
|
|
30
|
-
const spec = CONTROL_SIZES[size];
|
|
31
|
-
const base = { minH: spec.height, fontSize: spec.fontSize, px: spec.paddingX };
|
|
32
|
-
return hasChips ? { ...base, py: chipRowPaddingY(size, chipSize) } : { ...base, h: spec.height };
|
|
33
|
-
};
|
|
34
27
|
const asIdSet = (values) => new Set((values !== null && values !== void 0 ? values : []).map((v) => v.toString()));
|
|
35
28
|
/** Regression-guarded id scheme — tickets e2e (`e2e/tests/helpers/selectV2.ts`) resolves it verbatim. */
|
|
36
29
|
const optionDomId = (listboxId, id) => `${listboxId}-option-${id.toString().replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
@@ -60,7 +53,7 @@ const buildGroupedRows = (options) => {
|
|
|
60
53
|
/** The unified `Select`'s popup mode (`native={false}`) — absorbs SelectV2's behaviour, plus clear/create/groups/virtualization. */
|
|
61
54
|
const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
62
55
|
var _a, _b, _c, _d;
|
|
63
|
-
const { options = [], id, name, placeholder = "Select Option", label, error = false, errorMessage, helperText, isDisabled = false, isReadOnly = false, isRequired = false, isInformation = false, informationMessage, size = DEFAULT_CONTROL_SIZE, isSearchable = true, width, formControlStyle, onBlur, onFocus, renderOption, renderValue, maxMenuHeight = "16rem", defaultOpen = false, isLoading = false, loadingText = "Loading...", onLoadMore, onSearchChange, searchValue, defaultSearchValue, searchDebounceMs = 0, isClearable = false, onClear, noOptionsMessage, onCreate, createLabel, virtualizeAfter, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, } = props;
|
|
56
|
+
const { options = [], id, name, placeholder = "Select Option", label, error = false, errorMessage, helperText, isDisabled = false, isReadOnly = false, isRequired = false, isInformation = false, informationMessage, size = DEFAULT_CONTROL_SIZE, isSearchable = true, width, formControlStyle, onBlur, onFocus, renderOption, renderValue, maxMenuHeight = "16rem", defaultOpen = false, isLoading = false, loadingText = "Loading...", onLoadMore, hasMore, onSearchChange, searchValue, defaultSearchValue, searchDebounceMs = 0, filterOptions, isClearable = false, onClear, noOptionsMessage, onCreate, createLabel, virtualizeAfter, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, } = props;
|
|
64
57
|
const theme = useCustomTheme();
|
|
65
58
|
const { colors } = theme;
|
|
66
59
|
const { isOpen, onOpen, onClose } = useDisclosure({ defaultIsOpen: defaultOpen });
|
|
@@ -70,6 +63,8 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
70
63
|
const mergedTriggerRef = useMergeRefs(triggerRef, ref);
|
|
71
64
|
const filterInputRef = useRef(null);
|
|
72
65
|
const listRef = useRef(null);
|
|
66
|
+
const lastActiveIndex = useRef(-1);
|
|
67
|
+
const wasVirtualized = useRef(false);
|
|
73
68
|
const [triggerWidth, setTriggerWidth] = useState(0);
|
|
74
69
|
const ids = useFieldIds(id, { error, errorMessage, helperText });
|
|
75
70
|
const listboxId = `${ids.inputId}-listbox`;
|
|
@@ -87,13 +82,28 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
87
82
|
const selectedIds = useMemo(() => props.isMulti
|
|
88
83
|
? asIdSet(currentValue)
|
|
89
84
|
: asIdSet(currentValue !== undefined ? [currentValue] : undefined), [props.isMulti, currentValue]);
|
|
90
|
-
const
|
|
85
|
+
const selectionCacheRef = useRef(new Map());
|
|
86
|
+
const selectedOptions = useMemo(() => {
|
|
87
|
+
const cache = selectionCacheRef.current;
|
|
88
|
+
options.forEach((option) => {
|
|
89
|
+
if (selectedIds.has(option.id.toString()))
|
|
90
|
+
cache.set(option.id.toString(), option);
|
|
91
|
+
});
|
|
92
|
+
Array.from(cache.keys()).forEach((cachedId) => {
|
|
93
|
+
if (!selectedIds.has(cachedId))
|
|
94
|
+
cache.delete(cachedId);
|
|
95
|
+
});
|
|
96
|
+
return Array.from(selectedIds)
|
|
97
|
+
.map((selectedId) => cache.get(selectedId))
|
|
98
|
+
.filter((option) => option !== undefined);
|
|
99
|
+
}, [options, selectedIds]);
|
|
100
|
+
const shouldFilterLocally = filterOptions !== null && filterOptions !== void 0 ? filterOptions : !onSearchChange;
|
|
91
101
|
const filteredOptions = useMemo(() => {
|
|
92
|
-
if (!isSearchable || !currentSearch.trim())
|
|
102
|
+
if (!isSearchable || !currentSearch.trim() || !shouldFilterLocally)
|
|
93
103
|
return options;
|
|
94
104
|
const needle = currentSearch.trim().toLowerCase();
|
|
95
105
|
return options.filter((option) => option.label.toString().toLowerCase().includes(needle));
|
|
96
|
-
}, [options, currentSearch, isSearchable]);
|
|
106
|
+
}, [options, currentSearch, isSearchable, shouldFilterLocally]);
|
|
97
107
|
const hasGroups = useMemo(() => options.some((option) => option.group), [options]);
|
|
98
108
|
const resetAndClose = useCallback((opts) => {
|
|
99
109
|
if (props.isMulti && (opts === null || opts === void 0 ? void 0 : opts.keepOpenForMulti))
|
|
@@ -115,15 +125,19 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
115
125
|
: currentValue;
|
|
116
126
|
setActiveIndex(filteredOptions.findIndex((option) => option.id.toString() === (firstSelected === null || firstSelected === void 0 ? void 0 : firstSelected.toString())));
|
|
117
127
|
};
|
|
128
|
+
const selectedIdsRef = useRef(selectedIds);
|
|
129
|
+
selectedIdsRef.current = selectedIds;
|
|
130
|
+
const selectedOptionsRef = useRef(selectedOptions);
|
|
131
|
+
selectedOptionsRef.current = selectedOptions;
|
|
118
132
|
const handleSelect = useCallback((option) => {
|
|
119
133
|
var _a, _b;
|
|
120
134
|
if (option.isDisabled)
|
|
121
135
|
return;
|
|
122
136
|
if (props.isMulti) {
|
|
123
|
-
const alreadySelected =
|
|
137
|
+
const alreadySelected = selectedIdsRef.current.has(option.id.toString());
|
|
124
138
|
const next = alreadySelected
|
|
125
|
-
?
|
|
126
|
-
: [...
|
|
139
|
+
? selectedOptionsRef.current.filter((selected) => selected.id.toString() !== option.id.toString())
|
|
140
|
+
: [...selectedOptionsRef.current, option];
|
|
127
141
|
if (!isValueControlled)
|
|
128
142
|
setUncontrolledValue(next.map((o) => o.id));
|
|
129
143
|
props.onChange(next);
|
|
@@ -138,7 +152,7 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
138
152
|
(_b = triggerRef.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
139
153
|
},
|
|
140
154
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
141
|
-
[props.isMulti, props.onChange,
|
|
155
|
+
[props.isMulti, props.onChange, resetAndClose, isValueControlled]);
|
|
142
156
|
const handleCreate = useCallback(() => {
|
|
143
157
|
if (!onCreate)
|
|
144
158
|
return;
|
|
@@ -180,24 +194,47 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
180
194
|
const maxMenuHeightPx = useMemo(() => parseRemToPx(maxMenuHeight, 256), [maxMenuHeight]);
|
|
181
195
|
const shouldVirtualize = !!virtualizeAfter && !hasGroups && filteredOptions.length > virtualizeAfter;
|
|
182
196
|
const [scrollTop, setScrollTop] = useState(0);
|
|
183
|
-
|
|
197
|
+
// A description, an avatar or a renderOption row outgrows the spec's single-line height.
|
|
198
|
+
const [rowHeightPx, setRowHeightPx] = useState(spec.virtualRowHeightPx);
|
|
199
|
+
const visibleCount = Math.ceil(maxMenuHeightPx / rowHeightPx) + VIRTUAL_OVERSCAN * 2;
|
|
184
200
|
const startIndex = shouldVirtualize
|
|
185
|
-
? Math.max(0, Math.floor(scrollTop /
|
|
201
|
+
? Math.max(0, Math.floor(scrollTop / rowHeightPx) - VIRTUAL_OVERSCAN)
|
|
186
202
|
: 0;
|
|
187
203
|
const endIndex = shouldVirtualize
|
|
188
204
|
? Math.min(filteredOptions.length, startIndex + visibleCount)
|
|
189
205
|
: filteredOptions.length;
|
|
190
206
|
const renderedOptions = shouldVirtualize ? filteredOptions.slice(startIndex, endIndex) : filteredOptions;
|
|
191
|
-
const topPadPx = shouldVirtualize ? startIndex *
|
|
192
|
-
const bottomPadPx = shouldVirtualize ? (filteredOptions.length - endIndex) *
|
|
207
|
+
const topPadPx = shouldVirtualize ? startIndex * rowHeightPx : 0;
|
|
208
|
+
const bottomPadPx = shouldVirtualize ? (filteredOptions.length - endIndex) * rowHeightPx : 0;
|
|
209
|
+
useLayoutEffect(() => {
|
|
210
|
+
var _a;
|
|
211
|
+
if (!isOpen)
|
|
212
|
+
return;
|
|
213
|
+
const row = (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('[role="option"]');
|
|
214
|
+
if (!row)
|
|
215
|
+
return;
|
|
216
|
+
const measured = row.getBoundingClientRect().height;
|
|
217
|
+
if (measured > 0 && Math.abs(measured - rowHeightPx) > 0.5)
|
|
218
|
+
setRowHeightPx(measured);
|
|
219
|
+
}, [isOpen, rowHeightPx, filteredOptions.length]);
|
|
220
|
+
// Windowing starts mid-list, so the window must open where the user already is.
|
|
221
|
+
useLayoutEffect(() => {
|
|
222
|
+
var _a, _b;
|
|
223
|
+
if (shouldVirtualize && !wasVirtualized.current)
|
|
224
|
+
setScrollTop((_b = (_a = listRef.current) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0);
|
|
225
|
+
wasVirtualized.current = shouldVirtualize;
|
|
226
|
+
}, [shouldVirtualize]);
|
|
193
227
|
useEffect(() => {
|
|
194
228
|
var _a, _b;
|
|
195
229
|
if (activeIndex < 0 || !listRef.current)
|
|
196
230
|
return;
|
|
231
|
+
if (lastActiveIndex.current === activeIndex)
|
|
232
|
+
return;
|
|
233
|
+
lastActiveIndex.current = activeIndex;
|
|
197
234
|
const el = listRef.current;
|
|
198
235
|
if (shouldVirtualize) {
|
|
199
|
-
const targetTop = activeIndex *
|
|
200
|
-
const targetBottom = targetTop +
|
|
236
|
+
const targetTop = activeIndex * rowHeightPx;
|
|
237
|
+
const targetBottom = targetTop + rowHeightPx;
|
|
201
238
|
if (targetTop < el.scrollTop) {
|
|
202
239
|
el.scrollTop = targetTop;
|
|
203
240
|
setScrollTop(targetTop);
|
|
@@ -210,18 +247,44 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
210
247
|
return;
|
|
211
248
|
}
|
|
212
249
|
(_b = (_a = el.querySelector(`[data-index="${activeIndex}"]`)) === null || _a === void 0 ? void 0 : _a.scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, { block: "nearest" });
|
|
213
|
-
}, [activeIndex, shouldVirtualize, maxMenuHeightPx,
|
|
250
|
+
}, [activeIndex, shouldVirtualize, maxMenuHeightPx, rowHeightPx]);
|
|
251
|
+
const loadMoreInFlightRef = useRef(false);
|
|
252
|
+
const prevOptionsLengthRef = useRef(options.length);
|
|
253
|
+
const prevIsLoadingRef = useRef(isLoading);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (options.length !== prevOptionsLengthRef.current)
|
|
256
|
+
loadMoreInFlightRef.current = false;
|
|
257
|
+
prevOptionsLengthRef.current = options.length;
|
|
258
|
+
}, [options.length]);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (prevIsLoadingRef.current && !isLoading)
|
|
261
|
+
loadMoreInFlightRef.current = false;
|
|
262
|
+
prevIsLoadingRef.current = isLoading;
|
|
263
|
+
}, [isLoading]);
|
|
264
|
+
const canLoadMore = !!onLoadMore && hasMore !== false;
|
|
265
|
+
const triggerLoadMore = useCallback(() => {
|
|
266
|
+
if (!onLoadMore || hasMore === false || isLoading || loadMoreInFlightRef.current)
|
|
267
|
+
return;
|
|
268
|
+
loadMoreInFlightRef.current = true;
|
|
269
|
+
onLoadMore();
|
|
270
|
+
}, [onLoadMore, hasMore, isLoading]);
|
|
214
271
|
const moveActive = (delta) => {
|
|
215
272
|
if (!filteredOptions.length)
|
|
216
273
|
return;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
274
|
+
const next = activeIndex + delta;
|
|
275
|
+
if (delta > 0 && next >= filteredOptions.length) {
|
|
276
|
+
if (canLoadMore) {
|
|
277
|
+
triggerLoadMore();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
setActiveIndex(0);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (next < 0) {
|
|
284
|
+
setActiveIndex(filteredOptions.length - 1);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
setActiveIndex(next);
|
|
225
288
|
};
|
|
226
289
|
const moveActiveBy = (delta) => {
|
|
227
290
|
if (!filteredOptions.length)
|
|
@@ -308,10 +371,8 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
308
371
|
const { scrollTop: nextScrollTop, clientHeight, scrollHeight } = event.currentTarget;
|
|
309
372
|
if (shouldVirtualize)
|
|
310
373
|
setScrollTop(nextScrollTop);
|
|
311
|
-
if (!onLoadMore || isLoading)
|
|
312
|
-
return;
|
|
313
374
|
if (nextScrollTop + clientHeight >= scrollHeight - LOAD_MORE_THRESHOLD_PX)
|
|
314
|
-
|
|
375
|
+
triggerLoadMore();
|
|
315
376
|
};
|
|
316
377
|
const handleFilterChange = (event) => {
|
|
317
378
|
const nextText = event.target.value;
|
|
@@ -373,7 +434,7 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
|
|
|
373
434
|
const selectedValueIds = selectedOptions.map((option) => option.id.toString());
|
|
374
435
|
const hiddenValues = !name ? [] : props.isMulti ? selectedValueIds : [(_c = selectedValueIds[0]) !== null && _c !== void 0 ? _c : ""];
|
|
375
436
|
const groupedRows = useMemo(() => (hasGroups ? buildGroupedRows(filteredOptions) : []), [hasGroups, filteredOptions]);
|
|
376
|
-
return (_jsxs(FormControl, { isInvalid: error, isDisabled: isDisabled, isReadOnly: isReadOnly, style: formControlStyle, width: width, children: [label && (_jsx(TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), _jsxs(Popover, { isOpen: isOpen, onClose: () => resetAndClose(), placement: "bottom-start", autoFocus: false, returnFocusOnClose: false, gutter: 4, children: [_jsx(PopoverAnchor, { children: _jsxs(Box, { as: "div", ref: mergedTriggerRef, ...ids.controlProps, role: "combobox", tabIndex: isDisabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-owns": `${listboxId} ${filterInputId}`, "aria-activedescendant": activeDescendantId, "aria-disabled": isDisabled || undefined, "data-readonly": isReadOnly || undefined, "aria-required": isRequired || undefined, "aria-label": triggerAriaLabel(), "aria-labelledby": ariaLabelledBy, onClick: handleOpen, onKeyDown: handleTriggerKeyDown, display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", textAlign: "left", cursor: isDisabled ? "not-allowed" : isReadOnly ? "default" : "pointer", "data-testid": dataTestId, sx: triggerSx, children: [_jsx(Box, { as: "span", display: "inline-flex", flexWrap: showChips ? "wrap" : "nowrap", alignItems: "center", gap: showChips ? 1 : 1.5, minW: 0, flex: 1, isTruncated: !showChips, children: showChips ? (_jsxs(_Fragment, { children: [visibleChipOptions.map((option) => (_jsx(Tag, { label: String(option.label), size: chipSize, variant: "subtle", icon: isInteractive ? X : undefined, onIconClick: isInteractive ? () => handleSelect(option) : undefined }, option.id))), overflowChipCount > 0 && (_jsx(Tag, { label: `+${overflowChipCount}`, size: chipSize, variant: "subtle", colorScheme: "gray" }))] })) : (_jsxs(_Fragment, { children: [triggerIcon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, children: triggerIcon })) : null, _jsx(Box, { as: "span", isTruncated: true, children: renderValue ? renderValue(selectedOptions) : defaultTriggerContent() })] })) }), showClear && (_jsx(Box, { as: "button", type: "button", "aria-label": "Clear selection", onClick: handleClear, display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, ml: 2, color: colors.textMuted, _hover: { color: colors.text[700] }, children: _jsx(X, { size: CONTROL_SIZES[size].iconSize }) })), _jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, ml: 2, transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", color: colors.textMuted, sx: reducedMotionSx(), children: _jsx(ChevronDown, { size: CONTROL_SIZES[size].iconSize }) })] }) }), _jsx(Portal, { children: _jsxs(PopoverContent, { width: "max-content", minWidth: triggerWidth ? `${triggerWidth}px` : undefined, maxWidth: "min(24rem, calc(100vw - 2rem))", borderRadius: "xl", boxShadow: theme.shadows.lg, overflow: "hidden", onKeyDown: handleListKeyDown, children: [_jsx(Box, { p:
|
|
437
|
+
return (_jsxs(FormControl, { isInvalid: error, isDisabled: isDisabled, isReadOnly: isReadOnly, style: formControlStyle, width: width, children: [label && (_jsx(TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), _jsxs(Popover, { isOpen: isOpen, onClose: () => resetAndClose(), placement: "bottom-start", autoFocus: false, returnFocusOnClose: false, gutter: 4, children: [_jsx(PopoverAnchor, { children: _jsxs(Box, { as: "div", ref: mergedTriggerRef, ...ids.controlProps, role: "combobox", tabIndex: isDisabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-owns": `${listboxId} ${filterInputId}`, "aria-activedescendant": activeDescendantId, "aria-disabled": isDisabled || undefined, "data-readonly": isReadOnly || undefined, "aria-required": isRequired || undefined, "aria-label": triggerAriaLabel(), "aria-labelledby": ariaLabelledBy, onClick: handleOpen, onKeyDown: handleTriggerKeyDown, display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", textAlign: "left", cursor: isDisabled ? "not-allowed" : isReadOnly ? "default" : "pointer", "data-testid": dataTestId, sx: triggerSx, children: [_jsx(Box, { as: "span", display: "inline-flex", flexWrap: showChips ? "wrap" : "nowrap", alignItems: "center", gap: showChips ? 1 : 1.5, minW: 0, flex: 1, isTruncated: !showChips, children: showChips ? (_jsxs(_Fragment, { children: [visibleChipOptions.map((option) => (_jsx(Tag, { label: String(option.label), size: chipSize, variant: "subtle", icon: isInteractive ? X : undefined, onIconClick: isInteractive ? () => handleSelect(option) : undefined }, option.id))), overflowChipCount > 0 && (_jsx(Tag, { label: `+${overflowChipCount}`, size: chipSize, variant: "subtle", colorScheme: "gray" }))] })) : (_jsxs(_Fragment, { children: [triggerIcon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, children: triggerIcon })) : null, _jsx(Box, { as: "span", isTruncated: true, children: renderValue ? renderValue(selectedOptions) : defaultTriggerContent() })] })) }), showClear && (_jsx(Box, { as: "button", type: "button", "aria-label": "Clear selection", onClick: handleClear, display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, ml: 2, color: colors.textMuted, _hover: { color: colors.text[700] }, children: _jsx(X, { size: CONTROL_SIZES[size].iconSize }) })), _jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, ml: 2, transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", color: colors.textMuted, sx: reducedMotionSx(), children: _jsx(ChevronDown, { size: CONTROL_SIZES[size].iconSize }) })] }) }), _jsx(Portal, { children: _jsxs(PopoverContent, { width: "max-content", minWidth: triggerWidth ? `${triggerWidth}px` : undefined, maxWidth: "min(24rem, calc(100vw - 2rem))", borderRadius: "xl", boxShadow: theme.shadows.lg, overflow: "hidden", onKeyDown: handleListKeyDown, children: [_jsx(Box, { p: spec.mx, borderBottom: `0.063rem solid ${colors.boxborder[200]}`, display: isSearchable ? undefined : "none", children: _jsx(Input, { ref: filterInputRef, id: filterInputId, size: size, px: spec.px, placeholder: "Type to filter...", value: currentSearch, onChange: handleFilterChange, "aria-activedescendant": activeDescendantId, "aria-controls": listboxId, "aria-label": "Filter options", tabIndex: isSearchable ? undefined : -1 }) }), _jsxs(Box, { id: listboxId, ref: listRef, role: "listbox", "aria-label": (_d = label !== null && label !== void 0 ? label : ariaLabel) !== null && _d !== void 0 ? _d : placeholder, "aria-multiselectable": props.isMulti || undefined, "aria-busy": isLoading || undefined, maxH: maxMenuHeight, overflowY: "auto", py: 1, sx: listScrollStyles, onScroll: handleListScroll, children: [filteredOptions.length === 0 && !isLoading && !showCreateRow && (_jsx(Box, { mx: spec.mx, px: spec.px, py: spec.py, fontSize: spec.fontSize, color: colors.textMuted, children: noOptionsMessage !== null && noOptionsMessage !== void 0 ? noOptionsMessage : "No Options" })), showCreateRow && (_jsx(Box, { id: createRowId, role: "option", "aria-selected": false, "data-index": -1, onClick: handleCreate, mx: spec.mx, px: spec.px, py: spec.py, minH: spec.minH, borderRadius: "md", cursor: "pointer", fontSize: spec.fontSize, fontWeight: 600, color: colors.primary[600], bg: activeDescendantId === createRowId ? rowBackgrounds.active : "transparent", children: createLabel ? createLabel(currentSearch.trim()) : `Create "${currentSearch.trim()}"` })), hasGroups
|
|
377
438
|
? groupedRows.map((row) => {
|
|
378
439
|
var _a;
|
|
379
440
|
return row.kind === "header" ? (_jsx(Box, { mx: spec.mx, px: spec.px, pt: 2, pb: 1, fontSize: "0.6875rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.03em", color: colors.textMuted, children: row.group }, row.key)) : (_jsx(OptionRow, { option: row.option, index: row.index, listboxId: listboxId, isSelected: selectedIds.has(row.option.id.toString()), isActive: row.index === activeIndex, isMulti: (_a = props.isMulti) !== null && _a !== void 0 ? _a : false, theme: theme, backgrounds: rowBackgrounds, spec: spec, renderOption: renderOption, onSelect: handleSelect, onActivate: setActiveIndex }, row.key));
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import Select from "../Select/Select.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const controlHeightPx = (size) => parseFloat(CONTROL_SIZES[size].height) * 16;
|
|
7
|
-
const chipRowPaddingY = (size, chipSize) => `${(controlHeightPx(size) - CHIP_TAG_HEIGHT_PX[chipSize]) / 2 / 16}rem`;
|
|
8
|
-
export const chipTriggerSizeStyles = (size, hasChips, chipSize = "sm") => {
|
|
9
|
-
const spec = CONTROL_SIZES[size];
|
|
10
|
-
const base = { minH: spec.height, fontSize: spec.fontSize, px: spec.paddingX };
|
|
11
|
-
return hasChips ? { ...base, py: chipRowPaddingY(size, chipSize) } : { ...base, h: spec.height };
|
|
12
|
-
};
|
|
4
|
+
import { chipTriggerSizeStyles } from "../Common/chipTriggerSize.js";
|
|
5
|
+
export { chipTriggerSizeStyles };
|
|
13
6
|
/** @deprecated Use `Select` with `native={false}`. See MERGE-NOTES-select-deprecation.md. */
|
|
14
7
|
const SelectV2 = forwardRef(function SelectV2(props, ref) {
|
|
15
8
|
return _jsx(Select, { ...props, native: false, ref: ref });
|
|
@@ -77,7 +77,7 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
|
|
|
77
77
|
}
|
|
78
78
|
return {};
|
|
79
79
|
}, [tablePreferencesData]);
|
|
80
|
-
const { tableData, isContent, isLink, headerRefs, columnWidths, handleSort, handleCheckbox, filteredData, startRow, selection, columnsSort, currentPage, pages, rowsPerPage, handlePageSizeChange, setCurrentPage, columnsList, handleColumnPreferences, isSelecting, selectAllRowsRef, } = useTable({
|
|
80
|
+
const { tableData, isContent, isLink, headerRefs, columnWidths, slotRef, slotWidth, handleSort, handleCheckbox, filteredData, searchedData, startRow, selection, columnsSort, currentPage, pages, rowsPerPage, handlePageSizeChange, setCurrentPage, columnsList, handleColumnPreferences, isSelecting, selectAllRowsRef, } = useTable({
|
|
81
81
|
data,
|
|
82
82
|
isPagination,
|
|
83
83
|
selections,
|
|
@@ -90,6 +90,7 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
|
|
|
90
90
|
isServerPagination,
|
|
91
91
|
onNoOfRowsPerPageChange,
|
|
92
92
|
defaultVisibleColumns,
|
|
93
|
+
columnsSearch,
|
|
93
94
|
});
|
|
94
95
|
// Density + grouping are managed here so the Table Settings tabs can change +
|
|
95
96
|
// persist them (seeded from the prop / saved preferences when they load).
|
|
@@ -172,9 +173,7 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
|
|
|
172
173
|
setGroupByState(group);
|
|
173
174
|
handleColumnPreferences(cols, { density: densityState, groupBy: group !== null && group !== void 0 ? group : null });
|
|
174
175
|
};
|
|
175
|
-
const _filteredData =
|
|
176
|
-
return searchAndSortData(filteredData, columnsSearch);
|
|
177
|
-
}, [columnsSearch, filteredData]);
|
|
176
|
+
const _filteredData = filteredData;
|
|
178
177
|
// --- Monday-style grouping. Active whenever the user has picked a group column
|
|
179
178
|
// (in Settings > Group). When active, the numbered pager / infinite scroll are
|
|
180
179
|
// hidden and the body renders colored, collapsible groups over the loaded rows.
|
|
@@ -279,7 +278,7 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
|
|
|
279
278
|
: "Showing all records";
|
|
280
279
|
const tablePaginationText = isServerPagination
|
|
281
280
|
? `${startRow + 1} - ${Math.min(startRow + rowsPerPage, totalRecords)} of ${totalRecords}`
|
|
282
|
-
: `${startRow + 1} - ${Math.min(startRow + rowsPerPage,
|
|
281
|
+
: `${startRow + 1} - ${Math.min(startRow + rowsPerPage, searchedData.length)} of ${searchedData.length}`;
|
|
283
282
|
const rowHeight = rowHeightFor(densityState);
|
|
284
283
|
const headerHeight = pxOf(variantTokens.header.height);
|
|
285
284
|
const actualRows = isGrouped ? groupedSource.length : _filteredData.length;
|
|
@@ -400,7 +399,7 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
|
|
|
400
399
|
headerActions && (_jsx(HeaderActions, { actions: headerActions, selections: selection }, "actions")),
|
|
401
400
|
]
|
|
402
401
|
.filter(Boolean)
|
|
403
|
-
.flatMap((tool, i) => (i === 0 ? [tool] : [_jsx(VeritcalDivider, {}, `divider-${i}`), tool])), _jsx(ActiveFilters, { columns: columnsList, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch }), _jsxs(Box, { ml: "auto", display: "flex", alignItems: "center", gap: 2, children: [groupLoadMoreActive && loadMorePosition === "top" && (groupLoadMoreCaption || canGroupLoadMore) && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flex: "0 0 auto", children: [groupLoadMoreCaption && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], whiteSpace: "nowrap", children: groupLoadMoreCaption })), canGroupLoadMore && (_jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && !isCompactHeader && (_jsx("div", { children: _jsx(Pagination, { currentPage: currentPage, setCurrentPage: setCurrentPage, rowsPerPage: rowsPerPage, pages: pages, paginationText: tablePaginationText, handlePageSizeChange: handlePageSizeChange, dataLength: tableData.length, isServerPagination: isServerPagination, paginationSelectOptions: paginationSelectOptions }) })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && isCompactHeader &&
|
|
402
|
+
.flatMap((tool, i) => (i === 0 ? [tool] : [_jsx(VeritcalDivider, {}, `divider-${i}`), tool])), _jsx(ActiveFilters, { columns: columnsList, columnsSearch: columnsSearch, setColumnsSearch: setColumnsSearch }), _jsxs(Box, { ml: "auto", display: "flex", alignItems: "center", gap: 2, children: [groupLoadMoreActive && loadMorePosition === "top" && (groupLoadMoreCaption || canGroupLoadMore) && (_jsxs(Box, { display: "flex", alignItems: "center", gap: 2, flex: "0 0 auto", children: [groupLoadMoreCaption && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], whiteSpace: "nowrap", children: groupLoadMoreCaption })), canGroupLoadMore && (_jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && !isCompactHeader && (_jsx("div", { children: _jsx(Pagination, { currentPage: currentPage, setCurrentPage: setCurrentPage, rowsPerPage: rowsPerPage, pages: pages, paginationText: tablePaginationText, handlePageSizeChange: handlePageSizeChange, dataLength: tableData.length, isServerPagination: isServerPagination, paginationSelectOptions: paginationSelectOptions }) })), (isPagination || isServerPagination) && !isGrouped && !canInfinite && isCompactHeader && (_jsxs(Popover, { isOpen: isPaginationPopoverOpen, onClose: () => setIsPaginationPopoverOpen(false), placement: "bottom-end", children: [_jsx(PopoverTrigger, { children: _jsx(IconButton, { "aria-label": "Pagination", size: "sm", variant: "ghost", icon: _jsx(EllipsisVertical, { size: 18 }), color: theme.colors.text[500], _hover: {
|
|
404
403
|
color: theme.colors.accentText,
|
|
405
404
|
}, onClick: () => setIsPaginationPopoverOpen((open) => !open) }) }), _jsx(Portal, { children: _jsx(PopoverContent, { maxW: "22rem", p: 2, zIndex: 1600, bg: theme.colors.surfaceRaised, boxShadow: "lg", children: _jsx(PopoverBody, { p: 0, children: _jsx(Pagination, { currentPage: currentPage, setCurrentPage: setCurrentPage, rowsPerPage: rowsPerPage, pages: pages, paginationText: tablePaginationText, handlePageSizeChange: handlePageSizeChange, dataLength: tableData.length, isServerPagination: isServerPagination, paginationSelectOptions: paginationSelectOptions }) }) }) })] }))] })] }), _jsxs(TableContainer, { ref: tableContainerRef, position: "relative", h: isTableLoading ? tableMaxH : undefined, maxH: !isTableLoading ? tableMaxH : undefined, pb: isTableLoading ? 0 : compactScrollbarPadding, overflowY: isCompactRows ? "hidden" : "auto", overflowX: "auto", sx: {
|
|
406
405
|
...scrollShadowStyles(theme.colors.background[50]),
|
|
@@ -430,5 +429,5 @@ export default function Table({ data, columns, onSelection, isLoading, isCheckbo
|
|
|
430
429
|
backgroundColor: "transparent",
|
|
431
430
|
zIndex: 999,
|
|
432
431
|
},
|
|
433
|
-
}, children: [_jsx(Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: _jsx(TableHeader, { columns: columnsList, isCheckbox: isCheckbox, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleSort: handleSort, headerRefs: headerRefs, columnWidths: columnWidths, columnsSort: columnsSort, noBorders: noBorders, handleCheckbox: handleCheckbox, isLoading: isTableLoading, checked: headerChecked, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, setColumnsSearch: setColumnsSearch, columnsSearch: columnsSearch, isSelecting: isSelecting }) }), _jsx(Tbody, { children: _jsx(TableBody, { data: isGrouped ? groupedSource : _filteredData, groups: renderGroups, onAddItem: onAddItem, columns: columnsList, startRow: isGrouped ? 0 : startRow, scrollContainerRef: tableContainerRef, isCheckbox: isCheckbox, columnWidths: columnWidths, noBorders: noBorders, freezedBgColor: freezedBgColor !== null && freezedBgColor !== void 0 ? freezedBgColor : theme.colors.backgroundColor.secondary, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleCheckbox: handleCheckbox, selections: selection, isLoading: isTableLoading, loadingSkeletonRows: loadingSkeletonRows, onRowClick: onRowClick, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, density: densityState, stripe: stripe, emptyState: emptyState }) })] }), canInfinite && isLoadingMore && (_jsxs(Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [_jsx(Spinner, { size: "sm", color: theme.colors.accentText }), _jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && (_jsxs(Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && (_jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
|
|
432
|
+
}, children: [_jsx(Thead, { position: "sticky", top: 0, zIndex: 4, bg: variantTokens.header.bg, children: _jsx(TableHeader, { columns: columnsList, isCheckbox: isCheckbox, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleSort: handleSort, headerRefs: headerRefs, columnWidths: columnWidths, slotRef: slotRef, slotWidth: slotWidth, columnsSort: columnsSort, noBorders: noBorders, handleCheckbox: handleCheckbox, isLoading: isTableLoading, checked: headerChecked, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, setColumnsSearch: setColumnsSearch, columnsSearch: columnsSearch, isSelecting: isSelecting }) }), _jsx(Tbody, { children: _jsx(TableBody, { data: isGrouped ? groupedSource : _filteredData, groups: renderGroups, onAddItem: onAddItem, columns: columnsList, startRow: isGrouped ? 0 : startRow, rowsPerPage: rowsPerPage, scrollContainerRef: tableContainerRef, isCheckbox: isCheckbox, columnWidths: columnWidths, slotWidth: slotWidth, noBorders: noBorders, freezedBgColor: freezedBgColor !== null && freezedBgColor !== void 0 ? freezedBgColor : theme.colors.backgroundColor.secondary, freezedTextColor: freezedTextColor !== null && freezedTextColor !== void 0 ? freezedTextColor : theme.colors.text[500], handleCheckbox: handleCheckbox, selections: selection, isLoading: isTableLoading, loadingSkeletonRows: loadingSkeletonRows, onRowClick: onRowClick, isContent: isContent, isLink: isLink, isActionFreeze: isActionFreeze, density: densityState, stripe: stripe, emptyState: emptyState }) })] }), canInfinite && isLoadingMore && (_jsxs(Flex, { justify: "center", align: "center", py: 3, gap: 2, children: [_jsx(Spinner, { size: "sm", color: theme.colors.accentText }), _jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: "Loading more\u2026" })] }))] }), groupLoadMoreActive && loadMorePosition === "bottom" && (groupLoadMoreCaption || canGroupLoadMore) && (_jsxs(Flex, { justify: "center", align: "center", gap: 3, py: 3, borderTop: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [groupLoadMoreCaption && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.text[500], children: groupLoadMoreCaption })), canGroupLoadMore && (_jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", isLoading: isLoadingMore, loadingText: loadMoreText, onClick: handleGroupLoadMore, label: loadMoreText }))] }))] })] })] })] }));
|
|
434
433
|
}
|
|
@@ -7,6 +7,8 @@ import { DEFAULT_PAGE_SIZE } from "../hooks/useTable.js";
|
|
|
7
7
|
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
8
8
|
import ToolTip from "../../ToolTip/ToolTip.js";
|
|
9
9
|
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, } from "lucide-react";
|
|
10
|
+
const STEP_SIZE = "1.5rem";
|
|
11
|
+
const STEP_ICON = 16;
|
|
10
12
|
const Pagination = React.memo(({ paginationText, handlePageSizeChange, rowsPerPage, currentPage, pages, setCurrentPage, dataLength, isServerPagination, paginationSelectOptions, }) => {
|
|
11
13
|
const computedOptions = useMemo(() => {
|
|
12
14
|
if (paginationSelectOptions && paginationSelectOptions.length) {
|
|
@@ -18,10 +20,11 @@ const Pagination = React.memo(({ paginationText, handlePageSizeChange, rowsPerPa
|
|
|
18
20
|
return [...sizes].sort((a, b) => a - b);
|
|
19
21
|
}, [paginationSelectOptions, dataLength, rowsPerPage]);
|
|
20
22
|
const theme = useCustomTheme();
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
const isLeftDisabled =
|
|
24
|
-
const isRightDisabled = dataLength === 0 || currentPage >=
|
|
25
|
-
|
|
23
|
+
const lastPage = Math.max(0, pages - 1);
|
|
24
|
+
// Stepping back is how a user escapes an empty page, so it survives an empty result.
|
|
25
|
+
const isLeftDisabled = currentPage === 0;
|
|
26
|
+
const isRightDisabled = dataLength === 0 || currentPage >= lastPage;
|
|
27
|
+
const step = (label, ariaLabel, icon, isDisabled, onClick) => (_jsx(ToolTip, { label: label, placement: "top", children: _jsx(IconButton, { "aria-label": ariaLabel, icon: icon, variant: "ghost", size: "xs", minW: STEP_SIZE, h: STEP_SIZE, rounded: "full", color: isDisabled ? theme.colors.textMuted : theme.colors.text[500], isDisabled: isDisabled, onClick: onClick, _hover: isDisabled ? {} : { color: theme.colors.accentText } }) }));
|
|
28
|
+
return (_jsxs(Flex, { justify: "flex-end", align: "center", flexWrap: "nowrap", gap: "0.75rem", children: [(!isServerPagination || paginationSelectOptions) && (_jsx(Select, { native: false, isSearchable: false, "aria-label": "Rows per page", onChange: (option) => option && handlePageSizeChange(Number(option.id)), value: rowsPerPage, size: "xs", width: "3.75rem", isDisabled: dataLength === 0, options: computedOptions.map((size) => ({ id: size, label: String(size) })) })), (dataLength > 0 || currentPage > 0) && (_jsxs(Flex, { align: "center", gap: "0.125rem", children: [step("First", "first-page", _jsx(ChevronsLeft, { size: STEP_ICON }), isLeftDisabled, () => setCurrentPage(0)), step("Previous", "previous-page", _jsx(ChevronLeft, { size: STEP_ICON }), isLeftDisabled, () => setCurrentPage((page) => Math.max(0, page - 1))), _jsx(Text, { px: "0.375rem", fontSize: "0.75rem", lineHeight: STEP_SIZE, whiteSpace: "nowrap", color: theme.colors.text[500], sx: { fontVariantNumeric: "tabular-nums" }, children: paginationText }), step("Next", "next-page", _jsx(ChevronRight, { size: STEP_ICON }), isRightDisabled, () => setCurrentPage((page) => Math.min(lastPage, page + 1))), step("Last", "last-page", _jsx(ChevronsRight, { size: STEP_ICON }), isRightDisabled, () => setCurrentPage(lastPage))] }))] }));
|
|
26
29
|
});
|
|
27
30
|
export default Pagination;
|