@anymux/ui-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
- package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
- package/dist/FileBrowserContext-B6jixa2j.js +11 -0
- package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
- package/dist/calendar-DSlrbHoj.js +761 -0
- package/dist/calendar-DSlrbHoj.js.map +1 -0
- package/dist/calendar.d.ts +3 -0
- package/dist/calendar.js +3 -0
- package/dist/contacts-DQXTZzHc.js +539 -0
- package/dist/contacts-DQXTZzHc.js.map +1 -0
- package/dist/contacts.d.ts +3 -0
- package/dist/contacts.js +3 -0
- package/dist/file-browser-m5atC3kF.js +6755 -0
- package/dist/file-browser-m5atC3kF.js.map +1 -0
- package/dist/file-browser.d.ts +11 -0
- package/dist/file-browser.js +9 -0
- package/dist/git-B55e6LL-.js +561 -0
- package/dist/git-B55e6LL-.js.map +1 -0
- package/dist/git.d.ts +2 -0
- package/dist/git.js +3 -0
- package/dist/iconMap-V4B8P-Uh.js +206 -0
- package/dist/iconMap-V4B8P-Uh.js.map +1 -0
- package/dist/icons-CIsIOZXR.js +0 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index-BNmNIWBL.d.ts +71 -0
- package/dist/index-BNmNIWBL.d.ts.map +1 -0
- package/dist/index-Bryv_GCG.d.ts +1481 -0
- package/dist/index-Bryv_GCG.d.ts.map +1 -0
- package/dist/index-CuQIjSXs.d.ts +134 -0
- package/dist/index-CuQIjSXs.d.ts.map +1 -0
- package/dist/index-DSu19mq0.d.ts +153 -0
- package/dist/index-DSu19mq0.d.ts.map +1 -0
- package/dist/index-DmsyeHFr.d.ts +149 -0
- package/dist/index-DmsyeHFr.d.ts.map +1 -0
- package/dist/index-DxnJ8FYM.d.ts +17 -0
- package/dist/index-DxnJ8FYM.d.ts.map +1 -0
- package/dist/index-DzfY1Tok.d.ts +32 -0
- package/dist/index-DzfY1Tok.d.ts.map +1 -0
- package/dist/index-Ml_SgiKa.d.ts +1847 -0
- package/dist/index-Ml_SgiKa.d.ts.map +1 -0
- package/dist/index-kHr9udZD.d.ts +1025 -0
- package/dist/index-kHr9udZD.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/layout-Ca_4r8ka.js +89 -0
- package/dist/layout-Ca_4r8ka.js.map +1 -0
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +5 -0
- package/dist/list-CxfT6hix.js +6831 -0
- package/dist/list-CxfT6hix.js.map +1 -0
- package/dist/list.d.ts +2 -0
- package/dist/list.js +5 -0
- package/dist/media-DZ292aKK.js +557 -0
- package/dist/media-DZ292aKK.js.map +1 -0
- package/dist/media.d.ts +3 -0
- package/dist/media.js +3 -0
- package/dist/tree-Dd9Z0Aso.js +3351 -0
- package/dist/tree-Dd9Z0Aso.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +6 -0
- package/dist/types-common-CB3kRek8.d.ts +26 -0
- package/dist/types-common-CB3kRek8.d.ts.map +1 -0
- package/dist/utils-B4fdKKsy.js +3 -0
- package/package.json +109 -0
- package/src/calendar/AgendaView.tsx +37 -0
- package/src/calendar/CalendarBrowser.tsx +90 -0
- package/src/calendar/CalendarModel.ts +142 -0
- package/src/calendar/CalendarSidebar.tsx +81 -0
- package/src/calendar/DayView.tsx +76 -0
- package/src/calendar/EventCard.tsx +51 -0
- package/src/calendar/MockCalendarProvider.ts +98 -0
- package/src/calendar/MonthView.tsx +77 -0
- package/src/calendar/WeekView.tsx +129 -0
- package/src/calendar/index.ts +18 -0
- package/src/calendar/types.ts +25 -0
- package/src/contacts/ContactAvatar.tsx +35 -0
- package/src/contacts/ContactBrowser.tsx +56 -0
- package/src/contacts/ContactCard.tsx +37 -0
- package/src/contacts/ContactDetail.tsx +63 -0
- package/src/contacts/ContactGroupSidebar.tsx +40 -0
- package/src/contacts/ContactList.tsx +32 -0
- package/src/contacts/ContactListModel.ts +120 -0
- package/src/contacts/MockContactProvider.ts +77 -0
- package/src/contacts/index.ts +17 -0
- package/src/contacts/types.ts +26 -0
- package/src/demos/CalendarBrowserDemo.tsx +15 -0
- package/src/demos/ContactBrowserDemo.tsx +15 -0
- package/src/demos/MediaBrowserDemo.tsx +15 -0
- package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
- package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
- package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
- package/src/file-browser/adapters/README.md +504 -0
- package/src/file-browser/adapters/index.ts +27 -0
- package/src/file-browser/adapters/types.ts +70 -0
- package/src/file-browser/architecture.md +645 -0
- package/src/file-browser/components/CreateItemDialog.tsx +71 -0
- package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
- package/src/file-browser/components/FileBrowser.tsx +473 -0
- package/src/file-browser/components/FileBrowserContent.tsx +209 -0
- package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
- package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
- package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
- package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
- package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
- package/src/file-browser/components/PreviewPane.tsx +146 -0
- package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
- package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
- package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
- package/src/file-browser/components/UploadProgress.tsx +123 -0
- package/src/file-browser/components/ViewerHost.tsx +208 -0
- package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
- package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
- package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
- package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
- package/src/file-browser/components/shared/FileIcon.tsx +169 -0
- package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
- package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
- package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
- package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
- package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
- package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
- package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
- package/src/file-browser/context/FileBrowserContext.tsx +13 -0
- package/src/file-browser/examples/BasicUsage.tsx +20 -0
- package/src/file-browser/index.ts +98 -0
- package/src/file-browser/models/FileBrowserModel.ts +623 -0
- package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
- package/src/file-browser/models/NavigationManagerModel.ts +312 -0
- package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
- package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
- package/src/file-browser/models/SelectionManagerModel.ts +252 -0
- package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
- package/src/file-browser/models/UploadModel.ts +147 -0
- package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
- package/src/file-browser/models/ViewerHostModel.ts +44 -0
- package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
- package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
- package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
- package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
- package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
- package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
- package/src/file-browser/providers/FileSystemProvider.ts +401 -0
- package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
- package/src/file-browser/providers/GitProvider.ts +337 -0
- package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
- package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
- package/src/file-browser/providers/MemoryProvider.ts +303 -0
- package/src/file-browser/providers/index.ts +4 -0
- package/src/file-browser/registry/ViewerRegistry.ts +551 -0
- package/src/file-browser/registry/types.ts +144 -0
- package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
- package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
- package/src/file-browser/tasks.md +537 -0
- package/src/file-browser/types/FileBrowserTypes.ts +126 -0
- package/src/file-browser/types/ProviderTypes.ts +155 -0
- package/src/file-browser/types/UITypes.ts +235 -0
- package/src/file-browser/types/ViewModeTypes.ts +150 -0
- package/src/file-browser/utils/gestures.ts +327 -0
- package/src/file-browser/utils/performance.ts +563 -0
- package/src/file-browser/viewers/ImageViewer.tsx +163 -0
- package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
- package/src/file-browser/viewers/TextViewer.tsx +95 -0
- package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
- package/src/file-browser/viewers/index.ts +61 -0
- package/src/git/BranchList.tsx +128 -0
- package/src/git/CommitGraph.tsx +239 -0
- package/src/git/CommitList.tsx +258 -0
- package/src/git/DiffViewer.tsx +219 -0
- package/src/git/index.ts +4 -0
- package/src/icons/iconMap.ts +146 -0
- package/src/icons/index.ts +9 -0
- package/src/index.ts +13 -0
- package/src/layout/README.md +307 -0
- package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
- package/src/layout/examples/SimpleExample.tsx +60 -0
- package/src/layout/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/list/README.md +303 -0
- package/src/list/architecture.md +807 -0
- package/src/list/components/CalculatedGridView.tsx +252 -0
- package/src/list/components/DragPreview.tsx +102 -0
- package/src/list/components/ListContextMenu.tsx +274 -0
- package/src/list/components/ListItem.tsx +761 -0
- package/src/list/components/ListItems.tsx +919 -0
- package/src/list/components/MasonryView.tsx +241 -0
- package/src/list/components/SearchFilter.tsx +44 -0
- package/src/list/components/TreemapView.tsx +709 -0
- package/src/list/components/ViewSizeControls.tsx +205 -0
- package/src/list/components/ViewTypeSelector.tsx +312 -0
- package/src/list/components/VirtualizedDetailsView.tsx +231 -0
- package/src/list/components/VirtualizedGrid.tsx +164 -0
- package/src/list/components/VirtualizedList.tsx +154 -0
- package/src/list/components/VirtualizedMasonryView.tsx +344 -0
- package/src/list/components/shared/EmptyState.tsx +103 -0
- package/src/list/components/shared/ErrorBoundary.tsx +123 -0
- package/src/list/components/shared/ErrorDisplay.tsx +100 -0
- package/src/list/components/shared/ListLoader.tsx +146 -0
- package/src/list/components/shared/LoadingIndicator.tsx +80 -0
- package/src/list/index.ts +92 -0
- package/src/list/models/ListItemsModel.ts +1301 -0
- package/src/list/models/TreemapModel.ts +204 -0
- package/src/list/providers/ListItemsProvider.ts +313 -0
- package/src/list/providers/TestListProvider.ts +604 -0
- package/src/list/tasks.md +937 -0
- package/src/list/types/ListTypes.ts +178 -0
- package/src/list/utils/BenchmarkLogger.ts +243 -0
- package/src/list/utils/DragDropManager.ts +320 -0
- package/src/list/utils/GridLayoutCalculator.ts +290 -0
- package/src/list/utils/ListAccessibility.ts +367 -0
- package/src/list/utils/ListKeyboard.ts +414 -0
- package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
- package/src/list/utils/MasonryLayoutEngine.ts +401 -0
- package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
- package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
- package/src/media/AlbumSidebar.tsx +48 -0
- package/src/media/MediaBrowser.tsx +92 -0
- package/src/media/MediaBrowserModel.ts +138 -0
- package/src/media/MediaGrid.tsx +50 -0
- package/src/media/MediaList.tsx +49 -0
- package/src/media/MediaPreview.tsx +63 -0
- package/src/media/MediaTimeline.tsx +38 -0
- package/src/media/MockMediaProvider.ts +70 -0
- package/src/media/index.ts +18 -0
- package/src/media/types.ts +21 -0
- package/src/styles/variables.css +60 -0
- package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
- package/src/tree/__tests__/TreeModel.test.ts +16 -0
- package/src/tree/architecture.md +530 -0
- package/src/tree/components/Tree.tsx +283 -0
- package/src/tree/components/TreeCheckbox.tsx +147 -0
- package/src/tree/components/TreeContextMenu.tsx +139 -0
- package/src/tree/components/TreeNodeList.tsx +329 -0
- package/src/tree/components/TreeTable.tsx +382 -0
- package/src/tree/index.ts +58 -0
- package/src/tree/models/TreeModel.ts +839 -0
- package/src/tree/providers/SimpleTreeProvider.ts +463 -0
- package/src/tree/providers/TestTreeProvider.ts +946 -0
- package/src/tree/providers/TreeProvider.ts +308 -0
- package/src/tree/tasks.md +2046 -0
- package/src/tree/types/TreeTypes.ts +279 -0
- package/src/tree/utils/SelectionTheme.ts +150 -0
- package/src/tree/utils/logger.ts +203 -0
- package/src/types-common.ts +21 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeNodeList - Recursive component for rendering tree node lists
|
|
3
|
+
*
|
|
4
|
+
* This component handles the recursive rendering of tree nodes and their children,
|
|
5
|
+
* providing proper indentation and hierarchy visualization.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
9
|
+
import { observer } from 'mobx-react-lite';
|
|
10
|
+
import { ChevronRight, ChevronDown, Loader2, MoreVertical } from 'lucide-react';
|
|
11
|
+
import { cn } from '../../lib/utils';
|
|
12
|
+
import { resolveIcon } from '../../icons/iconMap';
|
|
13
|
+
import { TreeCheckbox } from './TreeCheckbox';
|
|
14
|
+
import { getSelectionClasses, getCustomColorVariables, DEFAULT_SELECTION_THEME } from '../utils/SelectionTheme';
|
|
15
|
+
import type { TreeNodeData } from '../types/TreeTypes';
|
|
16
|
+
import type { TreeModel } from '../models/TreeModel';
|
|
17
|
+
import { logger } from '../utils/logger';
|
|
18
|
+
import { useFileBrowserContext } from '../../file-browser/context/FileBrowserContext';
|
|
19
|
+
|
|
20
|
+
// Inline rename input for tree nodes
|
|
21
|
+
const TreeInlineRename: React.FC<{
|
|
22
|
+
currentName: string;
|
|
23
|
+
onCommit: (newName: string) => void;
|
|
24
|
+
onCancel: () => void;
|
|
25
|
+
}> = ({ currentName, onCommit, onCancel }) => {
|
|
26
|
+
const [value, setValue] = useState(currentName);
|
|
27
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
28
|
+
const mountTimeRef = useRef(Date.now());
|
|
29
|
+
const committedRef = useRef(false);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
mountTimeRef.current = Date.now();
|
|
33
|
+
const raf = requestAnimationFrame(() => {
|
|
34
|
+
const input = inputRef.current;
|
|
35
|
+
if (input) {
|
|
36
|
+
input.focus();
|
|
37
|
+
const dotIndex = currentName.lastIndexOf('.');
|
|
38
|
+
input.setSelectionRange(0, dotIndex > 0 ? dotIndex : currentName.length);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return () => cancelAnimationFrame(raf);
|
|
42
|
+
}, [currentName]);
|
|
43
|
+
|
|
44
|
+
const commit = () => {
|
|
45
|
+
if (committedRef.current) return;
|
|
46
|
+
committedRef.current = true;
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
if (trimmed && trimmed !== currentName) {
|
|
49
|
+
onCommit(trimmed);
|
|
50
|
+
} else {
|
|
51
|
+
onCancel();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleBlur = () => {
|
|
56
|
+
if (Date.now() - mountTimeRef.current < 200) return;
|
|
57
|
+
commit();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<input
|
|
62
|
+
ref={inputRef}
|
|
63
|
+
value={value}
|
|
64
|
+
onChange={(e) => setValue(e.target.value)}
|
|
65
|
+
onKeyDown={(e) => {
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
if (e.key === 'Enter') { e.preventDefault(); commit(); }
|
|
68
|
+
if (e.key === 'Escape') { e.preventDefault(); onCancel(); }
|
|
69
|
+
}}
|
|
70
|
+
onBlur={handleBlur}
|
|
71
|
+
onClick={(e) => e.stopPropagation()}
|
|
72
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
73
|
+
className="flex-1 bg-background border border-primary rounded px-1 py-0 text-[13px] outline-none min-w-0"
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export interface TreeNodeListProps {
|
|
79
|
+
/** Tree nodes to render */
|
|
80
|
+
nodes: TreeNodeData[];
|
|
81
|
+
/** Tree model instance */
|
|
82
|
+
treeModel: TreeModel;
|
|
83
|
+
/** Current depth level (for indentation) */
|
|
84
|
+
depth?: number;
|
|
85
|
+
/** Additional CSS classes */
|
|
86
|
+
className?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* TreeNodeList component renders a list of tree nodes with proper nesting and interaction
|
|
91
|
+
*/
|
|
92
|
+
export const TreeNodeList = observer<TreeNodeListProps>(({
|
|
93
|
+
nodes,
|
|
94
|
+
treeModel,
|
|
95
|
+
depth = 0,
|
|
96
|
+
className
|
|
97
|
+
}) => {
|
|
98
|
+
const fileBrowserCtx = useFileBrowserContext();
|
|
99
|
+
|
|
100
|
+
const renderNode = (node: TreeNodeData) => {
|
|
101
|
+
const isSelected = treeModel.isNodeSelected(node.id);
|
|
102
|
+
const isExpanded = treeModel.isNodeExpanded(node.id);
|
|
103
|
+
const isFocused = treeModel.focusedNode === node.id;
|
|
104
|
+
const isLoading = treeModel.isNodeLoading(node.id);
|
|
105
|
+
const isDirectory = node.type === 'directory';
|
|
106
|
+
const hasChildren = node.hasChildren || (node.children && node.children.length > 0);
|
|
107
|
+
const showCheckbox = treeModel.provider.useCheckboxSelection;
|
|
108
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
109
|
+
const isRenaming = fileBrowserCtx?.renameState?.itemId === node.id && fileBrowserCtx?.renameState?.source === 'tree';
|
|
110
|
+
|
|
111
|
+
// Get selection theme
|
|
112
|
+
const selectionTheme = treeModel.provider.getSelectionTheme?.() || DEFAULT_SELECTION_THEME;
|
|
113
|
+
|
|
114
|
+
const nodeClasses = cn(
|
|
115
|
+
// Base styles
|
|
116
|
+
'group flex items-center gap-2 py-1 px-2 text-[13px] cursor-default select-none transition-colors duration-150',
|
|
117
|
+
'focus:outline-none',
|
|
118
|
+
|
|
119
|
+
// Selection styling - must come before hover so selected state is visible
|
|
120
|
+
isSelected
|
|
121
|
+
? 'bg-accent text-accent-foreground'
|
|
122
|
+
: 'hover:bg-muted/50',
|
|
123
|
+
|
|
124
|
+
// Focus styles
|
|
125
|
+
isFocused && !isSelected && 'ring-1 ring-primary/50',
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Handle node click
|
|
129
|
+
const handleNodeClick = (event: React.MouseEvent) => {
|
|
130
|
+
event.stopPropagation();
|
|
131
|
+
logger.interaction('TreeNode clicked', node.id, { name: node.name });
|
|
132
|
+
|
|
133
|
+
if (event.ctrlKey || event.metaKey) {
|
|
134
|
+
// Multi-select with Ctrl/Cmd
|
|
135
|
+
if (treeModel.provider.isMultiSelectEnabled) {
|
|
136
|
+
if (isSelected) {
|
|
137
|
+
treeModel.deselectNode(node);
|
|
138
|
+
} else {
|
|
139
|
+
treeModel.selectNode(node);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// Single select
|
|
144
|
+
if (!isSelected) {
|
|
145
|
+
treeModel.selectNode(node);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Handle expand/collapse click
|
|
151
|
+
const handleExpandClick = (event: React.MouseEvent) => {
|
|
152
|
+
event.stopPropagation();
|
|
153
|
+
logger.interaction('TreeNode expand/collapse clicked', node.id, { name: node.name });
|
|
154
|
+
|
|
155
|
+
if (hasChildren) {
|
|
156
|
+
treeModel.toggleExpansion(node);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Handle context menu (right-click)
|
|
161
|
+
const handleContextMenu = (event: React.MouseEvent) => {
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
event.stopPropagation();
|
|
164
|
+
logger.interaction('TreeNode context menu', node.id, { name: node.name });
|
|
165
|
+
|
|
166
|
+
// Don't change selection on right-click, but ensure we show menu for this node
|
|
167
|
+
if (!isSelected && treeModel.selectedNodesArray.length <= 1) {
|
|
168
|
+
treeModel.selectNode(node);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Show context menu at cursor position
|
|
172
|
+
const rect = { x: event.clientX, y: event.clientY };
|
|
173
|
+
|
|
174
|
+
// Check if this node is part of a multi-selection
|
|
175
|
+
const selectedNodes = treeModel.selectedNodesArray;
|
|
176
|
+
if (selectedNodes.length > 1 && isSelected) {
|
|
177
|
+
treeModel.showMultiNodeContextMenu(selectedNodes, event.nativeEvent);
|
|
178
|
+
} else {
|
|
179
|
+
treeModel.showContextMenu(node, event.nativeEvent);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Handle menu button click - shows context menu without changing selection
|
|
184
|
+
const handleMenuButtonClick = (event: React.MouseEvent) => {
|
|
185
|
+
event.stopPropagation();
|
|
186
|
+
logger.interaction('TreeNode menu button clicked', node.id, { name: node.name });
|
|
187
|
+
|
|
188
|
+
// Show context menu positioned near the button
|
|
189
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
190
|
+
const fakeEvent = new MouseEvent('contextmenu', {
|
|
191
|
+
clientX: rect.right - 10,
|
|
192
|
+
clientY: rect.bottom,
|
|
193
|
+
bubbles: true,
|
|
194
|
+
cancelable: true
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// If this node is part of a multi-selection, show multi-menu
|
|
198
|
+
const selectedNodes = treeModel.selectedNodesArray;
|
|
199
|
+
if (selectedNodes.length > 1 && isSelected) {
|
|
200
|
+
treeModel.showMultiNodeContextMenu(selectedNodes, fakeEvent);
|
|
201
|
+
} else {
|
|
202
|
+
// Show menu for this specific node without changing selection
|
|
203
|
+
treeModel.showContextMenu(node, fakeEvent);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const iconSize = 'w-4 h-4';
|
|
208
|
+
|
|
209
|
+
// Get custom icon from provider
|
|
210
|
+
const renderIcon = () => {
|
|
211
|
+
// Try to get custom icon from provider first
|
|
212
|
+
const customIcon = treeModel.provider.getNodeIcon?.(node);
|
|
213
|
+
if (customIcon) {
|
|
214
|
+
if (typeof customIcon === 'string') {
|
|
215
|
+
return resolveIcon(customIcon, iconSize);
|
|
216
|
+
} else {
|
|
217
|
+
const IconComponent = customIcon;
|
|
218
|
+
return <IconComponent className={iconSize} />;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Default icons via shared icon map
|
|
223
|
+
if (isDirectory) {
|
|
224
|
+
return resolveIcon(isExpanded ? 'folder-open' : 'folder', iconSize);
|
|
225
|
+
} else {
|
|
226
|
+
return resolveIcon('file', iconSize);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div key={node.id} className="w-full">
|
|
232
|
+
{/* Node */}
|
|
233
|
+
<div
|
|
234
|
+
className={nodeClasses}
|
|
235
|
+
style={{ paddingLeft: `${depth * 20 + 8}px` }}
|
|
236
|
+
onClick={handleNodeClick}
|
|
237
|
+
onContextMenu={handleContextMenu}
|
|
238
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
239
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
240
|
+
tabIndex={isFocused ? 0 : -1}
|
|
241
|
+
role="treeitem"
|
|
242
|
+
aria-expanded={hasChildren ? isExpanded : undefined}
|
|
243
|
+
aria-selected={isSelected}
|
|
244
|
+
aria-level={depth + 1}
|
|
245
|
+
aria-label={`${node.type === 'directory' ? 'Folder' : 'File'}: ${node.name}`}
|
|
246
|
+
>
|
|
247
|
+
{/* Expand/Collapse Button */}
|
|
248
|
+
<div className="flex-shrink-0 w-4 h-4">
|
|
249
|
+
{hasChildren && (
|
|
250
|
+
<button
|
|
251
|
+
className="w-4 h-4 flex items-center justify-center hover:bg-muted rounded-sm transition-colors"
|
|
252
|
+
onClick={handleExpandClick}
|
|
253
|
+
aria-label={isExpanded ? 'Collapse' : 'Expand'}
|
|
254
|
+
>
|
|
255
|
+
{isLoading ? (
|
|
256
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
257
|
+
) : isExpanded ? (
|
|
258
|
+
<ChevronDown className="w-3 h-3" />
|
|
259
|
+
) : (
|
|
260
|
+
<ChevronRight className="w-3 h-3" />
|
|
261
|
+
)}
|
|
262
|
+
</button>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
{/* Checkbox (if enabled) */}
|
|
267
|
+
{showCheckbox && (
|
|
268
|
+
<TreeCheckbox
|
|
269
|
+
state={treeModel.getCheckboxState(node.id)}
|
|
270
|
+
onChange={(newState) => {
|
|
271
|
+
logger.interaction('TreeCheckbox changed', node.id, { name: node.name, newState });
|
|
272
|
+
treeModel.handleCheckboxChange(node, newState);
|
|
273
|
+
}}
|
|
274
|
+
nodeId={node.id}
|
|
275
|
+
/>
|
|
276
|
+
)}
|
|
277
|
+
|
|
278
|
+
{/* Icon */}
|
|
279
|
+
<div className="flex-shrink-0 w-4 h-4">
|
|
280
|
+
{renderIcon()}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Node Name */}
|
|
284
|
+
{isRenaming && fileBrowserCtx ? (
|
|
285
|
+
<TreeInlineRename
|
|
286
|
+
currentName={fileBrowserCtx.renameState!.currentName}
|
|
287
|
+
onCommit={fileBrowserCtx.onRenameCommit}
|
|
288
|
+
onCancel={fileBrowserCtx.onRenameCancel}
|
|
289
|
+
/>
|
|
290
|
+
) : (
|
|
291
|
+
<span className="flex-1 truncate">
|
|
292
|
+
{node.name}
|
|
293
|
+
</span>
|
|
294
|
+
)}
|
|
295
|
+
|
|
296
|
+
{/* Menu Button - positioned on the right */}
|
|
297
|
+
<button
|
|
298
|
+
className={cn(
|
|
299
|
+
'flex-shrink-0 w-6 h-6 flex items-center justify-center rounded hover:bg-muted/70 transition-colors cursor-pointer',
|
|
300
|
+
// Show on hover or when node is selected
|
|
301
|
+
'opacity-0 group-hover:opacity-100',
|
|
302
|
+
(isSelected || isHovered) && 'opacity-100'
|
|
303
|
+
)}
|
|
304
|
+
onClick={handleMenuButtonClick}
|
|
305
|
+
aria-label="Show context menu"
|
|
306
|
+
>
|
|
307
|
+
<MoreVertical className="w-4 h-4" />
|
|
308
|
+
</button>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Children */}
|
|
312
|
+
{isExpanded && hasChildren && node.children && (
|
|
313
|
+
<TreeNodeList
|
|
314
|
+
nodes={node.children}
|
|
315
|
+
treeModel={treeModel}
|
|
316
|
+
depth={depth + 1}
|
|
317
|
+
className={className}
|
|
318
|
+
/>
|
|
319
|
+
)}
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<div className={cn('w-full', className)}>
|
|
326
|
+
{nodes.map(renderNode)}
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
});
|