@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.
Files changed (244) hide show
  1. package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
  2. package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
  3. package/dist/FileBrowserContext-B6jixa2j.js +11 -0
  4. package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
  5. package/dist/calendar-DSlrbHoj.js +761 -0
  6. package/dist/calendar-DSlrbHoj.js.map +1 -0
  7. package/dist/calendar.d.ts +3 -0
  8. package/dist/calendar.js +3 -0
  9. package/dist/contacts-DQXTZzHc.js +539 -0
  10. package/dist/contacts-DQXTZzHc.js.map +1 -0
  11. package/dist/contacts.d.ts +3 -0
  12. package/dist/contacts.js +3 -0
  13. package/dist/file-browser-m5atC3kF.js +6755 -0
  14. package/dist/file-browser-m5atC3kF.js.map +1 -0
  15. package/dist/file-browser.d.ts +11 -0
  16. package/dist/file-browser.js +9 -0
  17. package/dist/git-B55e6LL-.js +561 -0
  18. package/dist/git-B55e6LL-.js.map +1 -0
  19. package/dist/git.d.ts +2 -0
  20. package/dist/git.js +3 -0
  21. package/dist/iconMap-V4B8P-Uh.js +206 -0
  22. package/dist/iconMap-V4B8P-Uh.js.map +1 -0
  23. package/dist/icons-CIsIOZXR.js +0 -0
  24. package/dist/icons.d.ts +2 -0
  25. package/dist/icons.js +4 -0
  26. package/dist/index-BNmNIWBL.d.ts +71 -0
  27. package/dist/index-BNmNIWBL.d.ts.map +1 -0
  28. package/dist/index-Bryv_GCG.d.ts +1481 -0
  29. package/dist/index-Bryv_GCG.d.ts.map +1 -0
  30. package/dist/index-CuQIjSXs.d.ts +134 -0
  31. package/dist/index-CuQIjSXs.d.ts.map +1 -0
  32. package/dist/index-DSu19mq0.d.ts +153 -0
  33. package/dist/index-DSu19mq0.d.ts.map +1 -0
  34. package/dist/index-DmsyeHFr.d.ts +149 -0
  35. package/dist/index-DmsyeHFr.d.ts.map +1 -0
  36. package/dist/index-DxnJ8FYM.d.ts +17 -0
  37. package/dist/index-DxnJ8FYM.d.ts.map +1 -0
  38. package/dist/index-DzfY1Tok.d.ts +32 -0
  39. package/dist/index-DzfY1Tok.d.ts.map +1 -0
  40. package/dist/index-Ml_SgiKa.d.ts +1847 -0
  41. package/dist/index-Ml_SgiKa.d.ts.map +1 -0
  42. package/dist/index-kHr9udZD.d.ts +1025 -0
  43. package/dist/index-kHr9udZD.d.ts.map +1 -0
  44. package/dist/index.d.ts +11 -0
  45. package/dist/index.js +15 -0
  46. package/dist/layout-Ca_4r8ka.js +89 -0
  47. package/dist/layout-Ca_4r8ka.js.map +1 -0
  48. package/dist/layout.d.ts +2 -0
  49. package/dist/layout.js +5 -0
  50. package/dist/list-CxfT6hix.js +6831 -0
  51. package/dist/list-CxfT6hix.js.map +1 -0
  52. package/dist/list.d.ts +2 -0
  53. package/dist/list.js +5 -0
  54. package/dist/media-DZ292aKK.js +557 -0
  55. package/dist/media-DZ292aKK.js.map +1 -0
  56. package/dist/media.d.ts +3 -0
  57. package/dist/media.js +3 -0
  58. package/dist/tree-Dd9Z0Aso.js +3351 -0
  59. package/dist/tree-Dd9Z0Aso.js.map +1 -0
  60. package/dist/tree.d.ts +2 -0
  61. package/dist/tree.js +6 -0
  62. package/dist/types-common-CB3kRek8.d.ts +26 -0
  63. package/dist/types-common-CB3kRek8.d.ts.map +1 -0
  64. package/dist/utils-B4fdKKsy.js +3 -0
  65. package/package.json +109 -0
  66. package/src/calendar/AgendaView.tsx +37 -0
  67. package/src/calendar/CalendarBrowser.tsx +90 -0
  68. package/src/calendar/CalendarModel.ts +142 -0
  69. package/src/calendar/CalendarSidebar.tsx +81 -0
  70. package/src/calendar/DayView.tsx +76 -0
  71. package/src/calendar/EventCard.tsx +51 -0
  72. package/src/calendar/MockCalendarProvider.ts +98 -0
  73. package/src/calendar/MonthView.tsx +77 -0
  74. package/src/calendar/WeekView.tsx +129 -0
  75. package/src/calendar/index.ts +18 -0
  76. package/src/calendar/types.ts +25 -0
  77. package/src/contacts/ContactAvatar.tsx +35 -0
  78. package/src/contacts/ContactBrowser.tsx +56 -0
  79. package/src/contacts/ContactCard.tsx +37 -0
  80. package/src/contacts/ContactDetail.tsx +63 -0
  81. package/src/contacts/ContactGroupSidebar.tsx +40 -0
  82. package/src/contacts/ContactList.tsx +32 -0
  83. package/src/contacts/ContactListModel.ts +120 -0
  84. package/src/contacts/MockContactProvider.ts +77 -0
  85. package/src/contacts/index.ts +17 -0
  86. package/src/contacts/types.ts +26 -0
  87. package/src/demos/CalendarBrowserDemo.tsx +15 -0
  88. package/src/demos/ContactBrowserDemo.tsx +15 -0
  89. package/src/demos/MediaBrowserDemo.tsx +15 -0
  90. package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
  91. package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
  92. package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
  93. package/src/file-browser/adapters/README.md +504 -0
  94. package/src/file-browser/adapters/index.ts +27 -0
  95. package/src/file-browser/adapters/types.ts +70 -0
  96. package/src/file-browser/architecture.md +645 -0
  97. package/src/file-browser/components/CreateItemDialog.tsx +71 -0
  98. package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
  99. package/src/file-browser/components/FileBrowser.tsx +473 -0
  100. package/src/file-browser/components/FileBrowserContent.tsx +209 -0
  101. package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
  102. package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
  103. package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
  104. package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
  105. package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
  106. package/src/file-browser/components/PreviewPane.tsx +146 -0
  107. package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
  108. package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
  109. package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
  110. package/src/file-browser/components/UploadProgress.tsx +123 -0
  111. package/src/file-browser/components/ViewerHost.tsx +208 -0
  112. package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
  113. package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
  114. package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
  115. package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
  116. package/src/file-browser/components/shared/FileIcon.tsx +169 -0
  117. package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
  118. package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
  119. package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
  120. package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
  121. package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
  122. package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
  123. package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
  124. package/src/file-browser/context/FileBrowserContext.tsx +13 -0
  125. package/src/file-browser/examples/BasicUsage.tsx +20 -0
  126. package/src/file-browser/index.ts +98 -0
  127. package/src/file-browser/models/FileBrowserModel.ts +623 -0
  128. package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
  129. package/src/file-browser/models/NavigationManagerModel.ts +312 -0
  130. package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
  131. package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
  132. package/src/file-browser/models/SelectionManagerModel.ts +252 -0
  133. package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
  134. package/src/file-browser/models/UploadModel.ts +147 -0
  135. package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
  136. package/src/file-browser/models/ViewerHostModel.ts +44 -0
  137. package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
  138. package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
  139. package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
  140. package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
  141. package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
  142. package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
  143. package/src/file-browser/providers/FileSystemProvider.ts +401 -0
  144. package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
  145. package/src/file-browser/providers/GitProvider.ts +337 -0
  146. package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
  147. package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
  148. package/src/file-browser/providers/MemoryProvider.ts +303 -0
  149. package/src/file-browser/providers/index.ts +4 -0
  150. package/src/file-browser/registry/ViewerRegistry.ts +551 -0
  151. package/src/file-browser/registry/types.ts +144 -0
  152. package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
  153. package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
  154. package/src/file-browser/tasks.md +537 -0
  155. package/src/file-browser/types/FileBrowserTypes.ts +126 -0
  156. package/src/file-browser/types/ProviderTypes.ts +155 -0
  157. package/src/file-browser/types/UITypes.ts +235 -0
  158. package/src/file-browser/types/ViewModeTypes.ts +150 -0
  159. package/src/file-browser/utils/gestures.ts +327 -0
  160. package/src/file-browser/utils/performance.ts +563 -0
  161. package/src/file-browser/viewers/ImageViewer.tsx +163 -0
  162. package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
  163. package/src/file-browser/viewers/TextViewer.tsx +95 -0
  164. package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
  165. package/src/file-browser/viewers/index.ts +61 -0
  166. package/src/git/BranchList.tsx +128 -0
  167. package/src/git/CommitGraph.tsx +239 -0
  168. package/src/git/CommitList.tsx +258 -0
  169. package/src/git/DiffViewer.tsx +219 -0
  170. package/src/git/index.ts +4 -0
  171. package/src/icons/iconMap.ts +146 -0
  172. package/src/icons/index.ts +9 -0
  173. package/src/index.ts +13 -0
  174. package/src/layout/README.md +307 -0
  175. package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
  176. package/src/layout/examples/SimpleExample.tsx +60 -0
  177. package/src/layout/index.ts +6 -0
  178. package/src/lib/utils.ts +1 -0
  179. package/src/list/README.md +303 -0
  180. package/src/list/architecture.md +807 -0
  181. package/src/list/components/CalculatedGridView.tsx +252 -0
  182. package/src/list/components/DragPreview.tsx +102 -0
  183. package/src/list/components/ListContextMenu.tsx +274 -0
  184. package/src/list/components/ListItem.tsx +761 -0
  185. package/src/list/components/ListItems.tsx +919 -0
  186. package/src/list/components/MasonryView.tsx +241 -0
  187. package/src/list/components/SearchFilter.tsx +44 -0
  188. package/src/list/components/TreemapView.tsx +709 -0
  189. package/src/list/components/ViewSizeControls.tsx +205 -0
  190. package/src/list/components/ViewTypeSelector.tsx +312 -0
  191. package/src/list/components/VirtualizedDetailsView.tsx +231 -0
  192. package/src/list/components/VirtualizedGrid.tsx +164 -0
  193. package/src/list/components/VirtualizedList.tsx +154 -0
  194. package/src/list/components/VirtualizedMasonryView.tsx +344 -0
  195. package/src/list/components/shared/EmptyState.tsx +103 -0
  196. package/src/list/components/shared/ErrorBoundary.tsx +123 -0
  197. package/src/list/components/shared/ErrorDisplay.tsx +100 -0
  198. package/src/list/components/shared/ListLoader.tsx +146 -0
  199. package/src/list/components/shared/LoadingIndicator.tsx +80 -0
  200. package/src/list/index.ts +92 -0
  201. package/src/list/models/ListItemsModel.ts +1301 -0
  202. package/src/list/models/TreemapModel.ts +204 -0
  203. package/src/list/providers/ListItemsProvider.ts +313 -0
  204. package/src/list/providers/TestListProvider.ts +604 -0
  205. package/src/list/tasks.md +937 -0
  206. package/src/list/types/ListTypes.ts +178 -0
  207. package/src/list/utils/BenchmarkLogger.ts +243 -0
  208. package/src/list/utils/DragDropManager.ts +320 -0
  209. package/src/list/utils/GridLayoutCalculator.ts +290 -0
  210. package/src/list/utils/ListAccessibility.ts +367 -0
  211. package/src/list/utils/ListKeyboard.ts +414 -0
  212. package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
  213. package/src/list/utils/MasonryLayoutEngine.ts +401 -0
  214. package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
  215. package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
  216. package/src/media/AlbumSidebar.tsx +48 -0
  217. package/src/media/MediaBrowser.tsx +92 -0
  218. package/src/media/MediaBrowserModel.ts +138 -0
  219. package/src/media/MediaGrid.tsx +50 -0
  220. package/src/media/MediaList.tsx +49 -0
  221. package/src/media/MediaPreview.tsx +63 -0
  222. package/src/media/MediaTimeline.tsx +38 -0
  223. package/src/media/MockMediaProvider.ts +70 -0
  224. package/src/media/index.ts +18 -0
  225. package/src/media/types.ts +21 -0
  226. package/src/styles/variables.css +60 -0
  227. package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
  228. package/src/tree/__tests__/TreeModel.test.ts +16 -0
  229. package/src/tree/architecture.md +530 -0
  230. package/src/tree/components/Tree.tsx +283 -0
  231. package/src/tree/components/TreeCheckbox.tsx +147 -0
  232. package/src/tree/components/TreeContextMenu.tsx +139 -0
  233. package/src/tree/components/TreeNodeList.tsx +329 -0
  234. package/src/tree/components/TreeTable.tsx +382 -0
  235. package/src/tree/index.ts +58 -0
  236. package/src/tree/models/TreeModel.ts +839 -0
  237. package/src/tree/providers/SimpleTreeProvider.ts +463 -0
  238. package/src/tree/providers/TestTreeProvider.ts +946 -0
  239. package/src/tree/providers/TreeProvider.ts +308 -0
  240. package/src/tree/tasks.md +2046 -0
  241. package/src/tree/types/TreeTypes.ts +279 -0
  242. package/src/tree/utils/SelectionTheme.ts +150 -0
  243. package/src/tree/utils/logger.ts +203 -0
  244. 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';