@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,209 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { AlertCircle, FolderOpen } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import { FileBrowserModel } from '../models/FileBrowserModel';
|
|
6
|
+
import { LoadingSpinner } from '@anymux/ui/components/loading-spinner';
|
|
7
|
+
import { ErrorBoundary } from './shared/ErrorBoundary';
|
|
8
|
+
|
|
9
|
+
export interface FileBrowserContentProps {
|
|
10
|
+
model: FileBrowserModel;
|
|
11
|
+
className?: string;
|
|
12
|
+
currentViewMode?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const FileBrowserContent: React.FC<FileBrowserContentProps> = observer(({
|
|
16
|
+
model,
|
|
17
|
+
className,
|
|
18
|
+
currentViewMode = 'tree',
|
|
19
|
+
}) => {
|
|
20
|
+
// Loading state
|
|
21
|
+
if (model.isLoading) {
|
|
22
|
+
return (
|
|
23
|
+
<div className={cn('flex items-center justify-center p-8', className)}>
|
|
24
|
+
<LoadingSpinner
|
|
25
|
+
size="lg"
|
|
26
|
+
label="Loading files..."
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Error state
|
|
33
|
+
if (model.hasError && model.error) {
|
|
34
|
+
return (
|
|
35
|
+
<div className={cn('p-8', className)}>
|
|
36
|
+
<div
|
|
37
|
+
className="flex flex-col items-center justify-center text-center border border-destructive/20 rounded-lg bg-destructive/5 p-8"
|
|
38
|
+
role="alert"
|
|
39
|
+
aria-live="assertive"
|
|
40
|
+
>
|
|
41
|
+
<AlertCircle className="w-12 h-12 text-destructive mb-4" />
|
|
42
|
+
|
|
43
|
+
<h3 className="text-lg font-semibold text-destructive mb-2">
|
|
44
|
+
Error Loading Files
|
|
45
|
+
</h3>
|
|
46
|
+
|
|
47
|
+
<p className="text-sm text-muted-foreground mb-4 max-w-md">
|
|
48
|
+
{model.error}
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<div className="flex gap-2">
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => model.refreshItems()}
|
|
54
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 transition-colors"
|
|
55
|
+
>
|
|
56
|
+
Try Again
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
<button
|
|
60
|
+
onClick={() => model.clearError()}
|
|
61
|
+
className="px-4 py-2 bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/90 focus:outline-none focus:ring-2 focus:ring-secondary focus:ring-offset-2 transition-colors"
|
|
62
|
+
>
|
|
63
|
+
Dismiss
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Empty state
|
|
72
|
+
if (!model.hasItems) {
|
|
73
|
+
return (
|
|
74
|
+
<div className={cn('flex items-center justify-center p-8', className)}>
|
|
75
|
+
<div className="text-center">
|
|
76
|
+
<FolderOpen className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
|
|
77
|
+
|
|
78
|
+
<h3 className="text-lg font-semibold text-muted-foreground mb-2">
|
|
79
|
+
No Files Found
|
|
80
|
+
</h3>
|
|
81
|
+
|
|
82
|
+
<p className="text-sm text-muted-foreground max-w-md">
|
|
83
|
+
This directory is empty or no files match the current filters.
|
|
84
|
+
</p>
|
|
85
|
+
|
|
86
|
+
<button
|
|
87
|
+
onClick={() => model.refreshItems()}
|
|
88
|
+
className="mt-4 px-4 py-2 bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/90 focus:outline-none focus:ring-2 focus:ring-secondary focus:ring-offset-2 transition-colors"
|
|
89
|
+
>
|
|
90
|
+
Refresh
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Content with view mode switching
|
|
98
|
+
const renderViewMode = () => {
|
|
99
|
+
switch (currentViewMode) {
|
|
100
|
+
case 'tree':
|
|
101
|
+
return <TreeViewPlaceholder model={model} />;
|
|
102
|
+
case 'list':
|
|
103
|
+
return <ListViewPlaceholder model={model} />;
|
|
104
|
+
case 'thumbnail':
|
|
105
|
+
return <ThumbnailViewPlaceholder model={model} />;
|
|
106
|
+
default:
|
|
107
|
+
return <TreeViewPlaceholder model={model} />;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<ErrorBoundary>
|
|
113
|
+
<div
|
|
114
|
+
className={cn('flex-1 overflow-auto', className)}
|
|
115
|
+
role="main"
|
|
116
|
+
aria-label={`File browser content - ${currentViewMode} view`}
|
|
117
|
+
>
|
|
118
|
+
{renderViewMode()}
|
|
119
|
+
</div>
|
|
120
|
+
</ErrorBoundary>
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Placeholder components for view modes (to be implemented in Phase 4)
|
|
125
|
+
const TreeViewPlaceholder: React.FC<{ model: FileBrowserModel }> = observer(({ model }) => (
|
|
126
|
+
<div className="p-4">
|
|
127
|
+
<div className="text-sm text-muted-foreground mb-4">
|
|
128
|
+
Tree View Mode - {model.itemsArray.length} items
|
|
129
|
+
</div>
|
|
130
|
+
<div className="space-y-1">
|
|
131
|
+
{model.itemsArray.map((item) => (
|
|
132
|
+
<div
|
|
133
|
+
key={item.id}
|
|
134
|
+
className="flex items-center gap-2 p-2 hover:bg-muted rounded-md cursor-pointer"
|
|
135
|
+
onClick={() => {
|
|
136
|
+
if (item.type === 'directory') {
|
|
137
|
+
model.setCurrentPath(item.path);
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
<span className="text-sm">{item.type === 'directory' ? '📁' : '📄'}</span>
|
|
142
|
+
<span className="text-sm">{item.name}</span>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
));
|
|
148
|
+
|
|
149
|
+
const ListViewPlaceholder: React.FC<{ model: FileBrowserModel }> = observer(({ model }) => (
|
|
150
|
+
<div className="p-4">
|
|
151
|
+
<div className="text-sm text-muted-foreground mb-4">
|
|
152
|
+
List View Mode - {model.itemsArray.length} items
|
|
153
|
+
</div>
|
|
154
|
+
<div className="grid grid-cols-1 gap-1">
|
|
155
|
+
{model.itemsArray.map((item) => (
|
|
156
|
+
<div
|
|
157
|
+
key={item.id}
|
|
158
|
+
className="grid grid-cols-3 gap-4 p-2 hover:bg-muted rounded-md cursor-pointer text-sm"
|
|
159
|
+
onClick={() => {
|
|
160
|
+
if (item.type === 'directory') {
|
|
161
|
+
model.setCurrentPath(item.path);
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<span className="flex items-center gap-2">
|
|
166
|
+
<span>{item.type === 'directory' ? '📁' : '📄'}</span>
|
|
167
|
+
{item.name}
|
|
168
|
+
</span>
|
|
169
|
+
<span className="text-muted-foreground">
|
|
170
|
+
{item.size ? `${Math.round(item.size / 1024)}KB` : '-'}
|
|
171
|
+
</span>
|
|
172
|
+
<span className="text-muted-foreground">
|
|
173
|
+
{item.lastModified ? item.lastModified.toLocaleDateString() : '-'}
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
))}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
));
|
|
180
|
+
|
|
181
|
+
const ThumbnailViewPlaceholder: React.FC<{ model: FileBrowserModel }> = observer(({ model }) => (
|
|
182
|
+
<div className="p-4">
|
|
183
|
+
<div className="text-sm text-muted-foreground mb-4">
|
|
184
|
+
Thumbnail View Mode - {model.itemsArray.length} items
|
|
185
|
+
</div>
|
|
186
|
+
<div className="grid grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-4">
|
|
187
|
+
{model.itemsArray.map((item) => (
|
|
188
|
+
<div
|
|
189
|
+
key={item.id}
|
|
190
|
+
className="flex flex-col items-center p-2 hover:bg-muted rounded-md cursor-pointer"
|
|
191
|
+
onClick={() => {
|
|
192
|
+
if (item.type === 'directory') {
|
|
193
|
+
model.setCurrentPath(item.path);
|
|
194
|
+
}
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
<div className="w-12 h-12 flex items-center justify-center text-2xl mb-2">
|
|
198
|
+
{item.type === 'directory' ? '📁' : '📄'}
|
|
199
|
+
</div>
|
|
200
|
+
<span className="text-xs text-center line-clamp-2">{item.name}</span>
|
|
201
|
+
</div>
|
|
202
|
+
))}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
));
|
|
206
|
+
|
|
207
|
+
FileBrowserContent.displayName = 'FileBrowserContent';
|
|
208
|
+
|
|
209
|
+
export default FileBrowserContent;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { ChevronRight, Home } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import { FileBrowserModel } from '../models/FileBrowserModel';
|
|
6
|
+
import { ToolbarManagerModel } from '../models/ToolbarManagerModel';
|
|
7
|
+
import type { FileBrowserItem } from '../types/FileBrowserTypes';
|
|
8
|
+
import type { ToolbarAction, ActionContext } from '../types/ProviderTypes';
|
|
9
|
+
|
|
10
|
+
export interface FileBrowserHeaderProps {
|
|
11
|
+
model: FileBrowserModel;
|
|
12
|
+
toolbarManager?: ToolbarManagerModel;
|
|
13
|
+
className?: string;
|
|
14
|
+
showBreadcrumb?: boolean;
|
|
15
|
+
showToolbar?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FileBrowserHeader: React.FC<FileBrowserHeaderProps> = observer(({
|
|
19
|
+
model,
|
|
20
|
+
toolbarManager,
|
|
21
|
+
className,
|
|
22
|
+
showBreadcrumb = true,
|
|
23
|
+
showToolbar = true,
|
|
24
|
+
}) => {
|
|
25
|
+
const currentPath = model.currentPath;
|
|
26
|
+
const pathSegments = currentPath.split('/').filter(Boolean);
|
|
27
|
+
|
|
28
|
+
const handlePathClick = (index: number) => {
|
|
29
|
+
const newPath = index === -1 ? '/' : '/' + pathSegments.slice(0, index + 1).join('/');
|
|
30
|
+
model.setCurrentPath(newPath);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const renderBreadcrumb = () => {
|
|
34
|
+
if (!showBreadcrumb) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex items-center gap-1 min-w-0 flex-1">
|
|
38
|
+
{/* Root/Home button */}
|
|
39
|
+
<button
|
|
40
|
+
onClick={() => handlePathClick(-1)}
|
|
41
|
+
className={cn(
|
|
42
|
+
'flex items-center gap-1 px-2 py-1 rounded-md text-sm hover:bg-muted transition-colors',
|
|
43
|
+
currentPath === '/' && 'bg-muted text-muted-foreground'
|
|
44
|
+
)}
|
|
45
|
+
aria-label="Navigate to root"
|
|
46
|
+
>
|
|
47
|
+
<Home className="w-4 h-4" />
|
|
48
|
+
<span className="hidden sm:inline">Root</span>
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
{/* Path segments */}
|
|
52
|
+
{pathSegments.length > 0 && (
|
|
53
|
+
<>
|
|
54
|
+
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
|
55
|
+
<div className="flex items-center gap-1 min-w-0 flex-1">
|
|
56
|
+
{pathSegments.map((segment, index) => {
|
|
57
|
+
const isLast = index === pathSegments.length - 1;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<React.Fragment key={index}>
|
|
61
|
+
<button
|
|
62
|
+
onClick={() => handlePathClick(index)}
|
|
63
|
+
className={cn(
|
|
64
|
+
'px-2 py-1 rounded-md text-sm transition-colors truncate',
|
|
65
|
+
isLast
|
|
66
|
+
? 'bg-muted text-muted-foreground font-medium'
|
|
67
|
+
: 'hover:bg-muted'
|
|
68
|
+
)}
|
|
69
|
+
title={segment}
|
|
70
|
+
aria-current={isLast ? 'page' : undefined}
|
|
71
|
+
>
|
|
72
|
+
{segment}
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
{!isLast && (
|
|
76
|
+
<ChevronRight className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
|
77
|
+
)}
|
|
78
|
+
</React.Fragment>
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</div>
|
|
82
|
+
</>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const renderToolbar = () => {
|
|
89
|
+
if (!showToolbar) return null;
|
|
90
|
+
|
|
91
|
+
if (!toolbarManager) {
|
|
92
|
+
return (
|
|
93
|
+
<div className="flex items-center gap-1" />
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const context: ActionContext = {
|
|
98
|
+
selectedItems: [],
|
|
99
|
+
currentPath: model.currentPath,
|
|
100
|
+
provider: undefined as any,
|
|
101
|
+
viewMode: 'list',
|
|
102
|
+
allItems: model.itemsArray as any as FileBrowserItem[]
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const actions = toolbarManager.getAvailableActions(context);
|
|
106
|
+
|
|
107
|
+
if (actions.length === 0) {
|
|
108
|
+
return <div className="flex items-center gap-1" />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="flex items-center gap-1">
|
|
113
|
+
{actions.map((action: ToolbarAction) => (
|
|
114
|
+
<button
|
|
115
|
+
key={action.id}
|
|
116
|
+
onClick={() => action.action(context)}
|
|
117
|
+
disabled={action.isEnabled ? !action.isEnabled(context) : false}
|
|
118
|
+
className={cn(
|
|
119
|
+
'flex items-center gap-1 px-2 py-1 rounded-md text-sm transition-colors',
|
|
120
|
+
'hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed',
|
|
121
|
+
action.variant === 'primary' && 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
122
|
+
action.variant === 'destructive' && 'text-destructive hover:bg-destructive/10'
|
|
123
|
+
)}
|
|
124
|
+
title={action.tooltip || action.label}
|
|
125
|
+
>
|
|
126
|
+
{action.showLabel !== false && (
|
|
127
|
+
<span className="hidden sm:inline">{action.label}</span>
|
|
128
|
+
)}
|
|
129
|
+
</button>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<header
|
|
137
|
+
className={cn(
|
|
138
|
+
'flex items-center justify-between gap-4 p-4 border-b bg-background',
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
role="banner"
|
|
142
|
+
>
|
|
143
|
+
{renderBreadcrumb()}
|
|
144
|
+
{renderToolbar()}
|
|
145
|
+
</header>
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
FileBrowserHeader.displayName = 'FileBrowserHeader';
|
|
150
|
+
|
|
151
|
+
export default FileBrowserHeader;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { ChevronLeft, ChevronRight, ArrowUp, Menu, Home } from 'lucide-react';
|
|
4
|
+
import { NavigationManagerModel } from '../models/NavigationManagerModel';
|
|
5
|
+
import { ResponsiveLayoutManagerModel } from '../models/ResponsiveLayoutManagerModel';
|
|
6
|
+
import { FileBrowserItem } from '../types/FileBrowserTypes';
|
|
7
|
+
import { PathBreadcrumb } from '@anymux/ui/components/path-breadcrumb';
|
|
8
|
+
import { MobileNavigation } from './mobile/MobileNavigation';
|
|
9
|
+
|
|
10
|
+
interface FileBrowserToolbarProps {
|
|
11
|
+
navigationManager: NavigationManagerModel;
|
|
12
|
+
responsiveManager: ResponsiveLayoutManagerModel;
|
|
13
|
+
currentItem?: FileBrowserItem | null;
|
|
14
|
+
onNavigate?: (path: string) => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export const FileBrowserToolbar: React.FC<FileBrowserToolbarProps> = observer(({
|
|
21
|
+
navigationManager,
|
|
22
|
+
responsiveManager,
|
|
23
|
+
currentItem,
|
|
24
|
+
onNavigate,
|
|
25
|
+
className = ''
|
|
26
|
+
}) => {
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const handleBackClick = () => {
|
|
30
|
+
if (navigationManager.canGoBack) {
|
|
31
|
+
const previousPath = navigationManager.goBack();
|
|
32
|
+
if (previousPath) {
|
|
33
|
+
// Use navigation coordination for history navigation
|
|
34
|
+
navigationManager.navigateToWithCoordination(previousPath, 'history');
|
|
35
|
+
onNavigate?.(previousPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleForwardClick = () => {
|
|
41
|
+
if (navigationManager.canGoForward) {
|
|
42
|
+
const nextPath = navigationManager.goForward();
|
|
43
|
+
if (nextPath) {
|
|
44
|
+
// Use navigation coordination for history navigation
|
|
45
|
+
navigationManager.navigateToWithCoordination(nextPath, 'history');
|
|
46
|
+
onNavigate?.(nextPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleUpClick = () => {
|
|
52
|
+
const parentPath = getParentPath(currentItem?.path || navigationManager.currentPath || '/');
|
|
53
|
+
if (parentPath) {
|
|
54
|
+
// Use navigation coordination for up navigation
|
|
55
|
+
navigationManager.navigateToWithCoordination(parentPath, 'breadcrumb');
|
|
56
|
+
onNavigate?.(parentPath);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className={`file-browser-toolbar ${className}`}>
|
|
62
|
+
<div className="toolbar-left">
|
|
63
|
+
{/* Mobile Navigation */}
|
|
64
|
+
<MobileNavigation responsiveLayout={responsiveManager}>
|
|
65
|
+
<div className="mobile-nav-placeholder">
|
|
66
|
+
Mobile navigation content will be provided by parent component
|
|
67
|
+
</div>
|
|
68
|
+
</MobileNavigation>
|
|
69
|
+
|
|
70
|
+
{/* Navigation Controls */}
|
|
71
|
+
<div className="navigation-controls">
|
|
72
|
+
<button
|
|
73
|
+
className="nav-button"
|
|
74
|
+
onClick={handleBackClick}
|
|
75
|
+
disabled={!navigationManager.canGoBack}
|
|
76
|
+
aria-label="Go back"
|
|
77
|
+
title="Go back"
|
|
78
|
+
>
|
|
79
|
+
<ChevronLeft size={16} />
|
|
80
|
+
</button>
|
|
81
|
+
|
|
82
|
+
<button
|
|
83
|
+
className="nav-button"
|
|
84
|
+
onClick={handleForwardClick}
|
|
85
|
+
disabled={!navigationManager.canGoForward}
|
|
86
|
+
aria-label="Go forward"
|
|
87
|
+
title="Go forward"
|
|
88
|
+
>
|
|
89
|
+
<ChevronRight size={16} />
|
|
90
|
+
</button>
|
|
91
|
+
|
|
92
|
+
<button
|
|
93
|
+
className="nav-button"
|
|
94
|
+
onClick={handleUpClick}
|
|
95
|
+
disabled={!canGoUp(currentItem?.path || navigationManager.currentPath || '/')}
|
|
96
|
+
aria-label="Go up one level"
|
|
97
|
+
title="Go up"
|
|
98
|
+
>
|
|
99
|
+
<ArrowUp size={16} />
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Breadcrumbs */}
|
|
105
|
+
<div className="breadcrumbs-container">
|
|
106
|
+
<PathBreadcrumb
|
|
107
|
+
path={currentItem?.path || navigationManager.currentPath || '/'}
|
|
108
|
+
onNavigate={(path) => {
|
|
109
|
+
navigationManager.navigateToWithCoordination(path, 'breadcrumb');
|
|
110
|
+
onNavigate?.(path);
|
|
111
|
+
}}
|
|
112
|
+
maxSegments={responsiveManager.isMobile ? 2 : 4}
|
|
113
|
+
showHome
|
|
114
|
+
rootLabel="Home"
|
|
115
|
+
className="file-browser-breadcrumbs"
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{/* Global Actions */}
|
|
120
|
+
<div className="toolbar-right">
|
|
121
|
+
{/* Additional actions can be added here */}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Helper functions
|
|
128
|
+
|
|
129
|
+
function getParentPath(path: string): string | null {
|
|
130
|
+
if (!path || path === '/') return null;
|
|
131
|
+
|
|
132
|
+
const normalized = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
133
|
+
const lastSlashIndex = normalized.lastIndexOf('/');
|
|
134
|
+
|
|
135
|
+
if (lastSlashIndex === 0) return '/';
|
|
136
|
+
if (lastSlashIndex === -1) return null;
|
|
137
|
+
|
|
138
|
+
return normalized.substring(0, lastSlashIndex);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function canGoUp(path: string): boolean {
|
|
142
|
+
return getParentPath(path) !== null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
FileBrowserToolbar.displayName = 'FileBrowserToolbar';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { LeftPanelManagerModel } from '../../models/LeftPanelManagerModel';
|
|
4
|
+
import { ResponsiveLayoutManagerModel } from '../../models/ResponsiveLayoutManagerModel';
|
|
5
|
+
import { LeftPanelTabs } from './LeftPanelTabs';
|
|
6
|
+
import { TreeNavigationView } from './TreeNavigationView';
|
|
7
|
+
|
|
8
|
+
interface LeftPanelProps {
|
|
9
|
+
leftPanelManager: LeftPanelManagerModel;
|
|
10
|
+
responsiveManager: ResponsiveLayoutManagerModel;
|
|
11
|
+
onNavigate?: (path: string) => void;
|
|
12
|
+
onSelectionChange?: (item: any) => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const LeftPanel: React.FC<LeftPanelProps> = observer(({
|
|
17
|
+
leftPanelManager,
|
|
18
|
+
responsiveManager,
|
|
19
|
+
onNavigate,
|
|
20
|
+
onSelectionChange,
|
|
21
|
+
className = ''
|
|
22
|
+
}) => {
|
|
23
|
+
const currentMode = leftPanelManager.currentMode;
|
|
24
|
+
const currentModeData = leftPanelManager.currentModeData;
|
|
25
|
+
|
|
26
|
+
// Handle mobile overlay
|
|
27
|
+
const shouldShowAsOverlay = responsiveManager.isMobile && responsiveManager.activePanel === 'left';
|
|
28
|
+
const isVisible = responsiveManager.shouldShowLeftPanel;
|
|
29
|
+
|
|
30
|
+
const handleModeChange = (modeId: string) => {
|
|
31
|
+
leftPanelManager.setCurrentMode(modeId);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleOverlayClick = (event: React.MouseEvent) => {
|
|
35
|
+
// Close overlay when clicking outside on mobile
|
|
36
|
+
if (event.target === event.currentTarget && shouldShowAsOverlay) {
|
|
37
|
+
responsiveManager.hideMobilePanel();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const renderCurrentModeView = () => {
|
|
42
|
+
switch (currentMode) {
|
|
43
|
+
case 'tree':
|
|
44
|
+
return (
|
|
45
|
+
<TreeNavigationView
|
|
46
|
+
leftPanelManager={leftPanelManager}
|
|
47
|
+
onNavigate={onNavigate}
|
|
48
|
+
onSelectionChange={onSelectionChange}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
// For future modes like git-graph, bookmarks, etc.
|
|
54
|
+
if (currentModeData?.component) {
|
|
55
|
+
const Component = currentModeData.component;
|
|
56
|
+
return (
|
|
57
|
+
<Component
|
|
58
|
+
leftPanelManager={leftPanelManager}
|
|
59
|
+
onNavigate={onNavigate}
|
|
60
|
+
onSelectionChange={onSelectionChange}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="left-panel-placeholder">
|
|
67
|
+
<p>Mode "{currentMode}" not implemented yet</p>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!isVisible) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
className={`left-panel ${shouldShowAsOverlay ? 'left-panel-overlay' : 'left-panel-docked'} ${className}`}
|
|
80
|
+
onClick={handleOverlayClick}
|
|
81
|
+
aria-label="Navigation panel"
|
|
82
|
+
role="navigation"
|
|
83
|
+
>
|
|
84
|
+
<div
|
|
85
|
+
className="left-panel-content"
|
|
86
|
+
onClick={(e) => e.stopPropagation()} // Prevent overlay close when clicking content
|
|
87
|
+
>
|
|
88
|
+
{/* Tab Switcher */}
|
|
89
|
+
<LeftPanelTabs
|
|
90
|
+
leftPanelManager={leftPanelManager}
|
|
91
|
+
onModeChange={handleModeChange}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
{/* Current Mode View */}
|
|
95
|
+
<div className="left-panel-view-container">
|
|
96
|
+
{renderCurrentModeView()}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
LeftPanel.displayName = 'LeftPanel';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { LeftPanelManagerModel } from '../../models/LeftPanelManagerModel';
|
|
4
|
+
|
|
5
|
+
interface LeftPanelTabsProps {
|
|
6
|
+
leftPanelManager: LeftPanelManagerModel;
|
|
7
|
+
onModeChange: (modeId: string) => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const LeftPanelTabs: React.FC<LeftPanelTabsProps> = observer(({
|
|
12
|
+
leftPanelManager,
|
|
13
|
+
onModeChange,
|
|
14
|
+
className = ''
|
|
15
|
+
}) => {
|
|
16
|
+
const availableModes = leftPanelManager.availableModesList;
|
|
17
|
+
const currentMode = leftPanelManager.currentMode;
|
|
18
|
+
|
|
19
|
+
// Don't show tabs if there's only one mode
|
|
20
|
+
if (availableModes.length <= 1) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const handleTabClick = (modeId: string) => {
|
|
25
|
+
if (modeId !== currentMode) {
|
|
26
|
+
onModeChange(modeId);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleKeyDown = (event: React.KeyboardEvent, modeId: string) => {
|
|
31
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
handleTabClick(modeId);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={`left-panel-tabs ${className}`}
|
|
40
|
+
role="tablist"
|
|
41
|
+
aria-label="Navigation modes"
|
|
42
|
+
>
|
|
43
|
+
{availableModes.map((mode) => (
|
|
44
|
+
<button
|
|
45
|
+
key={mode.id}
|
|
46
|
+
className={`left-panel-tab ${currentMode === mode.id ? 'left-panel-tab-active' : 'left-panel-tab-inactive'}`}
|
|
47
|
+
onClick={() => handleTabClick(mode.id)}
|
|
48
|
+
onKeyDown={(e) => handleKeyDown(e, mode.id)}
|
|
49
|
+
role="tab"
|
|
50
|
+
aria-selected={currentMode === mode.id}
|
|
51
|
+
aria-controls={`left-panel-view-${mode.id}`}
|
|
52
|
+
title={mode.name}
|
|
53
|
+
disabled={!mode.isAvailable?.()}
|
|
54
|
+
>
|
|
55
|
+
{/* Icon */}
|
|
56
|
+
<span className="left-panel-tab-icon" aria-hidden="true">
|
|
57
|
+
{mode.icon}
|
|
58
|
+
</span>
|
|
59
|
+
|
|
60
|
+
{/* Label */}
|
|
61
|
+
<span className="left-panel-tab-label">
|
|
62
|
+
{mode.name}
|
|
63
|
+
</span>
|
|
64
|
+
</button>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
LeftPanelTabs.displayName = 'LeftPanelTabs';
|