@tap-payments/os-micro-frontend-shared 0.3.255 → 0.3.256
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/build/_chunks/{chunk-5B3BM4HA.js → chunk-24X3BEJR.js} +1 -1
- package/build/_chunks/{chunk-YXRP3YVA.js → chunk-2FRVCEJY.js} +1 -1
- package/build/_chunks/chunk-2GSUGMI7.js +2 -0
- package/build/_chunks/{chunk-6AISJMRA.js → chunk-2J4W2CAI.js} +1 -1
- package/build/_chunks/{chunk-SFQF3NEZ.js → chunk-2QA5MXMV.js} +1 -1
- package/build/_chunks/{chunk-FXLC7QM6.js → chunk-3GA46JLW.js} +1 -1
- package/build/_chunks/{chunk-5CIBM232.js → chunk-4FOFVTQ2.js} +1 -1
- package/build/_chunks/{chunk-FEZOSRP2.js → chunk-4Y2COEZC.js} +1 -1
- package/build/_chunks/{chunk-7PCMUVPG.js → chunk-5RPLYU74.js} +1 -1
- package/build/_chunks/{chunk-BLPTYUL5.js → chunk-5VSOWOX2.js} +1 -1
- package/build/_chunks/chunk-6PLO2VOU.js +1 -0
- package/build/_chunks/{chunk-3ARGSIW6.js → chunk-6Q5NWGGT.js} +1 -1
- package/build/_chunks/{chunk-UDF64CIJ.js → chunk-6SBWV2KY.js} +1 -1
- package/build/_chunks/{chunk-467AH657.js → chunk-6WVAYA6W.js} +1 -1
- package/build/_chunks/chunk-7GJMLJ2X.js +1 -0
- package/build/_chunks/{chunk-LPVUMEPP.js → chunk-7UHNYLD7.js} +1 -1
- package/build/_chunks/{chunk-VTM5XAA4.js → chunk-ACIQTR6D.js} +1 -1
- package/build/_chunks/{chunk-TP36G4JZ.js → chunk-AO5K32SU.js} +1 -1
- package/build/_chunks/{chunk-BVZCPC4B.js → chunk-BDL56M7A.js} +1 -1
- package/build/_chunks/{chunk-MZ4CDPE6.js → chunk-C4ZTNHLR.js} +1 -1
- package/build/_chunks/{chunk-BNHPA7NL.js → chunk-CAZWVRDN.js} +1 -1
- package/build/_chunks/{chunk-USHKRLP3.js → chunk-CK64PBMR.js} +1 -1
- package/build/_chunks/{chunk-J7KCH7KM.js → chunk-CWNDTPM5.js} +1 -1
- package/build/_chunks/chunk-CZWXKHO7.js +1 -0
- package/build/_chunks/{chunk-N4CCG67Z.js → chunk-DFH5QS2C.js} +1 -1
- package/build/_chunks/{chunk-6JO37S7T.js → chunk-DN3GVPG7.js} +1 -1
- package/build/_chunks/{chunk-G7FUKYC4.js → chunk-DRBZWQC7.js} +1 -1
- package/build/_chunks/chunk-DRWSBTEH.js +1 -0
- package/build/_chunks/{chunk-6PNJ5QPQ.js → chunk-ECXZJHTU.js} +1 -1
- package/build/_chunks/{chunk-NUJYDXCY.js → chunk-ETTNSCF2.js} +1 -1
- package/build/_chunks/{chunk-53FTAIGE.js → chunk-F7TSJOX6.js} +1 -1
- package/build/_chunks/{chunk-LHPUWQK3.js → chunk-FGZVDVG2.js} +1 -1
- package/build/_chunks/{chunk-TXVDWYET.js → chunk-G5OEG7OX.js} +1 -1
- package/build/_chunks/{chunk-LSJ4DU5A.js → chunk-HKFOCHKQ.js} +1 -1
- package/build/_chunks/{chunk-667MADJD.js → chunk-IO5DNKRE.js} +1 -1
- package/build/_chunks/{chunk-ZDRR6O4Q.js → chunk-IQUQQVHU.js} +1 -1
- package/build/_chunks/{chunk-URDUQGSO.js → chunk-IULAG2J7.js} +1 -1
- package/build/_chunks/{chunk-EOEZUZCE.js → chunk-IUN4DEUF.js} +1 -1
- package/build/_chunks/{chunk-U7BM5XPI.js → chunk-IXZBLV5N.js} +1 -1
- package/build/_chunks/{chunk-IDIANGVR.js → chunk-IZLK35VJ.js} +2 -2
- package/build/_chunks/{chunk-KGG6V7HL.js → chunk-J2QN5EF5.js} +1 -1
- package/build/_chunks/{chunk-2FZQAWTT.js → chunk-J3CPRU2X.js} +1 -1
- package/build/_chunks/{chunk-YQKWAH6Y.js → chunk-JC7NZ5DQ.js} +1 -1
- package/build/_chunks/{chunk-ECCMCTOM.js → chunk-JLL4EOYJ.js} +1 -1
- package/build/_chunks/{chunk-M6YVU7V3.js → chunk-JNIXJXK3.js} +1 -1
- package/build/_chunks/{chunk-PGMH7MPJ.js → chunk-JO7EW2A3.js} +1 -1
- package/build/_chunks/{chunk-DJB73WJX.js → chunk-LBQWWXKH.js} +1 -1
- package/build/_chunks/{chunk-CUGWXBZN.js → chunk-LLOK2FPM.js} +1 -1
- package/build/_chunks/{chunk-BHQDWVM3.js → chunk-LNEWGSQE.js} +1 -1
- package/build/_chunks/chunk-M6JX6YSB.js +1 -0
- package/build/_chunks/{chunk-PCY4RMGU.js → chunk-MAPBEJDO.js} +1 -1
- package/build/_chunks/{chunk-N63SPB7J.js → chunk-MVWBQNBX.js} +1 -1
- package/build/_chunks/{chunk-QLBJFLHC.js → chunk-NVNFBBM3.js} +1 -1
- package/build/_chunks/{chunk-SQ6X7XQI.js → chunk-OGARNN6I.js} +1 -1
- package/build/_chunks/{chunk-2QYAHYYH.js → chunk-OQA73I3Z.js} +1 -1
- package/build/_chunks/{chunk-TSI47ZRD.js → chunk-PEV5GRRD.js} +1 -1
- package/build/_chunks/{chunk-NU6TMNMX.js → chunk-PGG4PIRO.js} +1 -1
- package/build/_chunks/{chunk-7EBCOZWQ.js → chunk-PIO2RM2Z.js} +1 -1
- package/build/_chunks/{chunk-KP2UMMK5.js → chunk-Q7C7A5RK.js} +1 -1
- package/build/_chunks/chunk-REMAOTWV.js +2 -0
- package/build/_chunks/{chunk-WX3CZG6V.js → chunk-RRBFJGS6.js} +1 -1
- package/build/_chunks/{chunk-OGUM23ZV.js → chunk-SKJZPJBN.js} +1 -1
- package/build/_chunks/{chunk-6QJCOTT4.js → chunk-SPZGODAQ.js} +1 -1
- package/build/_chunks/chunk-UFOU3EJK.js +1 -0
- package/build/_chunks/chunk-UOERDOEI.js +1 -0
- package/build/_chunks/{chunk-OTAHIU6Z.js → chunk-UZIT72EG.js} +1 -1
- package/build/_chunks/chunk-VTFUWYYT.js +1 -0
- package/build/_chunks/{chunk-Q2XPHAIX.js → chunk-X26MSUES.js} +1 -1
- package/build/_chunks/{chunk-QCVRHMCL.js → chunk-XKMKTJWE.js} +1 -1
- package/build/_chunks/{chunk-E7OZE7FK.js → chunk-Y2HHEHH2.js} +2 -2
- package/build/_chunks/{chunk-GRNUANVM.js → chunk-YAO5SBRE.js} +1 -1
- package/build/_chunks/chunk-YLVW27RP.js +2 -0
- package/build/components/ActionsDropdownMenu/index.js +1 -1
- package/build/components/Alert/index.js +1 -1
- package/build/components/AnimatedSpinnerIcon/index.js +1 -1
- package/build/components/AnimatedSpinnerIcon/style.d.ts +1 -1
- package/build/components/AppServices/index.js +1 -1
- package/build/components/AppServicesBar/index.js +1 -1
- package/build/components/AppWindowWrapper/AppWindow/AppWindow.d.ts +2 -1
- package/build/components/AppWindowWrapper/AppWindowWrapper.d.ts +1 -1
- package/build/components/AppWindowWrapper/index.js +1 -1
- package/build/components/AppWindowWrapper/type.d.ts +0 -6
- package/build/components/AreaChart/index.js +1 -1
- package/build/components/Autocomplete/index.js +1 -1
- package/build/components/AvatarStack/index.js +1 -1
- package/build/components/Calender/index.js +1 -1
- package/build/components/Card/index.js +1 -1
- package/build/components/ColorPicker/index.js +1 -1
- package/build/components/ColumnViews/index.js +1 -1
- package/build/components/ConfirmDialog/index.js +1 -1
- package/build/components/CountryFilter/index.js +1 -1
- package/build/components/CountryFlag/index.js +1 -1
- package/build/components/CurrenciesMenu/index.js +1 -1
- package/build/components/CurrencyAmount/index.js +1 -1
- package/build/components/CurrencyAmountInput/index.js +1 -1
- package/build/components/CurrencySymbol/index.js +1 -1
- package/build/components/DateFilter/index.js +1 -1
- package/build/components/DeviceIcon/index.js +1 -1
- package/build/components/DropdownMenu/index.js +1 -1
- package/build/components/EmptyState/index.js +1 -1
- package/build/components/ErrorToast/index.js +1 -1
- package/build/components/FileDropbox/index.js +1 -1
- package/build/components/FilePreview/index.js +1 -1
- package/build/components/FileUploader/index.js +1 -1
- package/build/components/FilterChip/FilterChip.d.ts +4 -0
- package/build/components/FilterChip/constants.d.ts +15 -0
- package/build/components/FilterChip/icons.d.ts +4 -0
- package/build/components/FilterChip/index.d.ts +4 -0
- package/build/components/FilterChip/index.js +1 -0
- package/build/components/FilterChip/style.d.ts +8 -0
- package/build/components/FilterChip/type.d.ts +43 -0
- package/build/components/FilterDropdown/index.js +1 -1
- package/build/components/Filters/index.js +1 -1
- package/build/components/FlippingCard/index.js +1 -1
- package/build/components/FormSelect/index.js +1 -1
- package/build/components/GroupedSelect/index.js +1 -1
- package/build/components/HeatMap/index.js +1 -1
- package/build/components/InputBase/index.js +1 -1
- package/build/components/Inputs/index.js +1 -1
- package/build/components/JSONViewer/components/JSONSearchBar/styles.d.ts +2 -2
- package/build/components/JSONViewer/index.js +1 -1
- package/build/components/LeftPeekRightExpandingChip/index.js +1 -1
- package/build/components/ListColumnFilter/index.js +1 -1
- package/build/components/ListHeader/index.js +1 -1
- package/build/components/ListHeader/types.d.ts +0 -2
- package/build/components/ListLayout/index.js +1 -1
- package/build/components/Loader/index.js +1 -1
- package/build/components/Logo/index.js +1 -1
- package/build/components/MFWidgetLoader/index.js +1 -1
- package/build/components/MaskedText/index.js +1 -1
- package/build/components/MultiSelect/index.js +1 -1
- package/build/components/NestedDropdown/index.js +1 -1
- package/build/components/NestedMultiSelect/index.js +1 -1
- package/build/components/PaymentSourceFilter/index.js +1 -1
- package/build/components/PhoneFilter/index.js +1 -1
- package/build/components/PhoneInput/index.js +1 -1
- package/build/components/ProgressRing/index.js +1 -1
- package/build/components/RadioGroup/index.js +1 -1
- package/build/components/RangeCalender/index.js +1 -1
- package/build/components/RatingCount/index.js +1 -1
- package/build/components/ReferenceTypeFilter/index.js +1 -1
- package/build/components/RightLeftExpandingCenterChip/index.js +1 -1
- package/build/components/SearchButton/SearchButton.d.ts +7 -2
- package/build/components/SearchButton/index.d.ts +2 -3
- package/build/components/SearchButton/index.js +1 -1
- package/build/components/SearchButton/{style.d.ts → styles.d.ts} +173 -173
- package/build/components/SearchInput/index.js +1 -1
- package/build/components/SheetViewChip/index.js +1 -1
- package/build/components/Sidebar/index.js +1 -1
- package/build/components/StatusBar/index.js +1 -1
- package/build/components/StatusButton/index.js +1 -1
- package/build/components/StatusFilter/index.js +1 -1
- package/build/components/StatusIcons/index.js +1 -1
- package/build/components/StatusLabel/index.js +1 -1
- package/build/components/TableCells/CustomCells/ActionCell/style.d.ts +1 -1
- package/build/components/TableCells/CustomCells/PayoutReportCell/style.d.ts +1 -1
- package/build/components/TableCells/CustomCells/StatusCell/style.d.ts +1 -1
- package/build/components/TableCells/index.js +1 -1
- package/build/components/TableReports/index.js +1 -1
- package/build/components/TextInput/index.js +1 -1
- package/build/components/Timepicker/index.js +1 -1
- package/build/components/ToggleView/index.js +1 -1
- package/build/components/Toolbar/index.js +1 -1
- package/build/components/ToolbarIcon/index.js +1 -1
- package/build/components/TreeDropdown/index.js +1 -1
- package/build/components/VerificationIcon/index.js +1 -1
- package/build/components/ViewColumnRadioFilter/index.js +1 -1
- package/build/components/VirtualTables/components/style.d.ts +1 -1
- package/build/components/VirtualTables/index.js +1 -1
- package/build/components/Widget/index.js +1 -1
- package/build/components/index.d.ts +2 -2
- package/build/components/index.js +1 -1
- package/build/components/primitives/index.js +1 -1
- package/build/constants/assets.d.ts +0 -3
- package/build/constants/index.js +1 -1
- package/build/datetime/components/index.js +1 -1
- package/build/hooks/index.js +1 -1
- package/build/index.js +1 -1
- package/build/utils/index.js +1 -1
- package/package.json +3 -3
- package/build/_chunks/chunk-AGUMSHB4.js +0 -1
- package/build/_chunks/chunk-C2QLJW5U.js +0 -1
- package/build/_chunks/chunk-GUU2BANS.js +0 -1
- package/build/_chunks/chunk-IDPTEI5C.js +0 -1
- package/build/_chunks/chunk-IRIZPPXM.js +0 -1
- package/build/_chunks/chunk-QK77GKMF.js +0 -2
- package/build/_chunks/chunk-RNBTIRD7.js +0 -2
- package/build/_chunks/chunk-SNMSU243.js +0 -1
- package/build/_chunks/chunk-T6FSTHWB.js +0 -2
- package/build/_chunks/chunk-UVXP2WAT.js +0 -1
- package/build/_chunks/chunk-VKTXI56O.js +0 -1
- package/build/_chunks/chunk-ZTZOHMJ3.js +0 -1
- package/build/components/ActiveFilterChip/ActiveFilterChip.d.ts +0 -5
- package/build/components/ActiveFilterChip/index.d.ts +0 -3
- package/build/components/ActiveFilterChip/index.js +0 -1
- package/build/components/ActiveFilterChip/styles.d.ts +0 -8
- package/build/components/ActiveFilterChip/types.d.ts +0 -7
- package/build/components/AppWindowWrapper/AppWindow/nestingContext.d.ts +0 -3
- package/build/components/SearchButton/constants.d.ts +0 -37
- package/build/components/SearchButton/icons.d.ts +0 -3
- package/build/components/SearchButton/type.d.ts +0 -28
- package/docs/README.md +0 -40
- package/src/components/TreeDropdown/README.md +0 -604
- package/src/components/primitives/README.md +0 -507
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
# TreeDropdown Component
|
|
2
|
-
|
|
3
|
-
A powerful, flexible tree dropdown component for React that supports hierarchical data structures with multi-select, single-select, search functionality, and customizable rendering.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The `TreeDropdown` component provides a nested menu structure that can display hierarchical data in a dropdown format. It supports both single and multiple selection modes, includes built-in search capabilities, and allows for extensive customization through props and render functions.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- 🌳 **Hierarchical Tree Structure**: Display nested data with unlimited depth
|
|
12
|
-
- ✅ **Multiple Selection Modes**: Support for both single and multiple selection
|
|
13
|
-
- 🔍 **Built-in Search**: Filter tree items with debounced search — simple field matching via `getValues`, or custom matching with subtree pruning via `filterItem`
|
|
14
|
-
- 🎨 **Customizable Rendering**: Custom render functions for items, headers, and footers
|
|
15
|
-
- 📦 **State Management**: Automatic state management with parent-child relationship handling
|
|
16
|
-
- 🎯 **Indeterminate States**: Visual indication for partially selected parent nodes
|
|
17
|
-
- 🔄 **Auto-promotion**: Automatically promotes single-child nodes to reduce nesting
|
|
18
|
-
- 🎭 **Icon Support**: Built-in support for icons and custom icon components
|
|
19
|
-
- 📱 **Responsive**: Works with Material-UI's Popper for positioning
|
|
20
|
-
- 📊 **Level-based Queries**: Get selected nodes grouped by tree level with filtering options
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
The component is part of the shared components library. Import it as follows:
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import TreeDropdown from '@components/TreeDropdown'
|
|
28
|
-
import type { TreeDropdownProps, TreeDropdownRef, TreeNode, TreeDefinition, SelectedState } from '@components/TreeDropdown'
|
|
29
|
-
|
|
30
|
-
// Optional: Import GetNodesByLevelOptions if you need to type variables
|
|
31
|
-
// import type { GetNodesByLevelOptions } from '@components/TreeDropdown/utils'
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Basic Usage
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
import { useState, useRef } from 'react'
|
|
38
|
-
import { Button } from '@mui/material'
|
|
39
|
-
import TreeDropdown from '@components/TreeDropdown'
|
|
40
|
-
import type { TreeDropdownRef, TreeDefinition } from '@components/TreeDropdown'
|
|
41
|
-
|
|
42
|
-
const MyComponent = () => {
|
|
43
|
-
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
|
|
44
|
-
const treeRef = useRef<TreeDropdownRef>(null)
|
|
45
|
-
|
|
46
|
-
const treeDefinition: TreeDefinition = {
|
|
47
|
-
children: [
|
|
48
|
-
{
|
|
49
|
-
id: '1',
|
|
50
|
-
label: 'Category 1',
|
|
51
|
-
children: [
|
|
52
|
-
{
|
|
53
|
-
id: '1-1',
|
|
54
|
-
label: 'Subcategory 1',
|
|
55
|
-
children: [
|
|
56
|
-
{ id: '1-1-1', label: 'Item 1' },
|
|
57
|
-
{ id: '1-1-2', label: 'Item 2' },
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<>
|
|
67
|
-
<Button onClick={(e) => setAnchorEl(e.currentTarget)}>Open Tree</Button>
|
|
68
|
-
<TreeDropdown
|
|
69
|
-
ref={treeRef}
|
|
70
|
-
anchorEl={anchorEl}
|
|
71
|
-
treeDefinition={treeDefinition}
|
|
72
|
-
multiple={true}
|
|
73
|
-
onChange={(tree) => console.log('Selected:', tree)}
|
|
74
|
-
/>
|
|
75
|
-
</>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Props
|
|
81
|
-
|
|
82
|
-
### TreeDropdownProps
|
|
83
|
-
|
|
84
|
-
| Prop | Type | Default | Description |
|
|
85
|
-
| ------------------------ | ---------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------- |
|
|
86
|
-
| `anchorEl` | `HTMLElement \| null` | **Required** | The element that the dropdown will anchor to |
|
|
87
|
-
| `treeDefinition` | `TreeDefinition` | **Required** | The tree structure definition |
|
|
88
|
-
| `onChange` | `(tree: TreeNode[]) => void` | `undefined` | Callback fired when selection changes |
|
|
89
|
-
| `multiple` | `boolean` | `true` | Enable multiple selection mode |
|
|
90
|
-
| `placement` | `PopperProps['placement']` | `'bottom-start'` | Placement of the dropdown relative to anchor |
|
|
91
|
-
| `sx` | `SxProps<Theme>` | `undefined` | Material-UI sx prop for styling |
|
|
92
|
-
| `IconComponent` | `React.ComponentType<{ fileId: string; width?: number \| string; height?: number \| string }>` | `undefined` | Custom component for rendering icons |
|
|
93
|
-
| `exposeOnlySelectedTree` | `boolean` | `false` | When `true`, `onChange` callback receives only selected branches instead of the entire tree |
|
|
94
|
-
|
|
95
|
-
## Types
|
|
96
|
-
|
|
97
|
-
### TreeNode
|
|
98
|
-
|
|
99
|
-
The basic node structure in the tree:
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
type TreeNode = {
|
|
103
|
-
id: string // Unique identifier (required)
|
|
104
|
-
label?: React.ReactNode // Display label
|
|
105
|
-
hintText?: React.ReactNode // Additional hint text below label
|
|
106
|
-
icon?: TreeNodeIcon // Icon definition
|
|
107
|
-
iconComponent?: React.ReactNode // Custom icon component
|
|
108
|
-
itemProps?: MenuItemProps // Props passed to underlying MenuItem
|
|
109
|
-
renderItem?: (params: {
|
|
110
|
-
nodeProps: TreeNode
|
|
111
|
-
checked?: boolean
|
|
112
|
-
indeterminate?: boolean
|
|
113
|
-
onSelect: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
114
|
-
}) => React.ReactNode // Custom render function
|
|
115
|
-
} & TreeDefinition // Note: status is part of TreeDefinition, not directly on TreeNode
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### TreeDefinition
|
|
119
|
-
|
|
120
|
-
Configuration for the tree structure:
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
type TreeDefinition = {
|
|
124
|
-
header?: React.ReactNode // Header content for this level
|
|
125
|
-
footer?: React.ReactNode // Footer content for this level
|
|
126
|
-
levelKey?: string // Unique key for this level (appears as data attribute)
|
|
127
|
-
searchProps?: {
|
|
128
|
-
getValues?: (item: TreeNode) => string[] // Simple filtering: strings to match against (case-insensitive `includes`)
|
|
129
|
-
filterItem?: (item: TreeNode, searchText: string) => TreeNode | null // Advanced: keep/drop AND reshape a node
|
|
130
|
-
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
131
|
-
noResultsText?: React.ReactNode
|
|
132
|
-
inputProps?: InputProps
|
|
133
|
-
}
|
|
134
|
-
children?: TreeNode[] // Child nodes
|
|
135
|
-
disablePromotion?: boolean // Disable auto-promotion of single children
|
|
136
|
-
status?: SelectedState // Initial selection state for this level
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### TreeDropdownRef
|
|
141
|
-
|
|
142
|
-
Methods exposed via the ref:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
type TreeDropdownRef = {
|
|
146
|
-
internalMapper: TreeMapper // Internal state mapper
|
|
147
|
-
getTree: () => TreeNode[] // Get the entire tree with current states
|
|
148
|
-
getSelectedBranches: () => TreeNode[] // Get only selected branches
|
|
149
|
-
getSelectedNodesByLevel: (options?: GetNodesByLevelOptions) => Record<number, TreeNode[]> // Get selected nodes grouped by level
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### GetNodesByLevelOptions
|
|
154
|
-
|
|
155
|
-
Options for the `getSelectedNodesByLevel` method:
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
type GetNodesByLevelOptions = {
|
|
159
|
-
filter?: (node: TreeNode) => boolean // Optional filter function to include/exclude nodes
|
|
160
|
-
includeChildren?: boolean // When true, includes children in the result; when false, excludes them (default: false)
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### SelectedState
|
|
165
|
-
|
|
166
|
-
Enumeration of selection states:
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
enum SelectedState {
|
|
170
|
-
CHECKED = 'CHECKED',
|
|
171
|
-
UNCHECKED = 'UNCHECKED',
|
|
172
|
-
INDETERMINATE = 'INDETERMINATE',
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Usage Examples
|
|
177
|
-
|
|
178
|
-
### Single Selection Mode
|
|
179
|
-
|
|
180
|
-
```tsx
|
|
181
|
-
<TreeDropdown
|
|
182
|
-
anchorEl={anchorEl}
|
|
183
|
-
treeDefinition={treeDefinition}
|
|
184
|
-
multiple={false}
|
|
185
|
-
onChange={(tree) => {
|
|
186
|
-
// Only one item can be selected at a time
|
|
187
|
-
console.log('Selected:', tree)
|
|
188
|
-
}}
|
|
189
|
-
/>
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### With Search
|
|
193
|
-
|
|
194
|
-
A level renders a search input whenever its `searchProps` is set. Matching is driven by one of two mutually exclusive hooks:
|
|
195
|
-
|
|
196
|
-
#### `getValues` — simple field matching
|
|
197
|
-
|
|
198
|
-
Return the strings a node should be matched against. The component does the matching for you (case-insensitive `includes`) and keeps or drops each item unchanged.
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
const treeDefinition: TreeDefinition = {
|
|
202
|
-
searchProps: {
|
|
203
|
-
getValues: (item) => [typeof item.label === 'string' ? item.label : '', typeof item.hintText === 'string' ? item.hintText : ''],
|
|
204
|
-
noResultsText: 'No items found',
|
|
205
|
-
inputProps: {
|
|
206
|
-
placeholder: 'Search items...',
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
children: [
|
|
210
|
-
/* ... */
|
|
211
|
-
],
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
#### `filterItem` — custom matching + subtree pruning
|
|
216
|
-
|
|
217
|
-
Use `filterItem` when you need custom match logic, or when a match on a parent should show only the matching branches underneath it. You receive the node and the (already lowercased) search text, and return:
|
|
218
|
-
|
|
219
|
-
- the node (optionally with a **modified `children`** array) to keep it, or
|
|
220
|
-
- `null` to drop it.
|
|
221
|
-
|
|
222
|
-
`filterItem` takes precedence over `getValues` when both are provided.
|
|
223
|
-
|
|
224
|
-
```tsx
|
|
225
|
-
// Keep a node if its label matches; otherwise prune its subtree down to
|
|
226
|
-
// only the branches that lead to a matching leaf.
|
|
227
|
-
const pruneToMatch = (node: TreeNode, text: string): TreeNode | null => {
|
|
228
|
-
if (!node.children?.length) {
|
|
229
|
-
return node.id.toLowerCase().includes(text) ? node : null
|
|
230
|
-
}
|
|
231
|
-
const children = node.children.map((c) => pruneToMatch(c, text)).filter(Boolean) as TreeNode[]
|
|
232
|
-
return children.length ? { ...node, children } : null
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const treeDefinition: TreeDefinition = {
|
|
236
|
-
searchProps: {
|
|
237
|
-
filterItem: (item, searchText) => {
|
|
238
|
-
const label = typeof item.label === 'string' ? item.label.toLowerCase() : ''
|
|
239
|
-
if (label.includes(searchText)) return item // parent match → keep whole subtree
|
|
240
|
-
return pruneToMatch(item, searchText) // otherwise → keep only matching branches
|
|
241
|
-
},
|
|
242
|
-
noResultsText: 'No items found',
|
|
243
|
-
},
|
|
244
|
-
children: [
|
|
245
|
-
/* ... */
|
|
246
|
-
],
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
> **Tip:** When pruning, consider stripping `searchProps` from levels that no longer exceed your "show search" threshold, so a search box isn't rendered over a single remaining result.
|
|
251
|
-
|
|
252
|
-
### With Headers and Footers
|
|
253
|
-
|
|
254
|
-
```tsx
|
|
255
|
-
const treeDefinition: TreeDefinition = {
|
|
256
|
-
header: <div>Category Header</div>,
|
|
257
|
-
footer: <div>Category Footer</div>,
|
|
258
|
-
children: [
|
|
259
|
-
{
|
|
260
|
-
id: '1',
|
|
261
|
-
label: 'Category 1',
|
|
262
|
-
header: <div>Subcategory Header</div>,
|
|
263
|
-
footer: <div>Subcategory Footer</div>,
|
|
264
|
-
children: [
|
|
265
|
-
/* ... */
|
|
266
|
-
],
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### With Icons
|
|
273
|
-
|
|
274
|
-
```tsx
|
|
275
|
-
// Using icon property
|
|
276
|
-
const treeDefinition: TreeDefinition = {
|
|
277
|
-
children: [
|
|
278
|
-
{
|
|
279
|
-
id: '1',
|
|
280
|
-
label: 'Item with Icon',
|
|
281
|
-
icon: {
|
|
282
|
-
fileId: 'icon-id',
|
|
283
|
-
width: 20,
|
|
284
|
-
height: 20,
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// With custom IconComponent
|
|
291
|
-
<TreeDropdown
|
|
292
|
-
anchorEl={anchorEl}
|
|
293
|
-
treeDefinition={treeDefinition}
|
|
294
|
-
IconComponent={({ fileId, width, height }) => (
|
|
295
|
-
<img src={`/icons/${fileId}.svg`} width={width} height={height} />
|
|
296
|
-
)}
|
|
297
|
-
/>
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### Custom Item Rendering
|
|
301
|
-
|
|
302
|
-
```tsx
|
|
303
|
-
const treeDefinition: TreeDefinition = {
|
|
304
|
-
children: [
|
|
305
|
-
{
|
|
306
|
-
id: '1',
|
|
307
|
-
label: 'Custom Item',
|
|
308
|
-
renderItem: ({ nodeProps, checked, indeterminate, onSelect }) => (
|
|
309
|
-
<div style={{ padding: '8px' }}>
|
|
310
|
-
<input type="checkbox" checked={checked} onChange={onSelect} />
|
|
311
|
-
<span>{nodeProps.label}</span>
|
|
312
|
-
{indeterminate && <span> (partial)</span>}
|
|
313
|
-
</div>
|
|
314
|
-
),
|
|
315
|
-
},
|
|
316
|
-
],
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Using Ref Methods
|
|
321
|
-
|
|
322
|
-
```tsx
|
|
323
|
-
const treeRef = useRef<TreeDropdownRef>(null)
|
|
324
|
-
|
|
325
|
-
// Get entire tree with current states
|
|
326
|
-
const handleGetTree = () => {
|
|
327
|
-
const tree = treeRef.current?.getTree()
|
|
328
|
-
console.log('Full tree:', tree)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Get only selected branches
|
|
332
|
-
const handleGetSelected = () => {
|
|
333
|
-
const selected = treeRef.current?.getSelectedBranches()
|
|
334
|
-
console.log('Selected branches:', selected)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Get selected nodes grouped by level
|
|
338
|
-
const handleGetByLevel = () => {
|
|
339
|
-
const nodesByLevel = treeRef.current?.getSelectedNodesByLevel({
|
|
340
|
-
filter: (node) => node.status === SelectedState.CHECKED, // Only fully checked nodes
|
|
341
|
-
includeChildren: false, // Exclude children from the result
|
|
342
|
-
})
|
|
343
|
-
console.log('Nodes by level:', nodesByLevel)
|
|
344
|
-
// Result: { 0: [TreeNode, ...], 1: [TreeNode, ...], ... }
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
;<TreeDropdown ref={treeRef} anchorEl={anchorEl} treeDefinition={treeDefinition} />
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Get Selected Nodes by Level
|
|
351
|
-
|
|
352
|
-
The `getSelectedNodesByLevel` method allows you to retrieve selected nodes grouped by their depth level in the tree:
|
|
353
|
-
|
|
354
|
-
```tsx
|
|
355
|
-
const treeRef = useRef<TreeDropdownRef>(null)
|
|
356
|
-
|
|
357
|
-
// Get all selected nodes grouped by level
|
|
358
|
-
const handleGetByLevel = () => {
|
|
359
|
-
const nodesByLevel = treeRef.current?.getSelectedNodesByLevel()
|
|
360
|
-
// Result: { 0: [TreeNode, ...], 1: [TreeNode, ...], 2: [TreeNode, ...] }
|
|
361
|
-
console.log('Level 0 nodes:', nodesByLevel?.[0])
|
|
362
|
-
console.log('Level 1 nodes:', nodesByLevel?.[1])
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// With filtering - only get fully checked nodes
|
|
366
|
-
const handleGetCheckedByLevel = () => {
|
|
367
|
-
const nodesByLevel = treeRef.current?.getSelectedNodesByLevel({
|
|
368
|
-
filter: (node) => node.status === SelectedState.CHECKED,
|
|
369
|
-
})
|
|
370
|
-
console.log('Fully checked nodes by level:', nodesByLevel)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Include children in the result
|
|
374
|
-
const handleGetWithChildren = () => {
|
|
375
|
-
const nodesByLevel = treeRef.current?.getSelectedNodesByLevel({
|
|
376
|
-
includeChildren: true, // Include children in each node
|
|
377
|
-
})
|
|
378
|
-
console.log('Nodes with children:', nodesByLevel)
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Exclude children (default behavior)
|
|
382
|
-
const handleGetWithoutChildren = () => {
|
|
383
|
-
const nodesByLevel = treeRef.current?.getSelectedNodesByLevel({
|
|
384
|
-
includeChildren: false, // Exclude children (default)
|
|
385
|
-
})
|
|
386
|
-
// Each node in the result will not have its children property
|
|
387
|
-
console.log('Nodes without children:', nodesByLevel)
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
**Use Cases:**
|
|
392
|
-
|
|
393
|
-
- Display selected items grouped by category level
|
|
394
|
-
- Validate selections at specific tree levels
|
|
395
|
-
- Process selections level by level
|
|
396
|
-
- Filter and transform selected data by depth
|
|
397
|
-
|
|
398
|
-
### With Level Keys
|
|
399
|
-
|
|
400
|
-
Level keys are useful for styling or testing specific levels:
|
|
401
|
-
|
|
402
|
-
```tsx
|
|
403
|
-
const treeDefinition: TreeDefinition = {
|
|
404
|
-
levelKey: 'category',
|
|
405
|
-
children: [
|
|
406
|
-
{
|
|
407
|
-
id: '1',
|
|
408
|
-
label: 'Category 1',
|
|
409
|
-
levelKey: 'subcategory',
|
|
410
|
-
children: [
|
|
411
|
-
{
|
|
412
|
-
id: '1-1',
|
|
413
|
-
label: 'Item',
|
|
414
|
-
levelKey: 'item',
|
|
415
|
-
},
|
|
416
|
-
],
|
|
417
|
-
},
|
|
418
|
-
],
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Results in data attributes:
|
|
422
|
-
// <div data-level-key="category">...</div>
|
|
423
|
-
// <div data-level-key="subcategory">...</div>
|
|
424
|
-
// <div data-level-key="item">...</div>
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Pre-selected Items
|
|
428
|
-
|
|
429
|
-
You can pre-select items by setting the `status` property in the `TreeDefinition`:
|
|
430
|
-
|
|
431
|
-
```tsx
|
|
432
|
-
import { SelectedState } from '@components/TreeDropdown'
|
|
433
|
-
|
|
434
|
-
const treeDefinition: TreeDefinition = {
|
|
435
|
-
status: SelectedState.CHECKED, // Pre-select at the root level
|
|
436
|
-
children: [
|
|
437
|
-
{
|
|
438
|
-
id: '1',
|
|
439
|
-
label: 'Pre-selected Item',
|
|
440
|
-
status: SelectedState.CHECKED, // Pre-select this node
|
|
441
|
-
children: [
|
|
442
|
-
{
|
|
443
|
-
id: '1-1',
|
|
444
|
-
label: 'Child Item',
|
|
445
|
-
status: SelectedState.CHECKED, // Pre-select this child
|
|
446
|
-
},
|
|
447
|
-
],
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
}
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
**Note**: The `status` property is part of `TreeDefinition`, so it can be set at any level of the tree hierarchy.
|
|
454
|
-
|
|
455
|
-
### Expose Only Selected Tree
|
|
456
|
-
|
|
457
|
-
When `exposeOnlySelectedTree` is `true`, the `onChange` callback receives only the selected branches instead of the entire tree. This is useful when you only need to work with selected items:
|
|
458
|
-
|
|
459
|
-
```tsx
|
|
460
|
-
// With exposeOnlySelectedTree = false (default)
|
|
461
|
-
// onChange receives the entire tree with all nodes and their states
|
|
462
|
-
<TreeDropdown
|
|
463
|
-
anchorEl={anchorEl}
|
|
464
|
-
treeDefinition={treeDefinition}
|
|
465
|
-
onChange={(tree) => {
|
|
466
|
-
// tree contains ALL nodes, with their selection states
|
|
467
|
-
console.log('Full tree:', tree)
|
|
468
|
-
}}
|
|
469
|
-
/>
|
|
470
|
-
|
|
471
|
-
// With exposeOnlySelectedTree = true
|
|
472
|
-
// onChange receives only the selected branches
|
|
473
|
-
<TreeDropdown
|
|
474
|
-
anchorEl={anchorEl}
|
|
475
|
-
treeDefinition={treeDefinition}
|
|
476
|
-
exposeOnlySelectedTree={true}
|
|
477
|
-
onChange={(selectedBranches) => {
|
|
478
|
-
// selectedBranches contains ONLY selected nodes and their selected children
|
|
479
|
-
// Unselected branches are filtered out
|
|
480
|
-
console.log('Selected branches only:', selectedBranches)
|
|
481
|
-
}}
|
|
482
|
-
/>
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
**Note**: The `getSelectedBranches()` method on the ref always returns only selected branches, regardless of this prop setting.
|
|
486
|
-
|
|
487
|
-
## Advanced Features
|
|
488
|
-
|
|
489
|
-
### Auto-Promotion
|
|
490
|
-
|
|
491
|
-
The component automatically promotes single-child nodes to reduce unnecessary nesting. If a node has exactly one child and that child has children, the intermediate level is skipped.
|
|
492
|
-
|
|
493
|
-
To disable this behavior:
|
|
494
|
-
|
|
495
|
-
```tsx
|
|
496
|
-
const treeDefinition: TreeDefinition = {
|
|
497
|
-
children: [
|
|
498
|
-
{
|
|
499
|
-
id: '1',
|
|
500
|
-
label: 'Parent',
|
|
501
|
-
disablePromotion: true, // Disable promotion for this node
|
|
502
|
-
children: [
|
|
503
|
-
{
|
|
504
|
-
id: '1-1',
|
|
505
|
-
label: 'Only Child',
|
|
506
|
-
children: [
|
|
507
|
-
/* ... */
|
|
508
|
-
],
|
|
509
|
-
},
|
|
510
|
-
],
|
|
511
|
-
},
|
|
512
|
-
],
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### Styling
|
|
517
|
-
|
|
518
|
-
Customize the appearance using the `sx` prop:
|
|
519
|
-
|
|
520
|
-
```tsx
|
|
521
|
-
<TreeDropdown
|
|
522
|
-
anchorEl={anchorEl}
|
|
523
|
-
treeDefinition={treeDefinition}
|
|
524
|
-
sx={{
|
|
525
|
-
'& .MuiPopper-dropdown': {
|
|
526
|
-
width: '300px',
|
|
527
|
-
maxHeight: '400px',
|
|
528
|
-
},
|
|
529
|
-
}}
|
|
530
|
-
/>
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
### Positioning
|
|
534
|
-
|
|
535
|
-
Control dropdown placement:
|
|
536
|
-
|
|
537
|
-
```tsx
|
|
538
|
-
<TreeDropdown
|
|
539
|
-
anchorEl={anchorEl}
|
|
540
|
-
treeDefinition={treeDefinition}
|
|
541
|
-
placement="bottom-end" // Options: 'bottom-start', 'bottom-end', 'top-start', etc.
|
|
542
|
-
/>
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
## Selection Behavior
|
|
546
|
-
|
|
547
|
-
### Multiple Selection Mode
|
|
548
|
-
|
|
549
|
-
- Clicking a checkbox toggles its state
|
|
550
|
-
- Selecting a parent automatically selects all children
|
|
551
|
-
- Deselecting a parent automatically deselects all children
|
|
552
|
-
- Parent nodes show indeterminate state when some (but not all) children are selected
|
|
553
|
-
- The `onChange` callback behavior depends on `exposeOnlySelectedTree`:
|
|
554
|
-
- When `false` (default): receives the entire tree with updated states
|
|
555
|
-
- When `true`: receives only the selected branches (unselected nodes are filtered out)
|
|
556
|
-
|
|
557
|
-
### Single Selection Mode
|
|
558
|
-
|
|
559
|
-
- Only one item can be selected at a time
|
|
560
|
-
- Selecting a new item deselects the previously selected item
|
|
561
|
-
- Selection controls only appear on leaf nodes (nodes without children)
|
|
562
|
-
- Parent nodes without children can also be selected
|
|
563
|
-
|
|
564
|
-
## State Management
|
|
565
|
-
|
|
566
|
-
The component maintains an internal state mapper (`TreeMapper`) that tracks:
|
|
567
|
-
|
|
568
|
-
- Selection status of each node (`CHECKED`, `UNCHECKED`, `INDETERMINATE`)
|
|
569
|
-
- Parent-child relationships
|
|
570
|
-
|
|
571
|
-
You can access this internal state via the ref:
|
|
572
|
-
|
|
573
|
-
```tsx
|
|
574
|
-
const internalMapper = treeRef.current?.internalMapper
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
## Best Practices
|
|
578
|
-
|
|
579
|
-
1. **Unique IDs**: Ensure all node IDs are unique across the entire tree
|
|
580
|
-
2. **Search Performance**: Use `getValues` efficiently - it's called for every item during filtering
|
|
581
|
-
3. **Large Trees**: For very large trees, consider implementing virtualization (not currently built-in)
|
|
582
|
-
4. **Accessibility**: The component uses Material-UI's MenuItem which provides basic accessibility, but consider adding ARIA labels for complex use cases
|
|
583
|
-
5. **State Initialization**: Use `status` prop to initialize selection state rather than managing it externally
|
|
584
|
-
6. **Debouncing**: Search is automatically debounced (300ms default), but you can customize this in the search hook if needed
|
|
585
|
-
|
|
586
|
-
## Component Architecture
|
|
587
|
-
|
|
588
|
-
The component is built with the following structure:
|
|
589
|
-
|
|
590
|
-
- **TreeDropdown.tsx**: Main component that manages state and provides the ref API
|
|
591
|
-
- **TreeNodeList**: Renders a list of tree nodes at a given level
|
|
592
|
-
- **TreeNodeItem**: Individual tree node component with hover-based submenu
|
|
593
|
-
- **TreeDropdownProvider**: Context provider for sharing state across the tree
|
|
594
|
-
- **utils.ts**: Utility functions for tree manipulation, normalization, and selection logic
|
|
595
|
-
- **hooks/useSearch.ts**: Search functionality with debouncing
|
|
596
|
-
- **hooks/useTreeDropdown.ts**: Hook to access tree dropdown context
|
|
597
|
-
|
|
598
|
-
## Notes
|
|
599
|
-
|
|
600
|
-
- The component uses Material-UI's Popper for positioning nested menus
|
|
601
|
-
- Nested menus appear on hover by default
|
|
602
|
-
- Scrolling disables hover behavior temporarily to prevent menu flickering
|
|
603
|
-
- The component automatically handles parent-child selection relationships
|
|
604
|
-
- Search is case-insensitive by default
|