@react-fabric/data 1.0.0-beta.8 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
- package/css/styles.css +24 -0
- package/dist/cjs/checklist/CheckList.js +59 -33
- package/dist/cjs/histogram/Histogram.js +41 -26
- package/dist/cjs/hooks/useFilteredList.js +100 -17
- package/dist/cjs/hooks/usePagination.js +29 -8
- package/dist/cjs/hooks/useSelectableList.js +30 -5
- package/dist/cjs/i18n/{ar.json.js → ar/data.json.js} +8 -1
- package/dist/cjs/i18n/{en.json.js → en/data.json.js} +8 -1
- package/dist/cjs/i18n/index.js +4 -4
- package/dist/cjs/index.js +30 -0
- package/dist/cjs/json/JsonViewer.js +80 -43
- package/dist/cjs/pagination/Pagination.js +24 -15
- package/dist/cjs/table/AddColumn.js +5 -5
- package/dist/cjs/table/BodyCell.js +20 -13
- package/dist/cjs/table/BodyRow.js +41 -28
- package/dist/cjs/table/CheckboxCell.js +10 -9
- package/dist/cjs/table/ColumnFilters.js +100 -0
- package/dist/cjs/table/EmptyCell.js +4 -4
- package/dist/cjs/table/HeaderCell.js +79 -53
- package/dist/cjs/table/HtmlTable.js +84 -0
- package/dist/cjs/table/{Context.js → Provider.js} +8 -10
- package/dist/cjs/table/Table.js +99 -33
- package/dist/cjs/{tree/types.js → table/context.js} +9 -5
- package/dist/cjs/table/types.js +29 -0
- package/dist/cjs/table/useResizer.js +10 -10
- package/dist/cjs/table/useTableColumns.js +4 -4
- package/dist/cjs/table/useVirtualTable.js +129 -22
- package/dist/cjs/tree/TreeNode.js +66 -46
- package/dist/cjs/tree/TreePanel.js +60 -24
- package/dist/cjs/tree/useTree.js +212 -47
- package/dist/cjs/tree/utils.js +63 -34
- package/dist/cjs/utils/getColor.js +58 -0
- package/dist/cjs/virtual/Gallery.js +119 -61
- package/dist/cjs/virtual/List.js +116 -55
- package/dist/cjs/virtual/Scroller.js +16 -8
- package/dist/esm/checklist/CheckList.js +61 -35
- package/dist/esm/histogram/Histogram.js +43 -28
- package/dist/esm/hooks/useFilteredList.js +101 -18
- package/dist/esm/hooks/usePagination.js +29 -8
- package/dist/esm/hooks/useSelectableList.js +30 -5
- package/dist/esm/i18n/{ar.json.js → ar/data.json.js} +8 -2
- package/dist/esm/i18n/{en.json.js → en/data.json.js} +8 -2
- package/dist/esm/i18n/index.js +2 -2
- package/dist/esm/index.js +31 -0
- package/dist/esm/json/JsonViewer.js +82 -45
- package/dist/esm/pagination/Pagination.js +25 -16
- package/dist/esm/table/AddColumn.js +6 -6
- package/dist/esm/table/BodyCell.js +21 -14
- package/dist/esm/table/BodyRow.js +41 -28
- package/dist/esm/table/CheckboxCell.js +11 -10
- package/dist/esm/table/ColumnFilters.js +100 -0
- package/dist/esm/table/EmptyCell.js +4 -4
- package/dist/esm/table/HeaderCell.js +80 -54
- package/dist/esm/table/HtmlTable.js +84 -0
- package/dist/esm/table/{Context.js → Provider.js} +11 -12
- package/dist/esm/table/Table.js +100 -34
- package/dist/esm/{tree/types.js → table/context.js} +8 -4
- package/dist/esm/table/types.js +29 -0
- package/dist/esm/table/useResizer.js +10 -10
- package/dist/esm/table/useTableColumns.js +4 -4
- package/dist/esm/table/useVirtualTable.js +131 -24
- package/dist/esm/tree/TreeNode.js +69 -49
- package/dist/esm/tree/TreePanel.js +62 -26
- package/dist/esm/tree/useTree.js +215 -50
- package/dist/esm/tree/utils.js +64 -35
- package/dist/esm/utils/getColor.js +58 -0
- package/dist/esm/virtual/Gallery.js +121 -63
- package/dist/esm/virtual/List.js +118 -57
- package/dist/esm/virtual/Scroller.js +17 -9
- package/dist/types/checklist/CheckList.d.ts +25 -6
- package/dist/types/histogram/Histogram.d.ts +28 -6
- package/dist/types/hooks/useFilteredList.d.ts +62 -5
- package/dist/types/hooks/usePagination.d.ts +18 -2
- package/dist/types/hooks/useSelectableList.d.ts +24 -1
- package/dist/types/index.d.ts +27 -0
- package/dist/types/json/JsonViewer.d.ts +23 -3
- package/dist/types/pagination/Pagination.d.ts +8 -2
- package/dist/types/table/AddColumn.d.ts +2 -2
- package/dist/types/table/BodyCell.d.ts +2 -2
- package/dist/types/table/BodyRow.d.ts +9 -9
- package/dist/types/table/CheckboxCell.d.ts +3 -3
- package/dist/types/table/ColumnFilters.d.ts +12 -0
- package/dist/types/table/EmptyCell.d.ts +1 -1
- package/dist/types/table/HeaderCell.d.ts +1 -1
- package/dist/types/table/HtmlTable.d.ts +21 -0
- package/dist/types/table/Provider.d.ts +2 -0
- package/dist/types/table/Table.d.ts +10 -1
- package/dist/types/table/context.d.ts +7 -0
- package/dist/types/table/types.d.ts +52 -10
- package/dist/types/table/useResizer.d.ts +1 -1
- package/dist/types/table/useTableColumns.d.ts +1 -1
- package/dist/types/table/useVirtualTable.d.ts +31 -12
- package/dist/types/tree/TreeNode.d.ts +1 -1
- package/dist/types/tree/TreePanel.d.ts +36 -1
- package/dist/types/tree/types.d.ts +75 -28
- package/dist/types/tree/useTree.d.ts +8 -12
- package/dist/types/tree/utils.d.ts +15 -8
- package/dist/types/utils/getColor.d.ts +43 -0
- package/dist/types/virtual/Gallery.d.ts +37 -6
- package/dist/types/virtual/List.d.ts +38 -6
- package/dist/types/virtual/Scroller.d.ts +1 -1
- package/package.json +17 -19
- package/src/checklist/CheckList.tsx +68 -71
- package/src/histogram/Histogram.tsx +47 -48
- package/src/hooks/useFilteredList.tsx +111 -28
- package/src/hooks/usePagination.tsx +39 -39
- package/src/hooks/useSelectableList.ts +45 -33
- package/src/i18n/{ar.json → ar/data.json} +5 -0
- package/src/i18n/{en.json → en/data.json} +5 -0
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +23 -17
- package/src/json/JsonViewer.tsx +89 -120
- package/src/pagination/Pagination.tsx +104 -105
- package/src/table/AddColumn.tsx +6 -15
- package/src/table/BodyCell.tsx +27 -36
- package/src/table/BodyRow.tsx +36 -59
- package/src/table/CheckboxCell.tsx +12 -15
- package/src/table/ColumnFilters.tsx +93 -0
- package/src/table/EmptyCell.tsx +5 -18
- package/src/table/HeaderCell.tsx +72 -53
- package/src/table/HtmlTable.tsx +106 -0
- package/src/table/{Context.tsx → Provider.tsx} +11 -28
- package/src/table/Table.tsx +98 -83
- package/src/table/context.ts +33 -0
- package/src/table/types.ts +53 -27
- package/src/table/useResizer.ts +5 -9
- package/src/table/useTableColumns.ts +4 -6
- package/src/table/useVirtualTable.ts +136 -37
- package/src/tree/TreeNode.tsx +91 -86
- package/src/tree/TreePanel.tsx +87 -51
- package/src/tree/types.ts +85 -46
- package/src/tree/useTree.ts +210 -65
- package/src/tree/utils.ts +81 -62
- package/src/utils/getColor.ts +55 -0
- package/src/virtual/Gallery.tsx +129 -120
- package/src/virtual/List.tsx +124 -91
- package/src/virtual/Scroller.tsx +25 -9
- package/dist/cjs/histogram/Histogram.module.css +0 -1
- package/dist/esm/histogram/Histogram.module.css +0 -1
- package/dist/types/table/Context.d.ts +0 -8
package/src/tree/TreeNode.tsx
CHANGED
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { Badge,
|
|
25
|
-
import
|
|
26
|
-
import { useMemo } from "react";
|
|
24
|
+
import { Badge, getBadgeProps, getIconProps, Icon } from "@react-fabric/core";
|
|
25
|
+
import { cn, isEmpty, isString, isTrue } from "@react-fabric/utilities";
|
|
26
|
+
import { isValidElement, useMemo } from "react";
|
|
27
|
+
import { useTranslation } from "react-i18next";
|
|
27
28
|
import { type TreeNodeProps } from "./types";
|
|
28
29
|
|
|
29
|
-
export
|
|
30
|
+
export function TreeNode<T extends KeyValue>({
|
|
30
31
|
node,
|
|
31
32
|
noLines,
|
|
32
33
|
expanders,
|
|
@@ -35,129 +36,133 @@ export const TreeNode = ({
|
|
|
35
36
|
defaultLeafIcon,
|
|
36
37
|
defaultNodeIcon,
|
|
37
38
|
onToggle,
|
|
39
|
+
onClick,
|
|
38
40
|
onSelect,
|
|
39
41
|
onChecked,
|
|
40
42
|
children,
|
|
41
|
-
|
|
43
|
+
leafClassName,
|
|
44
|
+
nodeClassName,
|
|
45
|
+
}: TreeNodeProps<T>) {
|
|
46
|
+
const { t } = useTranslation("data");
|
|
42
47
|
const badgeProps = useMemo(() => {
|
|
43
|
-
return getBadgeProps(node.badge);
|
|
48
|
+
return getBadgeProps(node.badge) as KeyValue;
|
|
44
49
|
}, [node.badge]);
|
|
45
50
|
|
|
46
51
|
const filler = useMemo(() => {
|
|
47
|
-
return node.lines.slice(
|
|
48
|
-
<
|
|
52
|
+
return node.lines.slice(1).map((line, idx) => (
|
|
53
|
+
<div
|
|
54
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key
|
|
49
55
|
key={idx}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<path d="M14.989 12.389l0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v14.1l0.161 0.389 0.389 0.161h6.6z" />
|
|
57
|
-
)}
|
|
58
|
-
{!noLines && line === 3 && (
|
|
59
|
-
<path d="M8.55 12.55h6.050l0.389-0.161 0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v28.2h1.1z" />
|
|
56
|
+
className={cn(
|
|
57
|
+
"flex-initial self-stretch flex items-center w-6 relative",
|
|
58
|
+
!noLines &&
|
|
59
|
+
line > 0 &&
|
|
60
|
+
"before:absolute before:top-0 before:inset-s-[calc(7px)] before:bg-tint-200 before:min-h-[50%] before:w-0.5 before:scale-x-[.625]",
|
|
61
|
+
!noLines && line !== 2 && "before:h-full",
|
|
60
62
|
)}
|
|
61
|
-
|
|
63
|
+
>
|
|
64
|
+
<svg viewBox="0 0 24 24" className="fill-tint-200 rtl:-scale-x-100">
|
|
65
|
+
{line === 0 && <path d="" />}
|
|
66
|
+
{!noLines && line === 1 && <path d="M7.45 26.1v-28.2h1.1v28.2h-1.1z" />}
|
|
67
|
+
{!noLines && line === 2 && (
|
|
68
|
+
<path d="M14.989 12.389l0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v14.1l0.161 0.389 0.389 0.161h6.6z" />
|
|
69
|
+
)}
|
|
70
|
+
{!noLines && line === 3 && (
|
|
71
|
+
<path d="M8.55 12.55h6.050l0.389-0.161 0.161-0.389-0.161-0.389-0.389-0.161h-6.050v-13.55h-1.1v28.2h1.1z" />
|
|
72
|
+
)}
|
|
73
|
+
</svg>
|
|
74
|
+
</div>
|
|
62
75
|
));
|
|
63
76
|
}, [noLines, node.lines]);
|
|
64
77
|
|
|
65
78
|
const nodeIcon = useMemo(() => {
|
|
66
79
|
if (node.icon) return node.icon;
|
|
67
|
-
return node.leaf ? defaultLeafIcon : defaultNodeIcon;
|
|
68
|
-
}, [defaultLeafIcon, defaultNodeIcon, node.icon]);
|
|
80
|
+
return { icon: node.leaf ? defaultLeafIcon : defaultNodeIcon };
|
|
81
|
+
}, [defaultLeafIcon, defaultNodeIcon, node.icon, node.leaf]);
|
|
69
82
|
|
|
70
83
|
const canCheck = useMemo(() => {
|
|
71
|
-
return checkable
|
|
84
|
+
return isTrue(checkable) || (checkable === "leafOnly" && node.leaf);
|
|
72
85
|
}, [node.leaf, checkable]);
|
|
73
86
|
|
|
74
87
|
const canSelect = useMemo(() => {
|
|
75
|
-
return (
|
|
76
|
-
!node.disabled &&
|
|
77
|
-
(selectable === true || (selectable === "leafOnly" && node.leaf))
|
|
78
|
-
);
|
|
88
|
+
return !node.disabled && (selectable === true || (selectable === "leafOnly" && node.leaf));
|
|
79
89
|
}, [node.leaf, node.disabled, selectable]);
|
|
80
90
|
|
|
81
91
|
return (
|
|
82
92
|
<div
|
|
83
93
|
role="treeitem"
|
|
84
94
|
aria-selected={node.selected}
|
|
85
|
-
className="flex flex-nowrap"
|
|
95
|
+
className={cn("flex flex-nowrap items-center")}
|
|
86
96
|
data-testid={node["data-testid"]}
|
|
87
97
|
data-test-value={node["data-test-value"]}
|
|
98
|
+
data-id={node.id}
|
|
88
99
|
>
|
|
89
100
|
{filler}
|
|
90
|
-
{
|
|
101
|
+
{node.loading && (
|
|
91
102
|
<div className="flex-initial w-6">
|
|
92
|
-
<Icon
|
|
93
|
-
rtlFlip
|
|
94
|
-
onClick={() => !node.leaf && onToggle(node.id)}
|
|
95
|
-
icon={node.open ? expanders[1] : expanders[0]}
|
|
96
|
-
className="text-tint-600 hover:text-primary-600"
|
|
97
|
-
/>
|
|
103
|
+
<Icon icon="icon-[svg-spinners--eclipse]" className="text-muted" />
|
|
98
104
|
</div>
|
|
99
105
|
)}
|
|
106
|
+
{!node.leaf && !node.childFiltered && !node.loading && (
|
|
107
|
+
<button type="button" className="flex-initial w-6" onClick={() => !node.leaf && onToggle(node.id)}>
|
|
108
|
+
<Icon rtlFlip icon={node.open ? expanders[1] : expanders[0]} className="text-tint-600 hover:text-primary-600" />
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
100
111
|
{canCheck && (
|
|
101
|
-
<
|
|
112
|
+
<button type="button" className="flex-initial w-6" onClick={() => !node.disabled && onChecked(node.id)}>
|
|
102
113
|
<Icon
|
|
103
|
-
onClick={() => !node.disabled && onChecked(node.id)}
|
|
104
114
|
icon={
|
|
105
115
|
node.checked === 2
|
|
106
|
-
?
|
|
116
|
+
? "icon-[mdi--checkbox-intermediate]"
|
|
107
117
|
: node.checked === 1
|
|
108
|
-
?
|
|
109
|
-
:
|
|
118
|
+
? "icon-[mdi--checkbox-marked]"
|
|
119
|
+
: "icon-[mdi--checkbox-blank-outline]"
|
|
110
120
|
}
|
|
111
|
-
className={
|
|
112
|
-
node.disabled
|
|
113
|
-
? "text-muted"
|
|
114
|
-
: "text-primary-500 hover:text-primary-600",
|
|
115
|
-
)}
|
|
121
|
+
className={cn(node.disabled ? "text-muted" : "text-primary-500 hover:text-primary-600")}
|
|
116
122
|
/>
|
|
117
|
-
</
|
|
123
|
+
</button>
|
|
118
124
|
)}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
125
|
+
{!node.empty && (
|
|
126
|
+
<div
|
|
127
|
+
role="none"
|
|
128
|
+
className={cn(
|
|
129
|
+
"group/tool data-selected:bg-primary-100 flex flex-nowrap flex-1 overflow-hidden select-none",
|
|
130
|
+
node.childSelected && "font-medium",
|
|
131
|
+
node.disabled && "text-muted opacity-65",
|
|
132
|
+
node.className,
|
|
133
|
+
node.leaf ? leafClassName : nodeClassName,
|
|
134
|
+
!node.disabled && (canSelect || onClick) && "hover:bg-primary-50 cursor-pointer",
|
|
135
|
+
)}
|
|
136
|
+
data-selected={node.selected}
|
|
137
|
+
onClick={
|
|
138
|
+
node.disabled
|
|
139
|
+
? undefined
|
|
140
|
+
: (e) => {
|
|
141
|
+
onClick?.(node.id, node.data);
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
143
|
+
canSelect ? onSelect(node.id, e.shiftKey) : canCheck ? onChecked(node.id) : onToggle(node.id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
{nodeIcon && (
|
|
148
|
+
<div className="flex-content max-w-6 me-1 self-center leading-none">
|
|
149
|
+
{isValidElement(nodeIcon) && nodeIcon}
|
|
150
|
+
{isString(nodeIcon) && <Icon {...getIconProps(nodeIcon)} />}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
{node.childSelected && !node.open && <span className=" flex-content text-primary-500">•</span>}
|
|
154
|
+
<div role="none" className="flex-1 truncate">
|
|
155
|
+
{children}
|
|
143
156
|
</div>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
{!isEmpty(node.badge) && (
|
|
158
|
+
<Badge
|
|
159
|
+
{...badgeProps}
|
|
160
|
+
className={cn("me-1 self-center flex-content bg-opacity-50", badgeProps.className as string)}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
150
163
|
</div>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
{...badgeProps}
|
|
154
|
-
className={classNames(
|
|
155
|
-
"me-1 self-center flex-content bg-opacity-50",
|
|
156
|
-
badgeProps.className,
|
|
157
|
-
)}
|
|
158
|
-
/>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
164
|
+
)}
|
|
165
|
+
{node.empty && <div className="text-muted text-sm select-none">{t("tree.noItems")}</div>}
|
|
161
166
|
</div>
|
|
162
167
|
);
|
|
163
|
-
}
|
|
168
|
+
}
|
package/src/tree/TreePanel.tsx
CHANGED
|
@@ -21,33 +21,61 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
Button,
|
|
26
|
-
ButtonGroup,
|
|
27
|
-
CoreIcons,
|
|
28
|
-
Header,
|
|
29
|
-
Icon,
|
|
30
|
-
Section,
|
|
31
|
-
} from "@react-fabric/core";
|
|
24
|
+
import { Button, ButtonGroup, Header, Icon, Layout } from "@react-fabric/core";
|
|
32
25
|
import { Search } from "@react-fabric/form";
|
|
33
26
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
34
|
-
import { useImperativeHandle, useMemo, useRef } from "react";
|
|
27
|
+
import { useEffect, useImperativeHandle, useMemo, useRef } from "react";
|
|
35
28
|
import { TreeNode } from "./TreeNode";
|
|
36
|
-
import {
|
|
29
|
+
import { type TreePanelProps } from "./types";
|
|
37
30
|
import { useTree } from "./useTree";
|
|
38
31
|
|
|
39
32
|
// TODO: implement keyboard handler for navigating tree
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
/**
|
|
35
|
+
* TreePanel component renders a virtualized tree structure with support for
|
|
36
|
+
* selection, expansion, and filtering. It allows for custom rendering of tree
|
|
37
|
+
* nodes and supports checkable nodes.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```jsx
|
|
41
|
+
* <TreePanel
|
|
42
|
+
* items={items}
|
|
43
|
+
* selected={selectedItem}
|
|
44
|
+
* checked={checkedItems}
|
|
45
|
+
* checkable={true}
|
|
46
|
+
* renderer={(item) => <span>{item.name}</span>}
|
|
47
|
+
* searchable={true}
|
|
48
|
+
* selectable={true}
|
|
49
|
+
* matcher={(item, query) => item.name.toLowerCase().includes(query.toLowerCase())}
|
|
50
|
+
* filterPlaceholder="Search..."
|
|
51
|
+
* defaultLeafIcon={<Icon icon="icon-[mdi--file]" />}
|
|
52
|
+
* defaultNodeIcon={<Icon icon="icon-[mdi--folder]" />}
|
|
53
|
+
* expander="caret"
|
|
54
|
+
* onChecked={(checkedItems) => console.log(checkedItems)}
|
|
55
|
+
* onLoad={(id) => console.log(`Load children for ${id}`)}
|
|
56
|
+
* onQuery={(query) => console.log(`Query: ${query}`)}
|
|
57
|
+
* onSelect={(selectedItem) => console.log(selectedItem)}
|
|
58
|
+
* onClick={(item) => console.log(`Clicked on ${item.id}`)}
|
|
59
|
+
* sorter={(a, b) => a.label.localeCompare(b.label)}
|
|
60
|
+
* makeLabel={(item) => item.name}
|
|
61
|
+
* makeIcon={(item) => item.icon ? <Icon icon={item.icon} /> : null}
|
|
62
|
+
* defaultExpanded={["root"]}
|
|
63
|
+
* onExpandToggle={(id, expanded) => console.log(`Toggle expand for ${id}: ${expanded}`)}
|
|
64
|
+
* leafClassName="leaf-node"
|
|
65
|
+
* nodeClassName="tree-node"
|
|
66
|
+
* />
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function TreePanel<T extends KeyValue>({
|
|
42
70
|
ref,
|
|
43
71
|
items,
|
|
44
72
|
selected,
|
|
45
73
|
checked,
|
|
46
74
|
checkable,
|
|
47
75
|
noLines,
|
|
48
|
-
renderer,
|
|
49
76
|
searchable,
|
|
50
77
|
selectable,
|
|
78
|
+
multiple,
|
|
51
79
|
matcher,
|
|
52
80
|
filterPlaceholder,
|
|
53
81
|
defaultLeafIcon,
|
|
@@ -55,25 +83,43 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
55
83
|
expander = "box",
|
|
56
84
|
onChecked,
|
|
57
85
|
onLoad,
|
|
86
|
+
onQuery,
|
|
58
87
|
onSelect,
|
|
88
|
+
onClick,
|
|
89
|
+
sorter,
|
|
90
|
+
makeLabel,
|
|
91
|
+
makeIcon,
|
|
92
|
+
defaultExpanded,
|
|
93
|
+
onExpandToggle,
|
|
94
|
+
leafClassName,
|
|
95
|
+
nodeClassName,
|
|
59
96
|
...aria
|
|
60
|
-
}: TreePanelProps<T>)
|
|
97
|
+
}: TreePanelProps<T>) {
|
|
61
98
|
const {
|
|
62
99
|
tree,
|
|
100
|
+
query,
|
|
101
|
+
selected: currentSelected,
|
|
63
102
|
toggleExpand,
|
|
64
103
|
toggleCheck,
|
|
65
104
|
expandAll,
|
|
66
105
|
collapseAll,
|
|
106
|
+
expandAndLoad,
|
|
67
107
|
select,
|
|
68
108
|
expand,
|
|
69
109
|
onFilter,
|
|
70
|
-
} = useTree({
|
|
110
|
+
} = useTree<T>({
|
|
71
111
|
items,
|
|
72
112
|
checked,
|
|
73
113
|
selected,
|
|
114
|
+
multiple,
|
|
74
115
|
matcher,
|
|
116
|
+
sorter,
|
|
117
|
+
onLoad,
|
|
118
|
+
onQuery,
|
|
75
119
|
onSelect,
|
|
76
120
|
onChecked,
|
|
121
|
+
defaultExpanded,
|
|
122
|
+
onExpandToggle,
|
|
77
123
|
});
|
|
78
124
|
|
|
79
125
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
@@ -86,60 +132,48 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
86
132
|
const virtualItems = virtualizer.getVirtualItems();
|
|
87
133
|
|
|
88
134
|
const expanders = useMemo<[string, string]>(() => {
|
|
89
|
-
if (expander === "caret")
|
|
90
|
-
|
|
91
|
-
if (expander === "
|
|
92
|
-
|
|
93
|
-
if (expander === "folder")
|
|
94
|
-
return [CoreIcons.folderClosed, CoreIcons.folderOpen];
|
|
95
|
-
return [CoreIcons.expand, CoreIcons.collapse];
|
|
135
|
+
if (expander === "caret") return ["icon-[mdi--menu-right]", "icon-[mdi--menu-down]"];
|
|
136
|
+
if (expander === "chevron") return ["icon-[mdi--chevron-right]", "icon-[mdi--chevron-down]"];
|
|
137
|
+
if (expander === "folder") return ["icon-[mdi--folder-outline]", "icon-[mdi--folder-open-outline]"];
|
|
138
|
+
return ["icon-[mdi--plus-box-outline]", "icon-[mdi--minus-box-outline]"];
|
|
96
139
|
}, [expander]);
|
|
97
140
|
|
|
98
141
|
useImperativeHandle(
|
|
99
142
|
ref,
|
|
100
143
|
() => ({
|
|
101
144
|
open: (id: string) => expand(id),
|
|
102
|
-
|
|
145
|
+
openAndLoad: (list: string[]) => expandAndLoad(list),
|
|
146
|
+
select: (id: string) => select(id),
|
|
103
147
|
}),
|
|
148
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
104
149
|
[],
|
|
105
150
|
);
|
|
106
151
|
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const index = tree.findIndex((n) => n.id === currentSelected);
|
|
154
|
+
virtualizer.scrollToIndex(index, { align: "auto" });
|
|
155
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
156
|
+
}, [currentSelected]);
|
|
157
|
+
|
|
107
158
|
return (
|
|
108
|
-
<
|
|
109
|
-
<Header
|
|
110
|
-
flex
|
|
111
|
-
className="items-center p-px justify-end overflow-hidden border-b"
|
|
112
|
-
>
|
|
159
|
+
<Layout>
|
|
160
|
+
<Header flex className="items-center p-px justify-end overflow-hidden border-b">
|
|
113
161
|
{searchable && (
|
|
114
162
|
<Search
|
|
163
|
+
value={query}
|
|
115
164
|
onSearch={onFilter}
|
|
116
165
|
allowClear
|
|
117
166
|
placeholder={filterPlaceholder}
|
|
118
167
|
{...{ noBorder: true }}
|
|
119
|
-
decorateStart={<Icon icon=
|
|
168
|
+
decorateStart={<Icon icon="icon-[mdi--filter-variant]" className="p-1" />}
|
|
120
169
|
/>
|
|
121
170
|
)}
|
|
122
171
|
<ButtonGroup variant="link" className="m-0">
|
|
123
|
-
<Button
|
|
124
|
-
|
|
125
|
-
aria-label="expand all"
|
|
126
|
-
icon={iconExpandAll}
|
|
127
|
-
onClick={expandAll}
|
|
128
|
-
/>
|
|
129
|
-
<Button
|
|
130
|
-
size="sm"
|
|
131
|
-
aria-label="collapse all"
|
|
132
|
-
icon={iconCollapseAll}
|
|
133
|
-
onClick={collapseAll}
|
|
134
|
-
/>
|
|
172
|
+
<Button size="sm" aria-label="expand all" icon="icon-[mdi--arrow-expand-vertical]" onClick={expandAll} />
|
|
173
|
+
<Button size="sm" aria-label="collapse all" icon="icon-[mdi--arrow-collapse-vertical]" onClick={collapseAll} />
|
|
135
174
|
</ButtonGroup>
|
|
136
175
|
</Header>
|
|
137
|
-
<div
|
|
138
|
-
ref={scrollerRef}
|
|
139
|
-
role="tree"
|
|
140
|
-
className="area-content overflow-auto p-1"
|
|
141
|
-
{...aria}
|
|
142
|
-
>
|
|
176
|
+
<div ref={scrollerRef} role="tree" className="area-content overflow-auto p-1" {...aria}>
|
|
143
177
|
<div style={{ height: virtualizer.getTotalSize() }}>
|
|
144
178
|
<div style={{ height: virtualItems[0]?.start }} />
|
|
145
179
|
{virtualItems.map(({ index, key }) => (
|
|
@@ -151,18 +185,20 @@ export const TreePanel = <T extends KeyValue>({
|
|
|
151
185
|
checkable={checkable}
|
|
152
186
|
selectable={selectable}
|
|
153
187
|
defaultLeafIcon={defaultLeafIcon}
|
|
154
|
-
defaultNodeIcon={defaultNodeIcon}
|
|
188
|
+
defaultNodeIcon={makeIcon?.(tree[index].data) ?? defaultNodeIcon}
|
|
155
189
|
onToggle={toggleExpand}
|
|
156
190
|
onChecked={toggleCheck}
|
|
157
191
|
onSelect={select}
|
|
192
|
+
onClick={onClick}
|
|
193
|
+
leafClassName={leafClassName}
|
|
194
|
+
nodeClassName={nodeClassName}
|
|
158
195
|
>
|
|
159
|
-
{(tree[index].data &&
|
|
160
|
-
tree[index].label}
|
|
196
|
+
{(tree[index].data && makeLabel?.(tree[index].data)) ?? tree[index].label}
|
|
161
197
|
</TreeNode>
|
|
162
198
|
</div>
|
|
163
199
|
))}
|
|
164
200
|
</div>
|
|
165
201
|
</div>
|
|
166
|
-
</
|
|
202
|
+
</Layout>
|
|
167
203
|
);
|
|
168
|
-
}
|
|
204
|
+
}
|