@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23

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.
Files changed (192) hide show
  1. package/README.md +401 -155
  2. package/config/jest.setup.js +10 -0
  3. package/dist/ts-res-ui-components.d.ts +1657 -76
  4. package/lib/components/common/QualifierContextControl.js +4 -1
  5. package/lib/components/common/ResourceTreeView.js +4 -1
  6. package/lib/components/forms/GenericQualifierTypeEditForm.d.ts +26 -0
  7. package/lib/components/forms/GenericQualifierTypeEditForm.js +166 -0
  8. package/lib/components/forms/QualifierEditForm.d.ts +1 -1
  9. package/lib/components/forms/index.d.ts +2 -0
  10. package/lib/components/forms/index.js +1 -0
  11. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +3 -0
  12. package/lib/components/orchestrator/ResourceOrchestrator.js +118 -51
  13. package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  14. package/lib/components/pickers/ResourcePicker/index.js +4 -2
  15. package/lib/components/views/CompiledView/index.js +75 -16
  16. package/lib/components/views/ConfigurationView/index.js +94 -35
  17. package/lib/components/views/FilterView/index.js +7 -4
  18. package/lib/components/views/GridView/EditableGridCell.d.ts +76 -0
  19. package/lib/components/views/GridView/EditableGridCell.js +224 -0
  20. package/lib/components/views/GridView/GridSelector.d.ts +43 -0
  21. package/lib/components/views/GridView/GridSelector.js +89 -0
  22. package/lib/components/views/GridView/MultiGridView.d.ts +85 -0
  23. package/lib/components/views/GridView/MultiGridView.js +196 -0
  24. package/lib/components/views/GridView/ResourceGrid.d.ts +38 -0
  25. package/lib/components/views/GridView/ResourceGrid.js +232 -0
  26. package/lib/components/views/GridView/SharedContextControls.d.ts +47 -0
  27. package/lib/components/views/GridView/SharedContextControls.js +95 -0
  28. package/lib/components/views/GridView/cells/BooleanCell.d.ts +44 -0
  29. package/lib/components/views/GridView/cells/BooleanCell.js +49 -0
  30. package/lib/components/views/GridView/cells/DropdownCell.d.ts +58 -0
  31. package/lib/components/views/GridView/cells/DropdownCell.js +182 -0
  32. package/lib/components/views/GridView/cells/StringCell.d.ts +57 -0
  33. package/lib/components/views/GridView/cells/StringCell.js +106 -0
  34. package/lib/components/views/GridView/cells/TriStateCell.d.ts +54 -0
  35. package/lib/components/views/GridView/cells/TriStateCell.js +112 -0
  36. package/lib/components/views/GridView/cells/index.d.ts +15 -0
  37. package/lib/components/views/GridView/cells/index.js +11 -0
  38. package/lib/components/views/GridView/index.d.ts +53 -0
  39. package/lib/components/views/GridView/index.js +212 -0
  40. package/lib/components/views/ImportView/index.js +22 -19
  41. package/lib/components/views/MessagesWindow/index.js +4 -1
  42. package/lib/components/views/ResolutionView/index.js +8 -5
  43. package/lib/contexts/ObservabilityContext.d.ts +85 -0
  44. package/lib/contexts/ObservabilityContext.js +98 -0
  45. package/lib/contexts/index.d.ts +2 -0
  46. package/lib/contexts/index.js +24 -0
  47. package/lib/hooks/useConfigurationState.d.ts +3 -3
  48. package/lib/hooks/useResolutionState.js +850 -246
  49. package/lib/hooks/useResourceData.d.ts +7 -4
  50. package/lib/hooks/useResourceData.js +185 -184
  51. package/lib/index.d.ts +5 -1
  52. package/lib/index.js +8 -1
  53. package/lib/namespaces/GridTools.d.ts +136 -0
  54. package/lib/namespaces/GridTools.js +138 -0
  55. package/lib/namespaces/ObservabilityTools.d.ts +3 -0
  56. package/lib/namespaces/ObservabilityTools.js +23 -0
  57. package/lib/namespaces/ResolutionTools.d.ts +2 -1
  58. package/lib/namespaces/ResolutionTools.js +2 -0
  59. package/lib/namespaces/index.d.ts +2 -0
  60. package/lib/namespaces/index.js +2 -0
  61. package/lib/test/integration/observability.integration.test.d.ts +2 -0
  62. package/lib/test/unit/hooks/useResourceData.test.d.ts +2 -0
  63. package/lib/test/unit/utils/downloadHelper.test.d.ts +2 -0
  64. package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
  65. package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
  66. package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
  67. package/lib/test/unit/workflows/validation.test.d.ts +2 -0
  68. package/lib/types/index.d.ts +387 -20
  69. package/lib/types/index.js +2 -1
  70. package/lib/utils/cellValidation.d.ts +113 -0
  71. package/lib/utils/cellValidation.js +248 -0
  72. package/lib/utils/downloadHelper.d.ts +66 -0
  73. package/lib/utils/downloadHelper.js +195 -0
  74. package/lib/utils/observability/factories.d.ts +29 -0
  75. package/lib/utils/observability/factories.js +58 -0
  76. package/lib/utils/observability/implementations.d.ts +61 -0
  77. package/lib/utils/observability/implementations.js +103 -0
  78. package/lib/utils/observability/index.d.ts +4 -0
  79. package/lib/utils/observability/index.js +26 -0
  80. package/lib/utils/observability/interfaces.d.ts +30 -0
  81. package/lib/utils/observability/interfaces.js +23 -0
  82. package/lib/utils/resolutionEditing.js +2 -1
  83. package/lib/utils/resourceSelector.d.ts +97 -0
  84. package/lib/utils/resourceSelector.js +195 -0
  85. package/lib/utils/resourceSelectors.d.ts +146 -0
  86. package/lib/utils/resourceSelectors.js +233 -0
  87. package/lib/utils/tsResIntegration.d.ts +6 -41
  88. package/lib/utils/tsResIntegration.js +20 -16
  89. package/lib/utils/zipLoader/zipProcessingHelpers.d.ts +3 -2
  90. package/lib/utils/zipLoader/zipProcessingHelpers.js +6 -5
  91. package/lib-commonjs/components/common/QualifierContextControl.js +4 -1
  92. package/lib-commonjs/components/common/ResourceTreeView.js +4 -1
  93. package/lib-commonjs/components/forms/GenericQualifierTypeEditForm.js +171 -0
  94. package/lib-commonjs/components/forms/index.js +3 -1
  95. package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +118 -51
  96. package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  97. package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
  98. package/lib-commonjs/components/views/CompiledView/index.js +75 -16
  99. package/lib-commonjs/components/views/ConfigurationView/index.js +93 -34
  100. package/lib-commonjs/components/views/FilterView/index.js +7 -4
  101. package/lib-commonjs/components/views/GridView/EditableGridCell.js +232 -0
  102. package/lib-commonjs/components/views/GridView/GridSelector.js +94 -0
  103. package/lib-commonjs/components/views/GridView/MultiGridView.js +201 -0
  104. package/lib-commonjs/components/views/GridView/ResourceGrid.js +237 -0
  105. package/lib-commonjs/components/views/GridView/SharedContextControls.js +100 -0
  106. package/lib-commonjs/components/views/GridView/cells/BooleanCell.js +54 -0
  107. package/lib-commonjs/components/views/GridView/cells/DropdownCell.js +187 -0
  108. package/lib-commonjs/components/views/GridView/cells/StringCell.js +111 -0
  109. package/lib-commonjs/components/views/GridView/cells/TriStateCell.js +117 -0
  110. package/lib-commonjs/components/views/GridView/cells/index.js +18 -0
  111. package/lib-commonjs/components/views/GridView/index.js +217 -0
  112. package/lib-commonjs/components/views/ImportView/index.js +22 -19
  113. package/lib-commonjs/components/views/MessagesWindow/index.js +4 -1
  114. package/lib-commonjs/components/views/ResolutionView/index.js +8 -5
  115. package/lib-commonjs/contexts/ObservabilityContext.js +104 -0
  116. package/lib-commonjs/contexts/index.js +30 -0
  117. package/lib-commonjs/hooks/useResolutionState.js +849 -245
  118. package/lib-commonjs/hooks/useResourceData.js +184 -215
  119. package/lib-commonjs/index.js +15 -1
  120. package/lib-commonjs/namespaces/GridTools.js +161 -0
  121. package/lib-commonjs/namespaces/ObservabilityTools.js +33 -0
  122. package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
  123. package/lib-commonjs/namespaces/index.js +3 -1
  124. package/lib-commonjs/types/index.js +10 -0
  125. package/lib-commonjs/utils/cellValidation.js +253 -0
  126. package/lib-commonjs/utils/downloadHelper.js +198 -0
  127. package/lib-commonjs/utils/observability/factories.js +63 -0
  128. package/lib-commonjs/utils/observability/implementations.js +109 -0
  129. package/lib-commonjs/utils/observability/index.js +36 -0
  130. package/lib-commonjs/utils/observability/interfaces.js +24 -0
  131. package/lib-commonjs/utils/resolutionEditing.js +2 -1
  132. package/lib-commonjs/utils/resourceSelector.js +200 -0
  133. package/lib-commonjs/utils/resourceSelectors.js +242 -0
  134. package/lib-commonjs/utils/tsResIntegration.js +21 -16
  135. package/lib-commonjs/utils/zipLoader/zipProcessingHelpers.js +7 -5
  136. package/package.json +7 -7
  137. package/src/components/common/QualifierContextControl.tsx +0 -338
  138. package/src/components/common/ResolutionContextOptionsControl.tsx +0 -450
  139. package/src/components/common/ResolutionResults/index.tsx +0 -481
  140. package/src/components/common/ResourceListView.tsx +0 -167
  141. package/src/components/common/ResourcePickerOptionsControl.tsx +0 -351
  142. package/src/components/common/ResourceTreeView.tsx +0 -417
  143. package/src/components/common/SourceResourceDetail/index.tsx +0 -493
  144. package/src/components/forms/HierarchyEditor.tsx +0 -285
  145. package/src/components/forms/QualifierEditForm.tsx +0 -487
  146. package/src/components/forms/QualifierTypeEditForm.tsx +0 -458
  147. package/src/components/forms/ResourceTypeEditForm.tsx +0 -437
  148. package/src/components/forms/index.ts +0 -11
  149. package/src/components/orchestrator/ResourceOrchestrator.tsx +0 -444
  150. package/src/components/pickers/ResourcePicker/README.md +0 -570
  151. package/src/components/pickers/ResourcePicker/ResourceItem.tsx +0 -127
  152. package/src/components/pickers/ResourcePicker/ResourcePickerList.tsx +0 -114
  153. package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +0 -461
  154. package/src/components/pickers/ResourcePicker/index.tsx +0 -234
  155. package/src/components/pickers/ResourcePicker/types.ts +0 -301
  156. package/src/components/pickers/ResourcePicker/utils/treeNavigation.ts +0 -210
  157. package/src/components/views/CompiledView/index.tsx +0 -1342
  158. package/src/components/views/ConfigurationView/index.tsx +0 -848
  159. package/src/components/views/FilterView/index.tsx +0 -681
  160. package/src/components/views/ImportView/index.tsx +0 -789
  161. package/src/components/views/MessagesWindow/index.tsx +0 -325
  162. package/src/components/views/ResolutionView/EditableJsonView.tsx +0 -386
  163. package/src/components/views/ResolutionView/NewResourceModal.tsx +0 -158
  164. package/src/components/views/ResolutionView/UnifiedChangeControls.tsx +0 -163
  165. package/src/components/views/ResolutionView/index.tsx +0 -751
  166. package/src/components/views/SourceView/index.tsx +0 -291
  167. package/src/hooks/useConfigurationState.ts +0 -436
  168. package/src/hooks/useFilterState.ts +0 -150
  169. package/src/hooks/useResolutionState.ts +0 -893
  170. package/src/hooks/useResourceData.ts +0 -596
  171. package/src/hooks/useViewState.ts +0 -97
  172. package/src/index.ts +0 -68
  173. package/src/namespaces/ConfigurationTools.ts +0 -59
  174. package/src/namespaces/FilterTools.ts +0 -47
  175. package/src/namespaces/ImportTools.ts +0 -42
  176. package/src/namespaces/PickerTools.ts +0 -104
  177. package/src/namespaces/ResolutionTools.ts +0 -68
  178. package/src/namespaces/ResourceTools.ts +0 -106
  179. package/src/namespaces/TsResTools.ts +0 -49
  180. package/src/namespaces/ViewStateTools.ts +0 -91
  181. package/src/namespaces/ZipTools.ts +0 -49
  182. package/src/namespaces/index.ts +0 -49
  183. package/src/types/index.ts +0 -1273
  184. package/src/utils/configurationUtils.ts +0 -339
  185. package/src/utils/fileProcessing.ts +0 -164
  186. package/src/utils/filterResources.ts +0 -356
  187. package/src/utils/resolutionEditing.ts +0 -346
  188. package/src/utils/resolutionUtils.ts +0 -740
  189. package/src/utils/tsResIntegration.ts +0 -475
  190. package/src/utils/zipLoader/index.ts +0 -5
  191. package/src/utils/zipLoader/zipProcessingHelpers.ts +0 -46
  192. package/src/utils/zipLoader/zipUtils.ts +0 -7
@@ -1,417 +0,0 @@
1
- import React, { useState, useMemo, useCallback } from 'react';
2
- import {
3
- ChevronRightIcon,
4
- ChevronDownIcon,
5
- DocumentTextIcon,
6
- FolderIcon,
7
- FolderOpenIcon
8
- } from '@heroicons/react/24/outline';
9
- import { Resources, Runtime } from '@fgv/ts-res';
10
-
11
- /**
12
- * Props for the ResourceTreeView component.
13
- *
14
- * @public
15
- */
16
- interface ResourceTreeViewProps {
17
- /** Resource manager or compiled collection to display as a tree */
18
- resources: Resources.ResourceManagerBuilder | Runtime.CompiledResourceCollection;
19
- /** Currently selected resource ID for highlighting */
20
- selectedResourceId: string | null;
21
- /** Callback fired when a resource is selected */
22
- onResourceSelect: (resourceId: string) => void;
23
- /** Optional search term to filter and highlight resources */
24
- searchTerm?: string;
25
- /** Optional CSS classes to apply to the container */
26
- className?: string;
27
- }
28
-
29
- /**
30
- * Internal tree node structure for rendering.
31
- *
32
- * @internal
33
- */
34
- interface TreeNode {
35
- id: string;
36
- name: string;
37
- isLeaf: boolean;
38
- resource?: Resources.Resource | Runtime.IResource;
39
- children?: Map<string, TreeNode>;
40
- isExpanded?: boolean;
41
- isVisible?: boolean;
42
- matchesSearch?: boolean;
43
- }
44
-
45
- /**
46
- * A hierarchical tree view component for displaying and navigating resource structures.
47
- *
48
- * ResourceTreeView provides an interactive tree interface for browsing resource hierarchies,
49
- * with support for expansion/collapse, search filtering, selection highlighting, and intelligent
50
- * tree navigation. It automatically builds the tree structure from ts-res resource managers
51
- * and supports both builder and compiled resource collections.
52
- *
53
- * @example
54
- * ```tsx
55
- * import { ResourceTreeView } from '@fgv/ts-res-ui-components';
56
- *
57
- * function HierarchicalResourceBrowser() {
58
- * const [selectedId, setSelectedId] = useState<string | null>(null);
59
- * const [searchTerm, setSearchTerm] = useState('');
60
- *
61
- * return (
62
- * <div className="flex flex-col h-full">
63
- * <div className="p-4 border-b">
64
- * <input
65
- * type="search"
66
- * placeholder="Search in tree..."
67
- * value={searchTerm}
68
- * onChange={(e) => setSearchTerm(e.target.value)}
69
- * className="w-full px-3 py-2 border rounded-md"
70
- * />
71
- * </div>
72
- * <ResourceTreeView
73
- * resources={resourceManager}
74
- * selectedResourceId={selectedId}
75
- * onResourceSelect={setSelectedId}
76
- * searchTerm={searchTerm}
77
- * className="flex-1 min-h-0 p-2"
78
- * />
79
- * </div>
80
- * );
81
- * }
82
- * ```
83
- *
84
- * @example
85
- * ```tsx
86
- * // Using with orchestrator state for integrated navigation
87
- * import { ResourceTools } from '@fgv/ts-res-ui-components';
88
- *
89
- * function OrchestratorTreeView() {
90
- * const { state, actions } = ResourceTools.useResourceData();
91
- * const [searchTerm, setSearchTerm] = useState('');
92
- *
93
- * if (!state.resources) {
94
- * return <div className="p-4 text-gray-500">No resources loaded</div>;
95
- * }
96
- *
97
- * return (
98
- * <div className="h-full flex flex-col">
99
- * <div className="flex items-center gap-2 p-2 border-b">
100
- * <input
101
- * type="search"
102
- * placeholder="Search resources..."
103
- * value={searchTerm}
104
- * onChange={(e) => setSearchTerm(e.target.value)}
105
- * className="flex-1 px-3 py-1 text-sm border rounded"
106
- * />
107
- * <span className="text-xs text-gray-500">
108
- * {state.resources.summary?.resourceCount || 0} resources
109
- * </span>
110
- * </div>
111
- * <ResourceTreeView
112
- * resources={state.resources.resourceManager}
113
- * selectedResourceId={state.selectedResourceId}
114
- * onResourceSelect={(id) => actions.selectResource(id)}
115
- * searchTerm={searchTerm}
116
- * className="flex-1 min-h-0 overflow-auto"
117
- * />
118
- * </div>
119
- * );
120
- * }
121
- * ```
122
- *
123
- * @example
124
- * ```tsx
125
- * // Advanced usage with custom tree navigation
126
- * function AdvancedResourceTree({ resources, onResourceAction }) {
127
- * const [selectedId, setSelectedId] = useState<string | null>(null);
128
- * const [searchTerm, setSearchTerm] = useState('');
129
- *
130
- * const handleResourceSelect = (resourceId: string) => {
131
- * setSelectedId(resourceId);
132
- * onResourceAction('select', resourceId);
133
- * };
134
- *
135
- * const handleKeyDown = (event: React.KeyboardEvent) => {
136
- * if (event.key === 'Enter' && selectedId) {
137
- * onResourceAction('open', selectedId);
138
- * }
139
- * };
140
- *
141
- * return (
142
- * <div
143
- * className="resource-tree-container"
144
- * onKeyDown={handleKeyDown}
145
- * tabIndex={0}
146
- * >
147
- * <div className="tree-header">
148
- * <input
149
- * type="search"
150
- * placeholder="Find resources in tree..."
151
- * value={searchTerm}
152
- * onChange={(e) => setSearchTerm(e.target.value)}
153
- * />
154
- * <div className="tree-stats">
155
- * {resources && (
156
- * <span>
157
- * Tree: {resources.getBuiltResourceTree().isSuccess() ? 'Built' : 'Error'}
158
- * </span>
159
- * )}
160
- * </div>
161
- * </div>
162
- * <ResourceTreeView
163
- * resources={resources}
164
- * selectedResourceId={selectedId}
165
- * onResourceSelect={handleResourceSelect}
166
- * searchTerm={searchTerm}
167
- * className="tree-content"
168
- * />
169
- * </div>
170
- * );
171
- * }
172
- * ```
173
- *
174
- * @public
175
- */
176
- export const ResourceTreeView: React.FC<ResourceTreeViewProps> = ({
177
- resources,
178
- selectedResourceId,
179
- onResourceSelect,
180
- searchTerm = '',
181
- className = ''
182
- }) => {
183
- const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
184
-
185
- // Build the tree structure from resources
186
- const treeData = useMemo(() => {
187
- if (!resources) return null;
188
-
189
- // Get the tree from the resources
190
- const treeResult = resources.getBuiltResourceTree();
191
- if (treeResult.isFailure()) {
192
- console.error('Failed to build resource tree:', treeResult.message);
193
- return null;
194
- }
195
-
196
- return treeResult.value;
197
- }, [resources]);
198
-
199
- // Filter tree based on search term
200
- const filteredTree = useMemo(() => {
201
- if (!treeData || !searchTerm) return treeData;
202
-
203
- // Helper function to check if a node or its descendants match the search
204
- const markMatchingNodes = (
205
- node: Runtime.ResourceTree.IReadOnlyResourceTreeNode<any>,
206
- searchLower: string
207
- ): boolean => {
208
- const nodeIdLower = node.id.toLowerCase();
209
- let matches = nodeIdLower.includes(searchLower);
210
-
211
- if (!node.isLeaf && node.children) {
212
- // Check children recursively
213
- for (const child of node.children.values()) {
214
- if (markMatchingNodes(child, searchLower)) {
215
- matches = true;
216
- }
217
- }
218
- }
219
-
220
- return matches;
221
- };
222
-
223
- // Mark all matching nodes
224
- const searchLower = searchTerm.toLowerCase();
225
- for (const child of treeData.children.values()) {
226
- markMatchingNodes(child, searchLower);
227
- }
228
-
229
- return treeData;
230
- }, [treeData, searchTerm]);
231
-
232
- // Toggle node expansion
233
- const toggleNode = useCallback((nodeId: string) => {
234
- setExpandedNodes((prev) => {
235
- const newExpanded = new Set(prev);
236
- if (newExpanded.has(nodeId)) {
237
- newExpanded.delete(nodeId);
238
- } else {
239
- newExpanded.add(nodeId);
240
- }
241
- return newExpanded;
242
- });
243
- }, []);
244
-
245
- // Expand all nodes that contain search matches
246
- const expandMatchingNodes = useCallback(() => {
247
- if (!searchTerm || !filteredTree) return;
248
-
249
- const searchLower = searchTerm.toLowerCase();
250
- const nodesToExpand = new Set<string>();
251
-
252
- const checkNode = (node: Runtime.ResourceTree.IReadOnlyResourceTreeNode<any>) => {
253
- if (node.id.toLowerCase().includes(searchLower)) {
254
- // Expand all parent nodes
255
- const parts = node.id.split('.');
256
- for (let i = 1; i < parts.length; i++) {
257
- nodesToExpand.add(parts.slice(0, i).join('.'));
258
- }
259
- }
260
-
261
- if (!node.isLeaf && node.children) {
262
- for (const child of node.children.values()) {
263
- checkNode(child);
264
- }
265
- }
266
- };
267
-
268
- for (const child of filteredTree.children.values()) {
269
- checkNode(child);
270
- }
271
-
272
- setExpandedNodes(nodesToExpand);
273
- }, [searchTerm, filteredTree]);
274
-
275
- // Auto-expand when search term changes
276
- React.useEffect(() => {
277
- if (searchTerm) {
278
- expandMatchingNodes();
279
- }
280
- }, [searchTerm, expandMatchingNodes]);
281
-
282
- // Render a single tree node
283
- const renderTreeNode = (
284
- node: Runtime.ResourceTree.IReadOnlyResourceTreeNode<Resources.Resource | Runtime.IResource>,
285
- level: number = 0
286
- ): React.ReactElement | null => {
287
- const isExpanded = expandedNodes.has(node.id);
288
- const isSelected = selectedResourceId === node.id;
289
- const nodeIdLower = node.id.toLowerCase();
290
- const searchLower = searchTerm.toLowerCase();
291
- const matchesSearch = !searchTerm || nodeIdLower.includes(searchLower);
292
-
293
- // Check if any children match
294
- let hasMatchingChildren = false;
295
- if (!node.isLeaf && node.children && searchTerm) {
296
- const checkChildren = (n: Runtime.ResourceTree.IReadOnlyResourceTreeNode<any>): boolean => {
297
- if (n.id.toLowerCase().includes(searchLower)) return true;
298
- if (!n.isLeaf && n.children) {
299
- for (const child of n.children.values()) {
300
- if (checkChildren(child)) return true;
301
- }
302
- }
303
- return false;
304
- };
305
-
306
- for (const child of node.children.values()) {
307
- if (checkChildren(child)) {
308
- hasMatchingChildren = true;
309
- break;
310
- }
311
- }
312
- }
313
-
314
- // Hide nodes that don't match search and don't have matching children
315
- if (searchTerm && !matchesSearch && !hasMatchingChildren) {
316
- return null;
317
- }
318
-
319
- return (
320
- <div key={node.id}>
321
- <div
322
- className={`flex items-center px-2 py-1 cursor-pointer hover:bg-gray-100 ${
323
- isSelected ? 'bg-purple-50 border-l-2 border-purple-500' : ''
324
- } ${matchesSearch && searchTerm ? 'bg-yellow-50' : ''}`}
325
- style={{ paddingLeft: `${level * 20 + 8}px` }}
326
- onClick={() => {
327
- if (node.isLeaf) {
328
- onResourceSelect(node.id);
329
- } else {
330
- toggleNode(node.id);
331
- }
332
- }}
333
- >
334
- {/* Expand/Collapse icon for branches */}
335
- {!node.isLeaf && (
336
- <button
337
- onClick={(e) => {
338
- e.stopPropagation();
339
- toggleNode(node.id);
340
- }}
341
- className="mr-1 p-0.5 hover:bg-gray-200 rounded"
342
- >
343
- {isExpanded ? (
344
- <ChevronDownIcon className="w-3 h-3 text-gray-600" />
345
- ) : (
346
- <ChevronRightIcon className="w-3 h-3 text-gray-600" />
347
- )}
348
- </button>
349
- )}
350
-
351
- {/* Folder or document icon */}
352
- {node.isLeaf ? (
353
- <DocumentTextIcon className="w-4 h-4 text-green-500 mr-2 flex-shrink-0" />
354
- ) : isExpanded ? (
355
- <FolderOpenIcon className="w-4 h-4 text-blue-500 mr-2 flex-shrink-0" />
356
- ) : (
357
- <FolderIcon className="w-4 h-4 text-blue-500 mr-2 flex-shrink-0" />
358
- )}
359
-
360
- {/* Node name */}
361
- <span
362
- className={`text-sm truncate ${isSelected ? 'font-medium text-purple-900' : 'text-gray-700'} ${
363
- matchesSearch && searchTerm ? 'font-medium' : ''
364
- }`}
365
- title={node.id}
366
- >
367
- {node.name}
368
- </span>
369
-
370
- {/* Show child count for branches */}
371
- {!node.isLeaf && node.children && (
372
- <span className="ml-2 text-xs text-gray-500">({node.children.size})</span>
373
- )}
374
- </div>
375
-
376
- {/* Render children if expanded */}
377
- {!node.isLeaf && node.children && isExpanded && (
378
- <div>
379
- {Array.from(node.children.values())
380
- .sort((a, b) => {
381
- // Sort folders first, then by name
382
- if (a.isLeaf !== b.isLeaf) {
383
- return a.isLeaf ? 1 : -1;
384
- }
385
- return a.name.localeCompare(b.name);
386
- })
387
- .map((child) => renderTreeNode(child, level + 1))}
388
- </div>
389
- )}
390
- </div>
391
- );
392
- };
393
-
394
- if (!filteredTree) {
395
- return (
396
- <div className={`${className} p-4 text-center text-gray-500`}>
397
- <p>No resources available</p>
398
- </div>
399
- );
400
- }
401
-
402
- return (
403
- <div className={`${className} overflow-y-auto`}>
404
- {Array.from(filteredTree.children.values())
405
- .sort((a, b) => {
406
- // Sort folders first, then by name
407
- if (a.isLeaf !== b.isLeaf) {
408
- return a.isLeaf ? 1 : -1;
409
- }
410
- return a.name.localeCompare(b.name);
411
- })
412
- .map((child) => renderTreeNode(child))}
413
- </div>
414
- );
415
- };
416
-
417
- export default ResourceTreeView;