@djangocfg/ui-tools 2.1.317 → 2.1.318
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/dist/TreeRoot-A3J65L6F.mjs +4 -0
- package/dist/{TreeRoot-R6XVHYQK.mjs.map → TreeRoot-A3J65L6F.mjs.map} +1 -1
- package/dist/TreeRoot-DSK5JILT.cjs +19 -0
- package/dist/{TreeRoot-RAMQSBMO.cjs.map → TreeRoot-DSK5JILT.cjs.map} +1 -1
- package/dist/{chunk-44ZTWYAF.cjs → chunk-3Z3A7FHA.cjs} +17 -6
- package/dist/chunk-3Z3A7FHA.cjs.map +1 -0
- package/dist/{chunk-NTJL2SXK.mjs → chunk-MOME6KYD.mjs} +17 -6
- package/dist/chunk-MOME6KYD.mjs.map +1 -0
- package/dist/file-icon/index.cjs +59 -3
- package/dist/file-icon/index.cjs.map +1 -1
- package/dist/file-icon/index.d.cts +33 -4
- package/dist/file-icon/index.d.ts +33 -4
- package/dist/file-icon/index.mjs +60 -5
- package/dist/file-icon/index.mjs.map +1 -1
- package/dist/index.cjs +33 -33
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/tree/index.cjs +33 -33
- package/dist/tree/index.d.cts +6 -4
- package/dist/tree/index.d.ts +6 -4
- package/dist/tree/index.mjs +1 -1
- package/dist/{types-Cclwv4Hl.d.cts → types-CevSbyfD.d.cts} +6 -0
- package/dist/{types-Cclwv4Hl.d.ts → types-CevSbyfD.d.ts} +6 -0
- package/package.json +6 -6
- package/src/tools/FileIcon/FileIcon.tsx +13 -2
- package/src/tools/FileIcon/index.ts +6 -0
- package/src/tools/FileIcon/specialFolders.ts +93 -0
- package/src/tools/FileIcon/treeAdapter.tsx +8 -0
- package/src/tools/Tree/README.md +46 -2
- package/src/tools/Tree/Tree.story.tsx +36 -0
- package/src/tools/Tree/TreeRoot.tsx +2 -0
- package/src/tools/Tree/components/TreeContent.tsx +3 -1
- package/src/tools/Tree/context/TreeContext.tsx +4 -1
- package/src/tools/Tree/data/flatten.ts +10 -1
- package/src/tools/Tree/types.ts +7 -0
- package/dist/TreeRoot-R6XVHYQK.mjs +0 -4
- package/dist/TreeRoot-RAMQSBMO.cjs +0 -19
- package/dist/chunk-44ZTWYAF.cjs.map +0 -1
- package/dist/chunk-NTJL2SXK.mjs.map +0 -1
|
@@ -159,6 +159,12 @@ interface TreeRootProps<T> {
|
|
|
159
159
|
* on `activationMode`). Folders never call this — they toggle instead.
|
|
160
160
|
*/
|
|
161
161
|
onActivate?: (node: TreeNode<T>, opts: TreeActivateOptions) => void;
|
|
162
|
+
/**
|
|
163
|
+
* Optional predicate. Nodes returning `false` (and their descendants) are
|
|
164
|
+
* not rendered and not searchable. Use this to hide dot-files, system
|
|
165
|
+
* entries, or anything else the consumer wants to filter out.
|
|
166
|
+
*/
|
|
167
|
+
filterNode?: (node: TreeNode<T>) => boolean;
|
|
162
168
|
/** Show built-in search input. Default: false. */
|
|
163
169
|
enableSearch?: boolean;
|
|
164
170
|
/** Type printable letters to jump to a matching name. Default: true. */
|
|
@@ -159,6 +159,12 @@ interface TreeRootProps<T> {
|
|
|
159
159
|
* on `activationMode`). Folders never call this — they toggle instead.
|
|
160
160
|
*/
|
|
161
161
|
onActivate?: (node: TreeNode<T>, opts: TreeActivateOptions) => void;
|
|
162
|
+
/**
|
|
163
|
+
* Optional predicate. Nodes returning `false` (and their descendants) are
|
|
164
|
+
* not rendered and not searchable. Use this to hide dot-files, system
|
|
165
|
+
* entries, or anything else the consumer wants to filter out.
|
|
166
|
+
*/
|
|
167
|
+
filterNode?: (node: TreeNode<T>) => boolean;
|
|
162
168
|
/** Show built-in search input. Default: false. */
|
|
163
169
|
enableSearch?: boolean;
|
|
164
170
|
/** Type printable letters to jump to a matching name. Default: true. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-tools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.318",
|
|
4
4
|
"description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-tools",
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
"check": "tsc --noEmit"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@djangocfg/i18n": "^2.1.
|
|
105
|
-
"@djangocfg/ui-core": "^2.1.
|
|
104
|
+
"@djangocfg/i18n": "^2.1.318",
|
|
105
|
+
"@djangocfg/ui-core": "^2.1.318",
|
|
106
106
|
"consola": "^3.4.2",
|
|
107
107
|
"lodash-es": "^4.18.1",
|
|
108
108
|
"lucide-react": "^0.545.0",
|
|
@@ -155,10 +155,10 @@
|
|
|
155
155
|
"material-file-icons": "^2.4.0"
|
|
156
156
|
},
|
|
157
157
|
"devDependencies": {
|
|
158
|
-
"@djangocfg/i18n": "^2.1.
|
|
158
|
+
"@djangocfg/i18n": "^2.1.318",
|
|
159
159
|
"@djangocfg/playground": "workspace:*",
|
|
160
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
161
|
-
"@djangocfg/ui-core": "^2.1.
|
|
160
|
+
"@djangocfg/typescript-config": "^2.1.318",
|
|
161
|
+
"@djangocfg/ui-core": "^2.1.318",
|
|
162
162
|
"@types/lodash-es": "^4.17.12",
|
|
163
163
|
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
|
|
164
164
|
"@types/node": "^24.7.2",
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { File as FileFallback
|
|
4
|
+
import { File as FileFallback } from 'lucide-react';
|
|
5
5
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
6
|
|
|
7
7
|
import { getMaterialIconsSync, loadMaterialIcons } from './loader';
|
|
8
|
+
import { resolveFolderIcon, type FolderIconOverrides } from './specialFolders';
|
|
8
9
|
|
|
9
10
|
export type FileIconSize = 12 | 14 | 16 | 18 | 20 | 24 | 28 | 32 | 40 | 48 | 64;
|
|
10
11
|
|
|
@@ -17,6 +18,11 @@ export interface FileIconProps {
|
|
|
17
18
|
isExpanded?: boolean;
|
|
18
19
|
/** Pixel size of the icon. */
|
|
19
20
|
size?: FileIconSize;
|
|
21
|
+
/**
|
|
22
|
+
* Override or extend the built-in special-folder mapping. Keys are
|
|
23
|
+
* matched case-insensitively. Only used when `isFolder` is true.
|
|
24
|
+
*/
|
|
25
|
+
folderOverrides?: FolderIconOverrides;
|
|
20
26
|
className?: string;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -35,6 +41,7 @@ export function FileIcon({
|
|
|
35
41
|
isFolder = false,
|
|
36
42
|
isExpanded = false,
|
|
37
43
|
size = 16,
|
|
44
|
+
folderOverrides,
|
|
38
45
|
className,
|
|
39
46
|
}: FileIconProps) {
|
|
40
47
|
const [svg, setSvg] = useState<string | null>(() => {
|
|
@@ -59,7 +66,11 @@ export function FileIcon({
|
|
|
59
66
|
}, [isFolder, name]);
|
|
60
67
|
|
|
61
68
|
if (isFolder) {
|
|
62
|
-
const Icon =
|
|
69
|
+
const Icon = resolveFolderIcon({
|
|
70
|
+
name,
|
|
71
|
+
isExpanded,
|
|
72
|
+
overrides: folderOverrides,
|
|
73
|
+
});
|
|
63
74
|
return (
|
|
64
75
|
<Icon
|
|
65
76
|
className={cn('shrink-0 text-amber-500', className)}
|
|
@@ -6,4 +6,10 @@ export type { FileIconProps, FileIconSize } from './FileIcon';
|
|
|
6
6
|
export { createFileIconSlot } from './treeAdapter';
|
|
7
7
|
export type { CreateFileIconSlotOptions } from './treeAdapter';
|
|
8
8
|
|
|
9
|
+
export { resolveFolderIcon } from './specialFolders';
|
|
10
|
+
export type {
|
|
11
|
+
ResolveFolderIconOptions,
|
|
12
|
+
FolderIconOverrides,
|
|
13
|
+
} from './specialFolders';
|
|
14
|
+
|
|
9
15
|
export { loadMaterialIcons, getMaterialIconsSync } from './loader';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BookOpen,
|
|
5
|
+
FlaskConical,
|
|
6
|
+
Folder,
|
|
7
|
+
FolderCode,
|
|
8
|
+
FolderGit2,
|
|
9
|
+
FolderInput,
|
|
10
|
+
FolderOpen,
|
|
11
|
+
FolderOutput,
|
|
12
|
+
Github,
|
|
13
|
+
Image as ImageIcon,
|
|
14
|
+
Package,
|
|
15
|
+
Settings,
|
|
16
|
+
Terminal,
|
|
17
|
+
type LucideIcon,
|
|
18
|
+
} from 'lucide-react';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Conventional folder names → Lucide icon. Lookup is case-insensitive and
|
|
22
|
+
* trimmed. Anything not in this map falls back to a generic folder icon.
|
|
23
|
+
*
|
|
24
|
+
* Designed to feel "right" across the common dev project layouts (Node,
|
|
25
|
+
* Python, Go, Rust, Django, Next, Vite). Not exhaustive on purpose —
|
|
26
|
+
* keeps the bundle light and the behaviour predictable.
|
|
27
|
+
*/
|
|
28
|
+
const SPECIAL_FOLDERS: Record<string, LucideIcon> = {
|
|
29
|
+
src: FolderCode,
|
|
30
|
+
source: FolderCode,
|
|
31
|
+
lib: FolderCode,
|
|
32
|
+
app: FolderCode,
|
|
33
|
+
packages: Package,
|
|
34
|
+
node_modules: Package,
|
|
35
|
+
vendor: Package,
|
|
36
|
+
public: FolderInput,
|
|
37
|
+
static: FolderInput,
|
|
38
|
+
assets: ImageIcon,
|
|
39
|
+
images: ImageIcon,
|
|
40
|
+
media: ImageIcon,
|
|
41
|
+
dist: FolderOutput,
|
|
42
|
+
build: FolderOutput,
|
|
43
|
+
out: FolderOutput,
|
|
44
|
+
'.next': FolderOutput,
|
|
45
|
+
'.nuxt': FolderOutput,
|
|
46
|
+
'.turbo': FolderOutput,
|
|
47
|
+
docs: BookOpen,
|
|
48
|
+
documentation: BookOpen,
|
|
49
|
+
tests: FlaskConical,
|
|
50
|
+
test: FlaskConical,
|
|
51
|
+
__tests__: FlaskConical,
|
|
52
|
+
__test__: FlaskConical,
|
|
53
|
+
scripts: Terminal,
|
|
54
|
+
bin: Terminal,
|
|
55
|
+
config: Settings,
|
|
56
|
+
configs: Settings,
|
|
57
|
+
'.config': Settings,
|
|
58
|
+
'.vscode': Settings,
|
|
59
|
+
'.idea': Settings,
|
|
60
|
+
'.git': FolderGit2,
|
|
61
|
+
'.github': Github,
|
|
62
|
+
'.gitlab': FolderGit2,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface ResolveFolderIconOptions {
|
|
66
|
+
/** Folder display name (no path). */
|
|
67
|
+
name: string;
|
|
68
|
+
/** Open / closed state — only used for the *generic* folder fallback. */
|
|
69
|
+
isExpanded?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Optional override map. Wins over the built-in table. Keys are
|
|
72
|
+
* matched case-insensitively after `trim()`.
|
|
73
|
+
*/
|
|
74
|
+
overrides?: Record<string, LucideIcon>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Pick the right folder icon for `name`. Returns `Folder` / `FolderOpen`
|
|
79
|
+
* for unknown names so callers can render the generic open/closed pair.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveFolderIcon({
|
|
82
|
+
name,
|
|
83
|
+
isExpanded = false,
|
|
84
|
+
overrides,
|
|
85
|
+
}: ResolveFolderIconOptions): LucideIcon {
|
|
86
|
+
const key = name.trim().toLowerCase();
|
|
87
|
+
if (overrides?.[key]) return overrides[key];
|
|
88
|
+
const special = SPECIAL_FOLDERS[key];
|
|
89
|
+
if (special) return special;
|
|
90
|
+
return isExpanded ? FolderOpen : Folder;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type FolderIconOverrides = Record<string, LucideIcon>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { TreeNode, TreeRowSlot } from '../Tree/types';
|
|
4
4
|
import { FileIcon, type FileIconSize } from './FileIcon';
|
|
5
|
+
import type { FolderIconOverrides } from './specialFolders';
|
|
5
6
|
|
|
6
7
|
export interface CreateFileIconSlotOptions<T> {
|
|
7
8
|
/**
|
|
@@ -11,6 +12,11 @@ export interface CreateFileIconSlotOptions<T> {
|
|
|
11
12
|
getName: (node: TreeNode<T>) => string;
|
|
12
13
|
/** Pixel size for both file and folder icons. Default: 16. */
|
|
13
14
|
size?: FileIconSize;
|
|
15
|
+
/**
|
|
16
|
+
* Override or extend the built-in special-folder mapping (e.g. give
|
|
17
|
+
* `tests` a custom icon). Keys are matched case-insensitively.
|
|
18
|
+
*/
|
|
19
|
+
folderOverrides?: FolderIconOverrides;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -29,6 +35,7 @@ export interface CreateFileIconSlotOptions<T> {
|
|
|
29
35
|
export function createFileIconSlot<T>({
|
|
30
36
|
getName,
|
|
31
37
|
size = 16,
|
|
38
|
+
folderOverrides,
|
|
32
39
|
}: CreateFileIconSlotOptions<T>): TreeRowSlot<T> {
|
|
33
40
|
return ({ node, isFolder, isExpanded }) => (
|
|
34
41
|
<FileIcon
|
|
@@ -36,6 +43,7 @@ export function createFileIconSlot<T>({
|
|
|
36
43
|
isFolder={isFolder}
|
|
37
44
|
isExpanded={isExpanded}
|
|
38
45
|
size={size}
|
|
46
|
+
folderOverrides={folderOverrides}
|
|
39
47
|
/>
|
|
40
48
|
);
|
|
41
49
|
}
|
package/src/tools/Tree/README.md
CHANGED
|
@@ -200,6 +200,7 @@ Toggle the bar with `appearance.showActiveIndicator`. Intensity scales with `app
|
|
|
200
200
|
| --- | --- |
|
|
201
201
|
| Default | sensible cozy defaults |
|
|
202
202
|
| WithActivationModes | single-click / double-click / preview semantics |
|
|
203
|
+
| WithHiddenFilter | `filterNode` toggle hides dot-files |
|
|
203
204
|
| Densities | three density presets side-by-side |
|
|
204
205
|
| WithIcons | file-type icons through `renderIcon` |
|
|
205
206
|
| WithStatus | modified / error / disabled rows through `renderLabel` |
|
|
@@ -213,6 +214,35 @@ Toggle the bar with `appearance.showActiveIndicator`. Intensity scales with `app
|
|
|
213
214
|
| LargeTree | ~500 nodes scalability check |
|
|
214
215
|
| Playground | every knob exposed as a control |
|
|
215
216
|
|
|
217
|
+
## Filtering nodes
|
|
218
|
+
|
|
219
|
+
`filterNode` is a single predicate that decides which nodes appear at all.
|
|
220
|
+
Nodes returning `false` (and their descendants) are excluded from rendering,
|
|
221
|
+
keyboard navigation, and search.
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
const [showHidden, setShowHidden] = useState(false);
|
|
225
|
+
|
|
226
|
+
<TreeRoot
|
|
227
|
+
data={data}
|
|
228
|
+
getItemName={(n) => n.data.name}
|
|
229
|
+
filterNode={(n) => showHidden || !n.data.name.startsWith('.')}
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
This is intentionally minimal — Tree is generic over `T` and has no opinion
|
|
234
|
+
on what "hidden" means in your domain. If your backend already provides
|
|
235
|
+
flags like `entry.isHidden` / `entry.isSystem`, use them directly:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
filterNode={(n) => showHidden || (!n.data.isHidden && !n.data.isSystem)}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> **Frontend note.** From the browser you cannot read OS-level hidden
|
|
242
|
+
> attributes (Windows `FILE_ATTRIBUTE_HIDDEN`, macOS `kIsInvisible`).
|
|
243
|
+
> Either filter by name (Unix dot-prefix is the de-facto convention), or
|
|
244
|
+
> let your backend determine those flags and forward them in `node.data`.
|
|
245
|
+
|
|
216
246
|
## Activation modes
|
|
217
247
|
|
|
218
248
|
How a leaf becomes "activated" (opened) on pointer interaction is controlled
|
|
@@ -264,8 +294,22 @@ import { createFileIconSlot } from '@djangocfg/ui-tools/file-icon';
|
|
|
264
294
|
|
|
265
295
|
`material-file-icons` is declared in `optionalDependencies`. If it's not
|
|
266
296
|
installed, `<FileIcon>` falls back to a Lucide `File` icon — no warnings, no
|
|
267
|
-
runtime errors.
|
|
268
|
-
|
|
297
|
+
runtime errors.
|
|
298
|
+
|
|
299
|
+
Folders use a small built-in mapping (`src` → `FolderCode`,
|
|
300
|
+
`node_modules` → `Package`, `.git` → `FolderGit2`, `dist`/`build`/`.next`
|
|
301
|
+
→ `FolderOutput`, `tests`/`__tests__` → `FlaskConical`, …). Anything not
|
|
302
|
+
in the table renders the generic `Folder` / `FolderOpen` pair. Override
|
|
303
|
+
or extend the table per-call:
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
import { FolderHeart } from 'lucide-react';
|
|
307
|
+
|
|
308
|
+
renderIcon={createFileIconSlot({
|
|
309
|
+
getName: (n) => n.data.name,
|
|
310
|
+
folderOverrides: { favorites: FolderHeart },
|
|
311
|
+
})}
|
|
312
|
+
```
|
|
269
313
|
|
|
270
314
|
## Out of scope (today)
|
|
271
315
|
|
|
@@ -159,6 +159,42 @@ export const WithActivationModes = () => {
|
|
|
159
159
|
);
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// 1.7) WithHiddenFilter — filterNode prop hides dot-prefixed entries
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
const fsWithDotfiles: TreeNode<FsNode>[] = [
|
|
167
|
+
...fs,
|
|
168
|
+
{ id: '.env', data: { name: '.env' } },
|
|
169
|
+
{ id: '.gitignore', data: { name: '.gitignore' } },
|
|
170
|
+
{
|
|
171
|
+
id: '.git',
|
|
172
|
+
data: { name: '.git' },
|
|
173
|
+
isFolder: true,
|
|
174
|
+
children: [
|
|
175
|
+
{ id: '.git/HEAD', data: { name: 'HEAD' } },
|
|
176
|
+
{ id: '.git/config', data: { name: 'config' } },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
export const WithHiddenFilter = () => {
|
|
182
|
+
const [showHidden] = useBoolean('showHidden', {
|
|
183
|
+
defaultValue: false,
|
|
184
|
+
label: 'Show hidden',
|
|
185
|
+
});
|
|
186
|
+
return (
|
|
187
|
+
<div className="h-96 w-80 rounded-md border border-border bg-card">
|
|
188
|
+
<TreeRoot<FsNode>
|
|
189
|
+
data={fsWithDotfiles}
|
|
190
|
+
getItemName={getName}
|
|
191
|
+
initialExpandedIds={['src']}
|
|
192
|
+
filterNode={(n) => showHidden || !n.data.name.startsWith('.')}
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
162
198
|
// ---------------------------------------------------------------------------
|
|
163
199
|
// 2) Densities — three presets side-by-side for comparison
|
|
164
200
|
// ---------------------------------------------------------------------------
|
|
@@ -31,6 +31,7 @@ function TreeRoot<T>(props: TreeRootProps<T>) {
|
|
|
31
31
|
onSelectionChange,
|
|
32
32
|
onExpansionChange,
|
|
33
33
|
onActivate,
|
|
34
|
+
filterNode,
|
|
34
35
|
enableSearch = false,
|
|
35
36
|
enableTypeAhead = true,
|
|
36
37
|
showIndentGuides = false,
|
|
@@ -60,6 +61,7 @@ function TreeRoot<T>(props: TreeRootProps<T>) {
|
|
|
60
61
|
onSelectionChange={onSelectionChange}
|
|
61
62
|
onExpansionChange={onExpansionChange}
|
|
62
63
|
onActivate={onActivate}
|
|
64
|
+
filterNode={filterNode}
|
|
63
65
|
enableSearch={enableSearch}
|
|
64
66
|
showIndentGuides={showIndentGuides}
|
|
65
67
|
renderIcon={renderIcon}
|
|
@@ -4,6 +4,7 @@ import { Fragment, type ReactNode } from 'react';
|
|
|
4
4
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
5
5
|
|
|
6
6
|
import { useTreeContext } from '../context/TreeContext';
|
|
7
|
+
import { appearanceToStyle } from '../data/appearance';
|
|
7
8
|
import type { FlatRow, TreeRowRenderProps, TreeRowSlot } from '../types';
|
|
8
9
|
import { TreeRow } from './TreeRow';
|
|
9
10
|
import { TreeEmpty } from './TreeEmpty';
|
|
@@ -17,7 +18,7 @@ export interface TreeContentProps<T> {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function TreeContent<T>({ children, className, ariaLabel }: TreeContentProps<T>) {
|
|
20
|
-
const { flatRows, labels, selected, focused, matchingIds } = useTreeContext<T>();
|
|
21
|
+
const { flatRows, labels, selected, focused, matchingIds, appearance } = useTreeContext<T>();
|
|
21
22
|
|
|
22
23
|
if (flatRows.length === 0) {
|
|
23
24
|
return <TreeEmpty>{labels.empty}</TreeEmpty>;
|
|
@@ -28,6 +29,7 @@ export function TreeContent<T>({ children, className, ariaLabel }: TreeContentPr
|
|
|
28
29
|
role="tree"
|
|
29
30
|
aria-label={ariaLabel ?? labels.ariaLabel}
|
|
30
31
|
className={cn('relative flex flex-col py-1', className)}
|
|
32
|
+
style={appearanceToStyle(appearance)}
|
|
31
33
|
>
|
|
32
34
|
{flatRows.map((row: FlatRow<T>) => {
|
|
33
35
|
const slot: TreeRowRenderProps<T> = {
|
|
@@ -187,6 +187,7 @@ export interface TreeProviderProps<T>
|
|
|
187
187
|
| 'onSelectionChange'
|
|
188
188
|
| 'onExpansionChange'
|
|
189
189
|
| 'onActivate'
|
|
190
|
+
| 'filterNode'
|
|
190
191
|
| 'enableSearch'
|
|
191
192
|
| 'showIndentGuides'
|
|
192
193
|
| 'renderIcon'
|
|
@@ -237,6 +238,7 @@ export function TreeProvider<T>(props: TreeProviderProps<T>) {
|
|
|
237
238
|
onSelectionChange,
|
|
238
239
|
onExpansionChange,
|
|
239
240
|
onActivate,
|
|
241
|
+
filterNode,
|
|
240
242
|
enableSearch = false,
|
|
241
243
|
showIndentGuides = false,
|
|
242
244
|
renderIcon,
|
|
@@ -350,8 +352,9 @@ export function TreeProvider<T>(props: TreeProviderProps<T>) {
|
|
|
350
352
|
roots: data,
|
|
351
353
|
expandedIds: state.expanded,
|
|
352
354
|
cache: cacheRef.current,
|
|
355
|
+
filterNode,
|
|
353
356
|
}),
|
|
354
|
-
[data, state.expanded, state.cacheTick],
|
|
357
|
+
[data, state.expanded, state.cacheTick, filterNode],
|
|
355
358
|
);
|
|
356
359
|
|
|
357
360
|
// Search matches (case-insensitive substring on getItemName).
|
|
@@ -7,6 +7,8 @@ export interface FlattenInput<T> {
|
|
|
7
7
|
roots: TreeNode<T>[];
|
|
8
8
|
expandedIds: ReadonlySet<TreeItemId>;
|
|
9
9
|
cache: ChildCache<T>;
|
|
10
|
+
/** Optional predicate. Nodes returning `false` (and their descendants) are excluded. */
|
|
11
|
+
filterNode?: (node: TreeNode<T>) => boolean;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
const isNodeFolder = <T>(node: TreeNode<T>): boolean => {
|
|
@@ -21,11 +23,18 @@ const isNodeFolder = <T>(node: TreeNode<T>): boolean => {
|
|
|
21
23
|
* `expandedIds`. The output is ordered exactly as it should render,
|
|
22
24
|
* which keeps keyboard navigation (next/prev row) trivial.
|
|
23
25
|
*/
|
|
24
|
-
export function flattenTree<T>({
|
|
26
|
+
export function flattenTree<T>({
|
|
27
|
+
roots,
|
|
28
|
+
expandedIds,
|
|
29
|
+
cache,
|
|
30
|
+
filterNode,
|
|
31
|
+
}: FlattenInput<T>): FlatRow<T>[] {
|
|
25
32
|
const out: FlatRow<T>[] = [];
|
|
26
33
|
|
|
27
34
|
const walk = (nodes: TreeNode<T>[], level: number, parentId: TreeItemId | null) => {
|
|
28
35
|
for (const node of nodes) {
|
|
36
|
+
if (filterNode && !filterNode(node)) continue;
|
|
37
|
+
|
|
29
38
|
const isFolder = isNodeFolder(node);
|
|
30
39
|
const isExpanded = expandedIds.has(node.id);
|
|
31
40
|
const resolved = isFolder ? resolveChildren(cache, node) : { children: [], status: 'loaded' as const };
|
package/src/tools/Tree/types.ts
CHANGED
|
@@ -129,6 +129,13 @@ export interface TreeRootProps<T> {
|
|
|
129
129
|
*/
|
|
130
130
|
onActivate?: (node: TreeNode<T>, opts: TreeActivateOptions) => void;
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Optional predicate. Nodes returning `false` (and their descendants) are
|
|
134
|
+
* not rendered and not searchable. Use this to hide dot-files, system
|
|
135
|
+
* entries, or anything else the consumer wants to filter out.
|
|
136
|
+
*/
|
|
137
|
+
filterNode?: (node: TreeNode<T>) => boolean;
|
|
138
|
+
|
|
132
139
|
/** Show built-in search input. Default: false. */
|
|
133
140
|
enableSearch?: boolean;
|
|
134
141
|
/** Type printable letters to jump to a matching name. Default: true. */
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var chunk44ZTWYAF_cjs = require('./chunk-44ZTWYAF.cjs');
|
|
6
|
-
require('./chunk-WGEGR3DF.cjs');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Object.defineProperty(exports, "TreeRoot", {
|
|
11
|
-
enumerable: true,
|
|
12
|
-
get: function () { return chunk44ZTWYAF_cjs.TreeRoot; }
|
|
13
|
-
});
|
|
14
|
-
Object.defineProperty(exports, "default", {
|
|
15
|
-
enumerable: true,
|
|
16
|
-
get: function () { return chunk44ZTWYAF_cjs.TreeRoot_default; }
|
|
17
|
-
});
|
|
18
|
-
//# sourceMappingURL=TreeRoot-RAMQSBMO.cjs.map
|
|
19
|
-
//# sourceMappingURL=TreeRoot-RAMQSBMO.cjs.map
|