@lobehub/ui 5.60.0 → 5.61.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/es/EmojiPicker/AvatarUploader.mjs +1 -1
- package/es/Form/components/FormSubmitFooter.mjs +1 -1
- package/es/base-ui/Input/style.d.mts +1 -1
- package/es/base-ui/Pagination/Pagination.mjs +158 -41
- package/es/base-ui/Pagination/Pagination.mjs.map +1 -1
- package/es/base-ui/Pagination/helpers.mjs +3 -3
- package/es/base-ui/Pagination/helpers.mjs.map +1 -1
- package/es/base-ui/Pagination/index.d.mts +2 -2
- package/es/base-ui/Pagination/style.mjs +39 -0
- package/es/base-ui/Pagination/style.mjs.map +1 -1
- package/es/base-ui/Pagination/type.d.mts +42 -4
- package/es/base-ui/QRCode/QRCode.mjs +1 -1
- package/es/base-ui/Table/Column.d.mts +9 -0
- package/es/base-ui/Table/Column.mjs +7 -0
- package/es/base-ui/Table/Column.mjs.map +1 -0
- package/es/base-ui/Table/ColumnGroup.d.mts +12 -0
- package/es/base-ui/Table/ColumnGroup.mjs +7 -0
- package/es/base-ui/Table/ColumnGroup.mjs.map +1 -0
- package/es/base-ui/Table/ExpandIcon.mjs +33 -0
- package/es/base-ui/Table/ExpandIcon.mjs.map +1 -0
- package/es/base-ui/Table/FilterDropdown.mjs +284 -0
- package/es/base-ui/Table/FilterDropdown.mjs.map +1 -0
- package/es/base-ui/Table/LoadingSkeleton.mjs +83 -0
- package/es/base-ui/Table/LoadingSkeleton.mjs.map +1 -0
- package/es/base-ui/Table/Table.d.mts +21 -3
- package/es/base-ui/Table/Table.mjs +444 -97
- package/es/base-ui/Table/Table.mjs.map +1 -1
- package/es/base-ui/Table/columnUtils.mjs +56 -0
- package/es/base-ui/Table/columnUtils.mjs.map +1 -0
- package/es/base-ui/Table/index.d.mts +4 -2
- package/es/base-ui/Table/selectionUtils.d.mts +9 -0
- package/es/base-ui/Table/selectionUtils.mjs +128 -0
- package/es/base-ui/Table/selectionUtils.mjs.map +1 -0
- package/es/base-ui/Table/semantic.mjs +55 -0
- package/es/base-ui/Table/semantic.mjs.map +1 -0
- package/es/base-ui/Table/style.mjs +801 -161
- package/es/base-ui/Table/style.mjs.map +1 -1
- package/es/base-ui/Table/type.d.mts +471 -37
- package/es/base-ui/Table/useBreakpoint.mjs +41 -0
- package/es/base-ui/Table/useBreakpoint.mjs.map +1 -0
- package/es/base-ui/Table/useFilter.mjs +64 -0
- package/es/base-ui/Table/useFilter.mjs.map +1 -0
- package/es/base-ui/Table/useSelection.mjs +421 -0
- package/es/base-ui/Table/useSelection.mjs.map +1 -0
- package/es/base-ui/Table/useSorter.mjs +152 -0
- package/es/base-ui/Table/useSorter.mjs.map +1 -0
- package/es/base-ui/Table/useTableState.mjs +82 -55
- package/es/base-ui/Table/useTableState.mjs.map +1 -1
- package/es/base-ui/index.d.mts +6 -4
- package/es/base-ui/index.mjs +9 -9
- package/es/chat/EditableMessageList/EditableMessageList.mjs +1 -1
- package/es/chat/MessageInput/MessageInput.mjs +1 -1
- package/es/chat/MessageModal/MessageModal.mjs +1 -1
- package/es/chat/TokenTag/TokenTag.mjs +1 -1
- package/es/i18n/resources/en/index.d.mts +3 -2
- package/es/i18n/resources/en/index.mjs +4 -2
- package/es/i18n/resources/en/index.mjs.map +1 -1
- package/es/i18n/resources/en/table.d.mts +23 -0
- package/es/i18n/resources/en/table.mjs +24 -0
- package/es/i18n/resources/en/table.mjs.map +1 -0
- package/es/i18n/resources/zhCn/index.d.mts +3 -2
- package/es/i18n/resources/zhCn/index.mjs +4 -2
- package/es/i18n/resources/zhCn/index.mjs.map +1 -1
- package/es/i18n/resources/zhCn/table.d.mts +23 -0
- package/es/i18n/resources/zhCn/table.mjs +24 -0
- package/es/i18n/resources/zhCn/table.mjs.map +1 -0
- package/es/i18n/types.d.mts +2 -1
- package/es/index.mjs +2 -2
- package/package.json +2 -2
- package/es/base-ui/Table/FilterMenu.mjs +0 -45
- package/es/base-ui/Table/FilterMenu.mjs.map +0 -1
- package/es/base-ui/Table/TableBody.mjs +0 -59
- package/es/base-ui/Table/TableBody.mjs.map +0 -1
- package/es/base-ui/Table/TableHead.mjs +0 -50
- package/es/base-ui/Table/TableHead.mjs.map +0 -1
- package/es/base-ui/Table/features.mjs +0 -12
- package/es/base-ui/Table/features.mjs.map +0 -1
- package/es/base-ui/Table/fixedOffsets.mjs +0 -29
- package/es/base-ui/Table/fixedOffsets.mjs.map +0 -1
- package/es/base-ui/Table/toColumnDefs.mjs +0 -26
- package/es/base-ui/Table/toColumnDefs.mjs.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Icon from "../../Icon/Icon.mjs";
|
|
3
|
+
import Button from "../Button/Button.mjs";
|
|
4
|
+
import ActionIcon from "../ActionIcon/ActionIcon.mjs";
|
|
5
|
+
import Input_default from "../Input/Input.mjs";
|
|
6
|
+
import { PopoverPopup, PopoverPortal, PopoverPositioner, PopoverRoot, PopoverTriggerElement } from "../Popover/atoms.mjs";
|
|
7
|
+
import Empty from "../../Empty/Empty.mjs";
|
|
8
|
+
import Checkbox_default from "../Checkbox/Checkbox.mjs";
|
|
9
|
+
import Radio from "../Radio/Radio.mjs";
|
|
10
|
+
import { conductCheck, getAllKeys } from "../Tree/utils.mjs";
|
|
11
|
+
import Tree from "../Tree/Tree.mjs";
|
|
12
|
+
import { PREFIX_CLS, styles } from "./style.mjs";
|
|
13
|
+
import { flattenFilterKeys } from "./useFilter.mjs";
|
|
14
|
+
import { createContext, use, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
15
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
import { cx } from "antd-style";
|
|
17
|
+
import { ListFilter, Search } from "lucide-react";
|
|
18
|
+
import { RadioGroup } from "@base-ui/react/radio-group";
|
|
19
|
+
//#region src/base-ui/Table/FilterDropdown.tsx
|
|
20
|
+
const FILTER_CLS = `${PREFIX_CLS}-filter`;
|
|
21
|
+
/** True inside rc-table's hidden measure row, where only a static trigger is rendered. */
|
|
22
|
+
const TableMeasureRowContext = createContext(false);
|
|
23
|
+
const sameKeys = (a, b) => (a ?? null) === (b ?? null) || Boolean(a) && Boolean(b) && a.length === b.length && a.every((key, i) => key === b[i]);
|
|
24
|
+
const textMatches = (search, text) => (typeof text === "string" || typeof text === "number") && String(text).toLowerCase().includes(search);
|
|
25
|
+
const DEPRECATED_PROPS = [["filterDropdownOpen", "filterDropdownProps.open"], ["onFilterDropdownOpenChange", "filterDropdownProps.onOpenChange"]];
|
|
26
|
+
const warnedProps = /* @__PURE__ */ new Set();
|
|
27
|
+
/** Warns once per deprecated column prop, in development (antd `warning.deprecated`). */
|
|
28
|
+
const warnDeprecated = (column) => {
|
|
29
|
+
if (process.env.NODE_ENV === "production") return;
|
|
30
|
+
for (const [name, replacement] of DEPRECATED_PROPS) if (name in column && !warnedProps.has(name)) {
|
|
31
|
+
warnedProps.add(name);
|
|
32
|
+
console.warn(`[Table] \`${name}\` is deprecated. Please use \`${replacement}\` instead.`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/** Keys of `key` and its descendants, plus the keys of its ancestors. */
|
|
36
|
+
const findLineage = (nodes, key, ancestors = []) => {
|
|
37
|
+
for (const node of nodes) {
|
|
38
|
+
if (node.key === key) return {
|
|
39
|
+
ancestors,
|
|
40
|
+
subtree: getAllKeys([node])
|
|
41
|
+
};
|
|
42
|
+
const found = node.children && findLineage(node.children, key, [...ancestors, node.key]);
|
|
43
|
+
if (found) return found;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/** Draft keys that re-render on write and can be read synchronously (antd `useSyncState`). */
|
|
47
|
+
const useSyncKeys = (initial) => {
|
|
48
|
+
const ref = useRef(initial);
|
|
49
|
+
const [, rerender] = useReducer((count) => count + 1, 0);
|
|
50
|
+
return [() => ref.current, (next) => {
|
|
51
|
+
ref.current = next;
|
|
52
|
+
rerender();
|
|
53
|
+
}];
|
|
54
|
+
};
|
|
55
|
+
const FilterDropdown = ({ column, columnKey, filterState, getPopupContainer, label, locale, triggerFilter }) => {
|
|
56
|
+
const { defaultFilteredValue, filterDropdown, filterDropdownOpen, filterDropdownProps = {}, filterIcon, filterMode = "menu", filterMultiple = true, filterOnClose = true, filterResetToDefaultFilteredValue, filters, filterSearch = false, onFilterDropdownOpenChange } = column;
|
|
57
|
+
const inMeasureRow = use(TableMeasureRowContext);
|
|
58
|
+
warnDeprecated(column);
|
|
59
|
+
const triggerRef = useRef(null);
|
|
60
|
+
const [visible, setVisible] = useState(false);
|
|
61
|
+
const [searchValue, setSearchValue] = useState("");
|
|
62
|
+
const normalizedSearch = searchValue.trim().toLowerCase();
|
|
63
|
+
const mergedVisible = filterDropdownProps.open ?? filterDropdownOpen ?? visible;
|
|
64
|
+
const filtered = Boolean(filterState?.filteredKeys?.length || filterState?.forceFiltered);
|
|
65
|
+
const triggerOpen = (next) => {
|
|
66
|
+
setVisible(next);
|
|
67
|
+
setSearchValue("");
|
|
68
|
+
filterDropdownProps.onOpenChange?.(next);
|
|
69
|
+
onFilterDropdownOpenChange?.(next);
|
|
70
|
+
};
|
|
71
|
+
const propFilteredKeys = filterState?.filteredKeys;
|
|
72
|
+
const [getSelectedKeys, setSelectedKeys] = useSyncKeys(propFilteredKeys ?? []);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (mergedVisible) setSelectedKeys(propFilteredKeys ?? []);
|
|
75
|
+
}, [propFilteredKeys, mergedVisible]);
|
|
76
|
+
const internalTriggerFilter = (keys) => {
|
|
77
|
+
const mergedKeys = keys?.length ? keys : null;
|
|
78
|
+
if (mergedKeys === null && !propFilteredKeys) return;
|
|
79
|
+
if (sameKeys(mergedKeys, propFilteredKeys)) return;
|
|
80
|
+
triggerFilter(columnKey, mergedKeys);
|
|
81
|
+
};
|
|
82
|
+
const onConfirm = () => {
|
|
83
|
+
triggerOpen(false);
|
|
84
|
+
internalTriggerFilter(getSelectedKeys());
|
|
85
|
+
};
|
|
86
|
+
const onReset = ({ closeDropdown = false, confirm = false } = {}) => {
|
|
87
|
+
if (confirm) internalTriggerFilter([]);
|
|
88
|
+
if (closeDropdown) triggerOpen(false);
|
|
89
|
+
setSearchValue("");
|
|
90
|
+
setSelectedKeys(filterResetToDefaultFilteredValue ? (defaultFilteredValue ?? []).map(String) : []);
|
|
91
|
+
};
|
|
92
|
+
const doFilter = ({ closeDropdown = true } = {}) => {
|
|
93
|
+
if (closeDropdown) triggerOpen(false);
|
|
94
|
+
internalTriggerFilter(getSelectedKeys());
|
|
95
|
+
};
|
|
96
|
+
/** Opening / closing from the trigger, an outside press or Escape. */
|
|
97
|
+
const onPopoverOpenChange = (next) => {
|
|
98
|
+
if (next && propFilteredKeys !== void 0) setSelectedKeys(propFilteredKeys ?? []);
|
|
99
|
+
if (!next && !filterDropdown && filterOnClose) onConfirm();
|
|
100
|
+
else triggerOpen(next);
|
|
101
|
+
};
|
|
102
|
+
const selectedKeys = getSelectedKeys();
|
|
103
|
+
const selectedSet = new Set(selectedKeys.map(String));
|
|
104
|
+
const allKeys = useMemo(() => flattenFilterKeys(filters).map(String), [filters]);
|
|
105
|
+
const matches = (item) => typeof filterSearch === "function" ? filterSearch(normalizedSearch, item) : textMatches(normalizedSearch, item.text);
|
|
106
|
+
const toggleKey = (key, checked) => {
|
|
107
|
+
if (!filterMultiple) return setSelectedKeys(checked ? [key] : []);
|
|
108
|
+
const rest = selectedKeys.filter((item) => String(item) !== key);
|
|
109
|
+
setSelectedKeys(checked ? [...rest, key] : rest);
|
|
110
|
+
};
|
|
111
|
+
const renderMenuItems = (items) => items.flatMap((item, index) => {
|
|
112
|
+
const key = String(item.value);
|
|
113
|
+
if (item.children) {
|
|
114
|
+
const children = renderMenuItems(item.children);
|
|
115
|
+
if (children.length === 0) return [];
|
|
116
|
+
return [/* @__PURE__ */ jsxs("div", {
|
|
117
|
+
className: styles.filterGroup,
|
|
118
|
+
role: "group",
|
|
119
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
120
|
+
className: cx(styles.filterGroupTitle, `${FILTER_CLS}-dropdown-submenu-title`),
|
|
121
|
+
children: item.text
|
|
122
|
+
}), children]
|
|
123
|
+
}, key || index)];
|
|
124
|
+
}
|
|
125
|
+
if (normalizedSearch && !matches(item)) return [];
|
|
126
|
+
return [/* @__PURE__ */ jsx("div", {
|
|
127
|
+
className: cx(styles.filterItem, `${FILTER_CLS}-dropdown-item`),
|
|
128
|
+
children: filterMultiple ? /* @__PURE__ */ jsx(Checkbox_default, {
|
|
129
|
+
checked: selectedSet.has(key),
|
|
130
|
+
onChange: (checked) => toggleKey(key, checked),
|
|
131
|
+
children: item.text
|
|
132
|
+
}) : /* @__PURE__ */ jsx(Radio, {
|
|
133
|
+
value: key,
|
|
134
|
+
children: item.text
|
|
135
|
+
})
|
|
136
|
+
}, key)];
|
|
137
|
+
});
|
|
138
|
+
const toTreeData = (items, ancestorMatched = false) => (items ?? []).flatMap((item, index) => {
|
|
139
|
+
const selfMatched = ancestorMatched || !normalizedSearch || matches(item);
|
|
140
|
+
const children = item.children ? toTreeData(item.children, selfMatched) : void 0;
|
|
141
|
+
if (!selfMatched && !children?.length) return [];
|
|
142
|
+
return [{
|
|
143
|
+
children,
|
|
144
|
+
key: item.value !== void 0 ? String(item.value) : String(index),
|
|
145
|
+
title: item.text
|
|
146
|
+
}];
|
|
147
|
+
});
|
|
148
|
+
const renderSearch = () => filterSearch ? /* @__PURE__ */ jsx("div", {
|
|
149
|
+
className: cx(styles.filterSearch, `${PREFIX_CLS}-filter-dropdown-search`),
|
|
150
|
+
children: /* @__PURE__ */ jsx(Input_default, {
|
|
151
|
+
allowClear: true,
|
|
152
|
+
placeholder: locale.filterSearchPlaceholder,
|
|
153
|
+
prefix: /* @__PURE__ */ jsx(Icon, {
|
|
154
|
+
icon: Search,
|
|
155
|
+
size: 14
|
|
156
|
+
}),
|
|
157
|
+
size: "small",
|
|
158
|
+
value: searchValue,
|
|
159
|
+
onChange: (event) => setSearchValue(event.target.value)
|
|
160
|
+
})
|
|
161
|
+
}) : null;
|
|
162
|
+
const empty = /* @__PURE__ */ jsx(Empty, {
|
|
163
|
+
className: styles.filterEmpty,
|
|
164
|
+
description: locale.filterEmptyText,
|
|
165
|
+
imageSize: 24
|
|
166
|
+
});
|
|
167
|
+
let content;
|
|
168
|
+
if (typeof filterDropdown === "function") content = filterDropdown({
|
|
169
|
+
clearFilters: onReset,
|
|
170
|
+
close: () => triggerOpen(false),
|
|
171
|
+
confirm: doFilter,
|
|
172
|
+
filters,
|
|
173
|
+
prefixCls: `${FILTER_CLS}-dropdown-custom`,
|
|
174
|
+
selectedKeys,
|
|
175
|
+
setSelectedKeys,
|
|
176
|
+
visible: mergedVisible
|
|
177
|
+
});
|
|
178
|
+
else if (filterDropdown !== void 0) content = filterDropdown;
|
|
179
|
+
else {
|
|
180
|
+
let body;
|
|
181
|
+
if (!filters?.length) body = empty;
|
|
182
|
+
else if (filterMode === "tree") {
|
|
183
|
+
const treeData = toTreeData(filters);
|
|
184
|
+
const fullTreeData = normalizedSearch ? toTreeData(filters, true) : treeData;
|
|
185
|
+
const onTreeCheck = (checked, node) => {
|
|
186
|
+
if (!filterMultiple) return setSelectedKeys(checked ? [node.key] : []);
|
|
187
|
+
const lineage = findLineage(fullTreeData, node.key);
|
|
188
|
+
const base = conductCheck(fullTreeData, selectedKeys.map(String)).checked;
|
|
189
|
+
if (checked) lineage?.subtree.forEach((key) => base.add(key));
|
|
190
|
+
else [...lineage?.subtree ?? [], ...lineage?.ancestors ?? []].forEach((key) => base.delete(key));
|
|
191
|
+
setSelectedKeys([...conductCheck(fullTreeData, base).checked]);
|
|
192
|
+
};
|
|
193
|
+
body = /* @__PURE__ */ jsxs(Fragment$1, { children: [renderSearch(), /* @__PURE__ */ jsxs("div", {
|
|
194
|
+
className: cx(styles.filterTree, `${PREFIX_CLS}-filter-dropdown-tree`),
|
|
195
|
+
children: [filterMultiple && /* @__PURE__ */ jsx(Checkbox_default, {
|
|
196
|
+
checked: selectedKeys.length > 0 && selectedKeys.length === allKeys.length,
|
|
197
|
+
className: cx(styles.filterCheckAll, `lobe-table-filter-dropdown-checkall`),
|
|
198
|
+
indeterminate: selectedKeys.length > 0 && selectedKeys.length < allKeys.length,
|
|
199
|
+
onChange: (checked) => setSelectedKeys(checked ? allKeys : []),
|
|
200
|
+
children: locale.filterCheckAll
|
|
201
|
+
}), treeData.length > 0 ? /* @__PURE__ */ jsx(Tree, {
|
|
202
|
+
checkable: true,
|
|
203
|
+
defaultExpandAll: true,
|
|
204
|
+
checkStrictly: !filterMultiple || Boolean(normalizedSearch),
|
|
205
|
+
checkedKeys: selectedKeys.map(String),
|
|
206
|
+
multiple: filterMultiple,
|
|
207
|
+
selectedKeys: [],
|
|
208
|
+
size: "small",
|
|
209
|
+
treeData,
|
|
210
|
+
onCheck: (_, { checked, node }) => onTreeCheck(checked, node)
|
|
211
|
+
}) : empty]
|
|
212
|
+
})] });
|
|
213
|
+
} else {
|
|
214
|
+
const items = renderMenuItems(filters);
|
|
215
|
+
const list = /* @__PURE__ */ jsx("div", {
|
|
216
|
+
className: cx(styles.filterMenu, `${FILTER_CLS}-dropdown-menu`),
|
|
217
|
+
children: items
|
|
218
|
+
});
|
|
219
|
+
body = /* @__PURE__ */ jsxs(Fragment$1, { children: [renderSearch(), items.length === 0 ? empty : filterMultiple ? list : /* @__PURE__ */ jsx(RadioGroup, {
|
|
220
|
+
value: selectedKeys.length ? String(selectedKeys[0]) : null,
|
|
221
|
+
onValueChange: (value) => setSelectedKeys(value == null ? [] : [String(value)]),
|
|
222
|
+
children: list
|
|
223
|
+
})] });
|
|
224
|
+
}
|
|
225
|
+
const resetDisabled = filterResetToDefaultFilteredValue ? sameKeys((defaultFilteredValue ?? []).map(String), selectedKeys.map(String)) : selectedKeys.length === 0;
|
|
226
|
+
content = /* @__PURE__ */ jsxs(Fragment$1, { children: [body, /* @__PURE__ */ jsxs("div", {
|
|
227
|
+
className: cx(styles.filterButtons, `${FILTER_CLS}-dropdown-btns`),
|
|
228
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
229
|
+
disabled: resetDisabled,
|
|
230
|
+
size: "small",
|
|
231
|
+
type: "link",
|
|
232
|
+
onClick: () => onReset(),
|
|
233
|
+
children: locale.filterReset
|
|
234
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
235
|
+
size: "small",
|
|
236
|
+
type: "primary",
|
|
237
|
+
onClick: onConfirm,
|
|
238
|
+
children: locale.filterConfirm
|
|
239
|
+
})]
|
|
240
|
+
})] });
|
|
241
|
+
}
|
|
242
|
+
const popupRender = filterDropdownProps.popupRender ?? filterDropdownProps.dropdownRender;
|
|
243
|
+
const panel = /* @__PURE__ */ jsx("div", {
|
|
244
|
+
className: cx(styles.filterDropdown, `${FILTER_CLS}-dropdown`),
|
|
245
|
+
children: popupRender ? popupRender(content) : content
|
|
246
|
+
});
|
|
247
|
+
const icon = typeof filterIcon === "function" ? filterIcon(filtered) : filterIcon ?? ListFilter;
|
|
248
|
+
const trigger = /* @__PURE__ */ jsx(ActionIcon, {
|
|
249
|
+
active: filtered,
|
|
250
|
+
"aria-description": locale.filterTitle,
|
|
251
|
+
"aria-label": `Filter ${label}`,
|
|
252
|
+
className: cx(styles.filterButton, `${FILTER_CLS}-trigger`, filtered && styles.filterActive),
|
|
253
|
+
"data-active": filtered || void 0,
|
|
254
|
+
icon,
|
|
255
|
+
ref: triggerRef,
|
|
256
|
+
size: "small",
|
|
257
|
+
onClick: (event) => event.stopPropagation()
|
|
258
|
+
});
|
|
259
|
+
if (inMeasureRow) return trigger;
|
|
260
|
+
const resolveContainer = filterDropdownProps.getPopupContainer ?? getPopupContainer;
|
|
261
|
+
const container = mergedVisible && resolveContainer && triggerRef.current ? resolveContainer(triggerRef.current) : void 0;
|
|
262
|
+
return /* @__PURE__ */ jsxs(PopoverRoot, {
|
|
263
|
+
open: mergedVisible,
|
|
264
|
+
onOpenChange: onPopoverOpenChange,
|
|
265
|
+
children: [/* @__PURE__ */ jsx(PopoverTriggerElement, { children: trigger }), /* @__PURE__ */ jsx(PopoverPortal, {
|
|
266
|
+
container,
|
|
267
|
+
children: /* @__PURE__ */ jsx(PopoverPositioner, {
|
|
268
|
+
className: filterDropdownProps.rootClassName,
|
|
269
|
+
placement: filterDropdownProps.placement ?? "bottomRight",
|
|
270
|
+
sideOffset: 4,
|
|
271
|
+
style: filterDropdownProps.zIndex === void 0 ? void 0 : { zIndex: filterDropdownProps.zIndex },
|
|
272
|
+
children: /* @__PURE__ */ jsx(PopoverPopup, {
|
|
273
|
+
className: cx(styles.filterPopup, filterDropdownProps.className),
|
|
274
|
+
style: filterDropdownProps.style,
|
|
275
|
+
children: panel
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
})]
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
//#endregion
|
|
282
|
+
export { TableMeasureRowContext, FilterDropdown as default };
|
|
283
|
+
|
|
284
|
+
//# sourceMappingURL=FilterDropdown.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterDropdown.mjs","names":["Checkbox","Input","BaseRadioGroup"],"sources":["../../../src/base-ui/Table/FilterDropdown.tsx"],"sourcesContent":["'use client';\n\nimport { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';\nimport { cx } from 'antd-style';\nimport { ListFilter, Search } from 'lucide-react';\nimport {\n createContext,\n type Key,\n type ReactNode,\n use,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\n\nimport ActionIcon from '@/base-ui/ActionIcon';\nimport Button from '@/base-ui/Button';\nimport { Checkbox } from '@/base-ui/Checkbox';\nimport { Input } from '@/base-ui/Input';\nimport {\n PopoverPopup,\n PopoverPortal,\n PopoverPositioner,\n PopoverRoot,\n PopoverTriggerElement,\n} from '@/base-ui/Popover';\nimport { Radio } from '@/base-ui/Radio';\nimport Tree, { conductCheck, getAllKeys, type TreeDataNode } from '@/base-ui/Tree';\nimport Empty from '@/Empty';\nimport Icon from '@/Icon';\n\nimport { PREFIX_CLS, styles } from './style';\nimport type {\n TableColumnFilterItem,\n TableColumnType,\n TableFilterDropdownProps,\n TableLocale,\n} from './type';\nimport { type FilterKeys, type FilterState, flattenFilterKeys } from './useFilter';\n\nconst FILTER_CLS = `${PREFIX_CLS}-filter`;\n\n/** True inside rc-table's hidden measure row, where only a static trigger is rendered. */\nexport const TableMeasureRowContext = createContext(false);\n\nconst sameKeys = (a: readonly Key[] | null | undefined, b: readonly Key[] | null | undefined) =>\n (a ?? null) === (b ?? null) ||\n (Boolean(a) && Boolean(b) && a!.length === b!.length && a!.every((key, i) => key === b![i]));\n\nconst textMatches = (search: string, text: ReactNode) =>\n (typeof text === 'string' || typeof text === 'number') &&\n String(text).toLowerCase().includes(search);\n\nconst DEPRECATED_PROPS = [\n ['filterDropdownOpen', 'filterDropdownProps.open'],\n ['onFilterDropdownOpenChange', 'filterDropdownProps.onOpenChange'],\n] as const;\nconst warnedProps = new Set<string>();\n\n/** Warns once per deprecated column prop, in development (antd `warning.deprecated`). */\nconst warnDeprecated = (column: object) => {\n if (process.env.NODE_ENV === 'production') return;\n for (const [name, replacement] of DEPRECATED_PROPS) {\n if (name in column && !warnedProps.has(name)) {\n warnedProps.add(name);\n console.warn(`[Table] \\`${name}\\` is deprecated. Please use \\`${replacement}\\` instead.`);\n }\n }\n};\n\n/** Keys of `key` and its descendants, plus the keys of its ancestors. */\nconst findLineage = (\n nodes: readonly TreeDataNode[],\n key: string,\n ancestors: string[] = [],\n): { ancestors: string[]; subtree: string[] } | undefined => {\n for (const node of nodes) {\n if (node.key === key) return { ancestors, subtree: getAllKeys([node]) };\n const found = node.children && findLineage(node.children, key, [...ancestors, node.key]);\n if (found) return found;\n }\n};\n\n/** Draft keys that re-render on write and can be read synchronously (antd `useSyncState`). */\nconst useSyncKeys = (initial: Key[]) => {\n const ref = useRef(initial);\n const [, rerender] = useReducer((count: number) => count + 1, 0);\n return [\n () => ref.current,\n (next: Key[]) => {\n ref.current = next;\n rerender();\n },\n ] as const;\n};\n\ninterface FilterDropdownProps<T> {\n column: TableColumnType<T>;\n columnKey: string;\n filterState?: FilterState<T>;\n getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;\n label: string;\n locale: Pick<\n TableLocale,\n | 'filterCheckAll'\n | 'filterConfirm'\n | 'filterEmptyText'\n | 'filterReset'\n | 'filterSearchPlaceholder'\n | 'filterTitle'\n >;\n triggerFilter: (columnKey: string, filteredKeys: FilterKeys) => void;\n}\n\nconst FilterDropdown = <T,>({\n column,\n columnKey,\n filterState,\n getPopupContainer,\n label,\n locale,\n triggerFilter,\n}: FilterDropdownProps<T>) => {\n const {\n defaultFilteredValue,\n filterDropdown,\n filterDropdownOpen,\n filterDropdownProps = {},\n filterIcon,\n filterMode = 'menu',\n filterMultiple = true,\n filterOnClose = true,\n filterResetToDefaultFilteredValue,\n filters,\n filterSearch = false,\n onFilterDropdownOpenChange,\n } = column;\n\n const inMeasureRow = use(TableMeasureRowContext);\n warnDeprecated(column);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const [visible, setVisible] = useState(false);\n const [searchValue, setSearchValue] = useState('');\n const normalizedSearch = searchValue.trim().toLowerCase();\n const mergedVisible = filterDropdownProps.open ?? filterDropdownOpen ?? visible;\n const filtered = Boolean(filterState?.filteredKeys?.length || filterState?.forceFiltered);\n\n const triggerOpen = (next: boolean) => {\n setVisible(next);\n // The search box starts empty every time the panel opens.\n setSearchValue('');\n filterDropdownProps.onOpenChange?.(next);\n onFilterDropdownOpenChange?.(next);\n };\n\n // ============================ Draft keys ============================\n // Booleans only come from `filteredValue`; the panel works with keys, like antd.\n const propFilteredKeys = filterState?.filteredKeys as Key[] | null | undefined;\n const [getSelectedKeys, setSelectedKeys] = useSyncKeys(propFilteredKeys ?? []);\n\n useEffect(() => {\n if (mergedVisible) setSelectedKeys(propFilteredKeys ?? []);\n // `setSelectedKeys` is stable in behavior; re-sync only on open / applied-keys change.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [propFilteredKeys, mergedVisible]);\n\n // ============================== Submit ==============================\n const internalTriggerFilter = (keys: Key[] | null) => {\n const mergedKeys = keys?.length ? keys : null;\n if (mergedKeys === null && !propFilteredKeys) return;\n if (sameKeys(mergedKeys, propFilteredKeys)) return;\n triggerFilter(columnKey, mergedKeys);\n };\n\n const onConfirm = () => {\n triggerOpen(false);\n internalTriggerFilter(getSelectedKeys());\n };\n\n const onReset: NonNullable<TableFilterDropdownProps['clearFilters']> = ({\n closeDropdown = false,\n confirm = false,\n } = {}) => {\n if (confirm) internalTriggerFilter([]);\n if (closeDropdown) triggerOpen(false);\n setSearchValue('');\n setSelectedKeys(\n filterResetToDefaultFilteredValue ? (defaultFilteredValue ?? []).map(String) : [],\n );\n };\n\n const doFilter: TableFilterDropdownProps['confirm'] = ({ closeDropdown = true } = {}) => {\n if (closeDropdown) triggerOpen(false);\n internalTriggerFilter(getSelectedKeys());\n };\n\n /** Opening / closing from the trigger, an outside press or Escape. */\n const onPopoverOpenChange = (next: boolean) => {\n if (next && propFilteredKeys !== undefined) setSelectedKeys(propFilteredKeys ?? []);\n if (!next && !filterDropdown && filterOnClose) onConfirm();\n else triggerOpen(next);\n };\n\n // ============================== Content =============================\n const selectedKeys = getSelectedKeys();\n const selectedSet = new Set(selectedKeys.map(String));\n const allKeys = useMemo(() => flattenFilterKeys(filters).map(String), [filters]);\n\n const matches = (item: TableColumnFilterItem) =>\n typeof filterSearch === 'function'\n ? filterSearch(normalizedSearch, item)\n : textMatches(normalizedSearch, item.text);\n\n const toggleKey = (key: string, checked: boolean) => {\n if (!filterMultiple) return setSelectedKeys(checked ? [key] : []);\n const rest = selectedKeys.filter((item) => String(item) !== key);\n setSelectedKeys(checked ? [...rest, key] : rest);\n };\n\n const renderMenuItems = (items: readonly TableColumnFilterItem[]): ReactNode[] =>\n items.flatMap((item, index) => {\n const key = String(item.value);\n if (item.children) {\n const children = renderMenuItems(item.children);\n if (children.length === 0) return [];\n return [\n <div className={styles.filterGroup} key={key || index} role=\"group\">\n <div className={cx(styles.filterGroupTitle, `${FILTER_CLS}-dropdown-submenu-title`)}>\n {item.text}\n </div>\n {children}\n </div>,\n ];\n }\n if (normalizedSearch && !matches(item)) return [];\n return [\n <div className={cx(styles.filterItem, `${FILTER_CLS}-dropdown-item`)} key={key}>\n {filterMultiple ? (\n <Checkbox\n checked={selectedSet.has(key)}\n onChange={(checked) => toggleKey(key, checked)}\n >\n {item.text}\n </Checkbox>\n ) : (\n <Radio value={key}>{item.text}</Radio>\n )}\n </div>,\n ];\n });\n\n const toTreeData = (\n items: readonly TableColumnFilterItem[] | undefined,\n ancestorMatched = false,\n ): TreeDataNode[] =>\n (items ?? []).flatMap((item, index) => {\n const selfMatched = ancestorMatched || !normalizedSearch || matches(item);\n const children = item.children ? toTreeData(item.children, selfMatched) : undefined;\n if (!selfMatched && !children?.length) return [];\n return [\n {\n children,\n key: item.value !== undefined ? String(item.value) : String(index),\n title: item.text,\n },\n ];\n });\n\n const renderSearch = () =>\n filterSearch ? (\n <div className={cx(styles.filterSearch, `${PREFIX_CLS}-filter-dropdown-search`)}>\n <Input\n allowClear\n placeholder={locale.filterSearchPlaceholder}\n prefix={<Icon icon={Search} size={14} />}\n size=\"small\"\n value={searchValue}\n onChange={(event) => setSearchValue(event.target.value)}\n />\n </div>\n ) : null;\n\n const empty = (\n <Empty className={styles.filterEmpty} description={locale.filterEmptyText} imageSize={24} />\n );\n\n let content: ReactNode;\n if (typeof filterDropdown === 'function') {\n content = filterDropdown({\n clearFilters: onReset,\n close: () => triggerOpen(false),\n confirm: doFilter,\n filters,\n prefixCls: `${FILTER_CLS}-dropdown-custom`,\n selectedKeys,\n setSelectedKeys,\n visible: mergedVisible,\n });\n } else if (filterDropdown !== undefined) {\n content = filterDropdown;\n } else {\n let body: ReactNode;\n if (!filters?.length) {\n body = empty;\n } else if (filterMode === 'tree') {\n const treeData = toTreeData(filters);\n // Searching prunes the tree, so cascade checks on the full tree and show the keys as is.\n const fullTreeData = normalizedSearch ? toTreeData(filters, true) : treeData;\n const onTreeCheck = (checked: boolean, node: TreeDataNode) => {\n if (!filterMultiple) return setSelectedKeys(checked ? [node.key] : []);\n const lineage = findLineage(fullTreeData, node.key);\n const base = conductCheck(fullTreeData, selectedKeys.map(String)).checked;\n if (checked) lineage?.subtree.forEach((key) => base.add(key));\n else\n [...(lineage?.subtree ?? []), ...(lineage?.ancestors ?? [])].forEach((key) =>\n base.delete(key),\n );\n setSelectedKeys([...conductCheck(fullTreeData, base).checked]);\n };\n body = (\n <>\n {renderSearch()}\n <div className={cx(styles.filterTree, `${PREFIX_CLS}-filter-dropdown-tree`)}>\n {filterMultiple && (\n <Checkbox\n checked={selectedKeys.length > 0 && selectedKeys.length === allKeys.length}\n className={cx(styles.filterCheckAll, `${PREFIX_CLS}-filter-dropdown-checkall`)}\n indeterminate={selectedKeys.length > 0 && selectedKeys.length < allKeys.length}\n onChange={(checked) => setSelectedKeys(checked ? allKeys : [])}\n >\n {locale.filterCheckAll}\n </Checkbox>\n )}\n {treeData.length > 0 ? (\n <Tree\n checkable\n defaultExpandAll\n checkStrictly={!filterMultiple || Boolean(normalizedSearch)}\n checkedKeys={selectedKeys.map(String)}\n multiple={filterMultiple}\n selectedKeys={[]}\n size=\"small\"\n treeData={treeData}\n onCheck={(_, { checked, node }) => onTreeCheck(checked, node)}\n />\n ) : (\n empty\n )}\n </div>\n </>\n );\n } else {\n const items = renderMenuItems(filters);\n const list = (\n <div className={cx(styles.filterMenu, `${FILTER_CLS}-dropdown-menu`)}>{items}</div>\n );\n body = (\n <>\n {renderSearch()}\n {items.length === 0 ? (\n empty\n ) : filterMultiple ? (\n list\n ) : (\n <BaseRadioGroup\n value={selectedKeys.length ? String(selectedKeys[0]) : null}\n onValueChange={(value) => setSelectedKeys(value == null ? [] : [String(value)])}\n >\n {list}\n </BaseRadioGroup>\n )}\n </>\n );\n }\n\n const resetDisabled = filterResetToDefaultFilteredValue\n ? sameKeys((defaultFilteredValue ?? []).map(String), selectedKeys.map(String))\n : selectedKeys.length === 0;\n\n content = (\n <>\n {body}\n <div className={cx(styles.filterButtons, `${FILTER_CLS}-dropdown-btns`)}>\n <Button disabled={resetDisabled} size=\"small\" type=\"link\" onClick={() => onReset()}>\n {locale.filterReset}\n </Button>\n <Button size=\"small\" type=\"primary\" onClick={onConfirm}>\n {locale.filterConfirm}\n </Button>\n </div>\n </>\n );\n }\n\n const popupRender = filterDropdownProps.popupRender ?? filterDropdownProps.dropdownRender;\n const panel = (\n <div className={cx(styles.filterDropdown, `${FILTER_CLS}-dropdown`)}>\n {popupRender ? popupRender(content) : content}\n </div>\n );\n\n // ============================== Trigger =============================\n const icon = typeof filterIcon === 'function' ? filterIcon(filtered) : (filterIcon ?? ListFilter);\n\n // Native `title` would need ActionIcon's tooltip, which can't sit inside the popover trigger;\n // the localized hint rides on `aria-description` instead.\n const trigger = (\n <ActionIcon\n active={filtered}\n aria-description={locale.filterTitle}\n aria-label={`Filter ${label}`}\n className={cx(styles.filterButton, `${FILTER_CLS}-trigger`, filtered && styles.filterActive)}\n data-active={filtered || undefined}\n icon={icon}\n ref={triggerRef}\n size=\"small\"\n onClick={(event) => event.stopPropagation()}\n />\n );\n\n if (inMeasureRow) return trigger;\n\n const resolveContainer = filterDropdownProps.getPopupContainer ?? getPopupContainer;\n const container =\n mergedVisible && resolveContainer && triggerRef.current\n ? resolveContainer(triggerRef.current)\n : undefined;\n\n return (\n <PopoverRoot open={mergedVisible} onOpenChange={onPopoverOpenChange}>\n <PopoverTriggerElement>{trigger}</PopoverTriggerElement>\n <PopoverPortal container={container}>\n <PopoverPositioner\n className={filterDropdownProps.rootClassName}\n placement={filterDropdownProps.placement ?? 'bottomRight'}\n sideOffset={4}\n style={\n filterDropdownProps.zIndex === undefined\n ? undefined\n : { zIndex: filterDropdownProps.zIndex }\n }\n >\n <PopoverPopup\n className={cx(styles.filterPopup, filterDropdownProps.className)}\n style={filterDropdownProps.style}\n >\n {panel}\n </PopoverPopup>\n </PopoverPositioner>\n </PopoverPortal>\n </PopoverRoot>\n );\n};\n\nexport default FilterDropdown;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Skeleton from "../Skeleton/index.mjs";
|
|
3
|
+
import { isColumnGroup, isColumnVisibleAt } from "./columnUtils.mjs";
|
|
4
|
+
import { styles } from "./style.mjs";
|
|
5
|
+
import { memo, useMemo } from "react";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/base-ui/Table/LoadingSkeleton.tsx
|
|
8
|
+
const DEFAULT_ROWS = 5;
|
|
9
|
+
/** Varying bar widths so consecutive rows do not look identical. */
|
|
10
|
+
const CELL_WIDTHS = [
|
|
11
|
+
72,
|
|
12
|
+
56,
|
|
13
|
+
64,
|
|
14
|
+
48,
|
|
15
|
+
80,
|
|
16
|
+
40,
|
|
17
|
+
60,
|
|
18
|
+
52
|
|
19
|
+
];
|
|
20
|
+
const resolveTrack = (width) => {
|
|
21
|
+
if (typeof width === "number") return `${width}px`;
|
|
22
|
+
if (typeof width === "string" && width) return width;
|
|
23
|
+
return "minmax(0, 1fr)";
|
|
24
|
+
};
|
|
25
|
+
const leafColumns = (columns, screens) => columns.flatMap((column) => {
|
|
26
|
+
if (!isColumnVisibleAt(column, screens)) return [];
|
|
27
|
+
if (isColumnGroup(column)) return leafColumns(column.children, screens);
|
|
28
|
+
return [column];
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Body placeholder while `loading` and there are no rows. Uses the real leaf column
|
|
32
|
+
* widths (plus selection / expand tracks when those columns are on) so the skeleton
|
|
33
|
+
* lines up under the live header; row padding follows the table size CSS vars.
|
|
34
|
+
*/
|
|
35
|
+
function TableLoadingSkeletonInner({ columns, expandColumn = false, rows = DEFAULT_ROWS, screens, selectionColumn = false, size }) {
|
|
36
|
+
const tracks = useMemo(() => {
|
|
37
|
+
const list = [];
|
|
38
|
+
if (selectionColumn) list.push(`32px`);
|
|
39
|
+
if (expandColumn) list.push(`48px`);
|
|
40
|
+
const leaves = leafColumns(columns, screens);
|
|
41
|
+
if (leaves.length === 0) list.push("minmax(0, 1.4fr)", "minmax(0, 1fr)", "minmax(0, 0.8fr)", "minmax(0, 1fr)");
|
|
42
|
+
else leaves.forEach((column) => list.push(resolveTrack(column.width)));
|
|
43
|
+
return list;
|
|
44
|
+
}, [
|
|
45
|
+
columns,
|
|
46
|
+
expandColumn,
|
|
47
|
+
screens,
|
|
48
|
+
selectionColumn
|
|
49
|
+
]);
|
|
50
|
+
const gridTemplateColumns = tracks.join(" ");
|
|
51
|
+
const rowCount = Math.max(1, rows);
|
|
52
|
+
return /* @__PURE__ */ jsx("div", {
|
|
53
|
+
"aria-hidden": true,
|
|
54
|
+
className: styles.skeleton,
|
|
55
|
+
"data-table-skeleton": "",
|
|
56
|
+
"data-table-skeleton-size": size === "middle" ? "medium" : size,
|
|
57
|
+
children: Array.from({ length: rowCount }, (_, rowIndex) => /* @__PURE__ */ jsx("div", {
|
|
58
|
+
className: styles.skeletonRow,
|
|
59
|
+
style: { gridTemplateColumns },
|
|
60
|
+
children: tracks.map((_, colIndex) => {
|
|
61
|
+
const isLeading = selectionColumn && colIndex === 0 || expandColumn && colIndex === (selectionColumn ? 1 : 0);
|
|
62
|
+
const widthPct = isLeading ? 100 : CELL_WIDTHS[(rowIndex * 3 + colIndex) % CELL_WIDTHS.length];
|
|
63
|
+
return /* @__PURE__ */ jsx("div", {
|
|
64
|
+
className: styles.skeletonCell,
|
|
65
|
+
children: isLeading ? /* @__PURE__ */ jsx(Skeleton, {
|
|
66
|
+
height: 14,
|
|
67
|
+
radius: 4,
|
|
68
|
+
width: 14
|
|
69
|
+
}) : /* @__PURE__ */ jsx(Skeleton, {
|
|
70
|
+
height: 14,
|
|
71
|
+
radius: 4,
|
|
72
|
+
width: `${widthPct}%`
|
|
73
|
+
})
|
|
74
|
+
}, colIndex);
|
|
75
|
+
})
|
|
76
|
+
}, rowIndex))
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const TableLoadingSkeleton = memo(TableLoadingSkeletonInner);
|
|
80
|
+
//#endregion
|
|
81
|
+
export { TableLoadingSkeleton as default };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=LoadingSkeleton.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoadingSkeleton.mjs","names":[],"sources":["../../../src/base-ui/Table/LoadingSkeleton.tsx"],"sourcesContent":["'use client';\n\nimport { memo, useMemo } from 'react';\n\nimport Skeleton from '@/base-ui/Skeleton';\n\nimport { isColumnGroup, isColumnVisibleAt } from './columnUtils';\nimport { EXPAND_COLUMN_WIDTH, SELECTION_COLUMN_WIDTH, styles } from './style';\nimport type { TableBreakpoint, TableColumn, TableSize } from './type';\n\nconst DEFAULT_ROWS = 5;\n\n/** Varying bar widths so consecutive rows do not look identical. */\nconst CELL_WIDTHS = [72, 56, 64, 48, 80, 40, 60, 52] as const;\n\nexport interface TableLoadingSkeletonProps<T = any> {\n columns: readonly TableColumn<T>[];\n expandColumn?: boolean;\n rows?: number;\n screens: ReadonlySet<TableBreakpoint> | null;\n selectionColumn?: boolean;\n size: TableSize;\n}\n\nconst resolveTrack = (width: TableColumn<any>['width']) => {\n if (typeof width === 'number') return `${width}px`;\n if (typeof width === 'string' && width) return width;\n return 'minmax(0, 1fr)';\n};\n\nconst leafColumns = <T,>(\n columns: readonly TableColumn<T>[],\n screens: ReadonlySet<TableBreakpoint> | null,\n): TableColumn<T>[] =>\n columns.flatMap((column) => {\n if (!isColumnVisibleAt(column, screens)) return [];\n if (isColumnGroup(column)) return leafColumns(column.children, screens);\n return [column];\n });\n\n/**\n * Body placeholder while `loading` and there are no rows. Uses the real leaf column\n * widths (plus selection / expand tracks when those columns are on) so the skeleton\n * lines up under the live header; row padding follows the table size CSS vars.\n */\nfunction TableLoadingSkeletonInner<T>({\n columns,\n expandColumn = false,\n rows = DEFAULT_ROWS,\n screens,\n selectionColumn = false,\n size,\n}: TableLoadingSkeletonProps<T>) {\n const tracks = useMemo(() => {\n const list: string[] = [];\n if (selectionColumn) list.push(`${SELECTION_COLUMN_WIDTH}px`);\n if (expandColumn) list.push(`${EXPAND_COLUMN_WIDTH}px`);\n const leaves = leafColumns(columns, screens);\n if (leaves.length === 0) {\n list.push('minmax(0, 1.4fr)', 'minmax(0, 1fr)', 'minmax(0, 0.8fr)', 'minmax(0, 1fr)');\n } else {\n leaves.forEach((column) => list.push(resolveTrack(column.width)));\n }\n return list;\n }, [columns, expandColumn, screens, selectionColumn]);\n\n const gridTemplateColumns = tracks.join(' ');\n const rowCount = Math.max(1, rows);\n\n return (\n <div\n aria-hidden\n className={styles.skeleton}\n data-table-skeleton=\"\"\n data-table-skeleton-size={size === 'middle' ? 'medium' : size}\n >\n {Array.from({ length: rowCount }, (_, rowIndex) => (\n <div className={styles.skeletonRow} key={rowIndex} style={{ gridTemplateColumns }}>\n {tracks.map((_, colIndex) => {\n const leadingSelection = selectionColumn && colIndex === 0;\n const leadingExpand = expandColumn && colIndex === (selectionColumn ? 1 : 0);\n const isLeading = leadingSelection || leadingExpand;\n const widthPct = isLeading\n ? 100\n : CELL_WIDTHS[(rowIndex * 3 + colIndex) % CELL_WIDTHS.length];\n return (\n <div className={styles.skeletonCell} key={colIndex}>\n {isLeading ? (\n <Skeleton height={14} radius={4} width={14} />\n ) : (\n <Skeleton height={14} radius={4} width={`${widthPct}%`} />\n )}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n\nconst TableLoadingSkeleton = memo(TableLoadingSkeletonInner) as typeof TableLoadingSkeletonInner;\n\nexport default TableLoadingSkeleton;\n"],"mappings":";;;;;;;AAUA,MAAM,eAAe;;AAGrB,MAAM,cAAc;CAAC;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;AAAE;AAWnD,MAAM,gBAAgB,UAAqC;CACzD,IAAI,OAAO,UAAU,UAAU,OAAO,GAAG,MAAM;CAC/C,IAAI,OAAO,UAAU,YAAY,OAAO,OAAO;CAC/C,OAAO;AACT;AAEA,MAAM,eACJ,SACA,YAEA,QAAQ,SAAS,WAAW;CAC1B,IAAI,CAAC,kBAAkB,QAAQ,OAAO,GAAG,OAAO,CAAC;CACjD,IAAI,cAAc,MAAM,GAAG,OAAO,YAAY,OAAO,UAAU,OAAO;CACtE,OAAO,CAAC,MAAM;AAChB,CAAC;;;;;;AAOH,SAAS,0BAA6B,EACpC,SACA,eAAe,OACf,OAAO,cACP,SACA,kBAAkB,OAClB,QAC+B;CAC/B,MAAM,SAAS,cAAc;EAC3B,MAAM,OAAiB,CAAC;EACxB,IAAI,iBAAiB,KAAK,KAAK,MAA6B;EAC5D,IAAI,cAAc,KAAK,KAAK,MAA0B;EACtD,MAAM,SAAS,YAAY,SAAS,OAAO;EAC3C,IAAI,OAAO,WAAW,GACpB,KAAK,KAAK,oBAAoB,kBAAkB,oBAAoB,gBAAgB;OAEpF,OAAO,SAAS,WAAW,KAAK,KAAK,aAAa,OAAO,KAAK,CAAC,CAAC;EAElE,OAAO;CACT,GAAG;EAAC;EAAS;EAAc;EAAS;CAAe,CAAC;CAEpD,MAAM,sBAAsB,OAAO,KAAK,GAAG;CAC3C,MAAM,WAAW,KAAK,IAAI,GAAG,IAAI;CAEjC,OACE,oBAAC,OAAD;EACE,eAAA;EACA,WAAW,OAAO;EAClB,uBAAoB;EACpB,4BAA0B,SAAS,WAAW,WAAW;EAExD,UAAA,MAAM,KAAK,EAAE,QAAQ,SAAS,IAAI,GAAG,aACpC,oBAAC,OAAD;GAAK,WAAW,OAAO;GAA4B,OAAO,EAAE,oBAAoB;GAC7E,UAAA,OAAO,KAAK,GAAG,aAAa;IAG3B,MAAM,YAFmB,mBAAmB,aAAa,KACnC,gBAAgB,cAAc,kBAAkB,IAAI;IAE1E,MAAM,WAAW,YACb,MACA,aAAa,WAAW,IAAI,YAAY,YAAY;IACxD,OACE,oBAAC,OAAD;KAAK,WAAW,OAAO;KACpB,UAAA,YACC,oBAAC,UAAD;MAAU,QAAQ;MAAI,QAAQ;MAAG,OAAO;KAAK,CAAA,IAE7C,oBAAC,UAAD;MAAU,QAAQ;MAAI,QAAQ;MAAG,OAAO,GAAG,SAAS;KAAK,CAAA;IAExD,GANqC,QAMrC;GAET,CAAC;EACE,GAlBoC,QAkBpC,CACN;CACE,CAAA;AAET;AAEA,MAAM,uBAAuB,KAAK,yBAAyB"}
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import { TableProps } from "./type.mjs";
|
|
1
|
+
import { TableColumn, TableProps as TableProps$1 } from "./type.mjs";
|
|
2
|
+
import Column from "./Column.mjs";
|
|
3
|
+
import ColumnGroup from "./ColumnGroup.mjs";
|
|
4
|
+
import { SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE } from "./selectionUtils.mjs";
|
|
2
5
|
import { ReactNode } from "react";
|
|
3
|
-
import {
|
|
6
|
+
import { Summary } from "@rc-component/table";
|
|
4
7
|
//#region src/base-ui/Table/Table.d.ts
|
|
5
|
-
|
|
8
|
+
type TableComponent = (<T>(props: TableProps$1<T>) => ReactNode) & {
|
|
9
|
+
Column: typeof Column;
|
|
10
|
+
ColumnGroup: typeof ColumnGroup;
|
|
6
11
|
displayName?: string;
|
|
12
|
+
/** Placeholder in `columns` marking where the expand column goes. */
|
|
13
|
+
EXPAND_COLUMN: TableColumn<any>;
|
|
14
|
+
/** `rowSelection.selections` preset: select every row of every page. */
|
|
15
|
+
SELECTION_ALL: typeof SELECTION_ALL;
|
|
16
|
+
/** Placeholder in `columns` marking where the selection column goes. */
|
|
17
|
+
SELECTION_COLUMN: TableColumn<any>;
|
|
18
|
+
/** Rows rendered by `summary`: `Table.Summary` (`fixed`), `.Row` and `.Cell` (`index`, `colSpan`). */
|
|
19
|
+
Summary: typeof Summary;
|
|
20
|
+
/** `rowSelection.selections` preset: invert the current page. */
|
|
21
|
+
SELECTION_INVERT: typeof SELECTION_INVERT;
|
|
22
|
+
/** `rowSelection.selections` preset: clear the selection. */
|
|
23
|
+
SELECTION_NONE: typeof SELECTION_NONE;
|
|
7
24
|
};
|
|
25
|
+
declare const Table: TableComponent;
|
|
8
26
|
//#endregion
|
|
9
27
|
export { Table as default };
|
|
10
28
|
//# sourceMappingURL=Table.d.mts.map
|