@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,645 @@
1
+ # FileBrowser Architecture v2.0 - Integrated with TreeComponent and ListItemsComponent
2
+
3
+ ## Overview
4
+
5
+ The FileBrowser v2.0 is a unified, extensible dual-panel file browser component that leverages existing TreeComponent and ListItemsComponent architectures. It provides a modern file exploration experience with a dedicated tree navigation panel (folders only) and a synchronized content panel (files and folders) that maintains consistent selection state.
6
+
7
+ // AICODE-NOTE: Architecture updated to use existing TreeComponent and ListItemsComponent instead of custom implementations
8
+ // AICODE-NOTE: Tree shows directories only, list shows all items, both are synchronized via shared selection state
9
+
10
+ ## Key Design Principles
11
+
12
+ - **Component Reuse**: Leverages existing TreeComponent (left panel) and ListItemsComponent (right panel)
13
+ - **File System Agnostic**: Works with any `@anymux/file-system` implementation
14
+ - **Synchronized State**: Tree selection drives list content, list navigation updates tree selection
15
+ - **Provider Bridge**: Custom providers bridge between `@anymux/file-system` and component providers
16
+ - **Directory-Only Tree**: Tree shows only directories for hierarchical navigation
17
+ - **Full Content List**: List shows all items (files and directories) in current selection
18
+ - **MobX Reactive State**: Following strict MobX model guidelines for state synchronization
19
+
20
+ ## Architecture Overview
21
+
22
+ ```
23
+ FileBrowser v2.0
24
+ ├── FileBrowserModel (Orchestrator)
25
+ │ ├── FileSystemBridge (Adapter)
26
+ │ │ └── IFileSystem (@anymux/file-system)
27
+ │ ├── TreeProvider (Directory Tree)
28
+ │ │ └── TreeComponent (Left Panel)
29
+ │ ├── ListItemsProvider (Content List)
30
+ │ │ └── ListItemsComponent (Right Panel)
31
+ │ └── SelectionSynchronizer (State Coordinator)
32
+ ```
33
+
34
+ ## Core Components Integration
35
+
36
+ ### 1. FileBrowserModel (Orchestrator)
37
+ **Location**: `/models/FileBrowserModel.ts`
38
+
39
+ **Purpose**: Main orchestrator that manages both TreeComponent and ListItemsComponent, handling state synchronization and file system interactions.
40
+
41
+ ```typescript
42
+ class FileBrowserModel {
43
+ // File system integration
44
+ fileSystem: IFileSystem
45
+ fileSystemBridge: FileSystemBridge
46
+
47
+ // Component providers
48
+ treeProvider: FileSystemTreeProvider
49
+ listProvider: FileSystemListProvider
50
+
51
+ // Models
52
+ treeModel: TreeModel
53
+ listModel: ListItemsModel
54
+
55
+ // Synchronized state
56
+ currentPath: string = '/'
57
+ selectedPath: string | null = null
58
+
59
+ constructor(fileSystem: IFileSystem) {
60
+ makeAutoObservable(this, {})
61
+
62
+ this.fileSystem = fileSystem
63
+ this.fileSystemBridge = new FileSystemBridge(fileSystem)
64
+
65
+ // Create providers that bridge to file system
66
+ this.treeProvider = new FileSystemTreeProvider(this.fileSystemBridge, {
67
+ showFilesInTree: false, // AICODE-NOTE: Tree shows directories only
68
+ onSelectionChange: this.handleTreeSelection
69
+ })
70
+
71
+ this.listProvider = new FileSystemListProvider(this.fileSystemBridge, {
72
+ showAllItems: true, // AICODE-NOTE: List shows files and directories
73
+ onSelectionChange: this.handleListSelection,
74
+ onNavigation: this.handleListNavigation
75
+ })
76
+
77
+ // Create component models
78
+ this.treeModel = new TreeModel(this.treeProvider)
79
+ this.listModel = new ListItemsModel(this.listProvider)
80
+ }
81
+
82
+ // Synchronization actions
83
+ handleTreeSelection = (path: string) => {
84
+ this.selectedPath = path
85
+ this.currentPath = path
86
+ // Load list content for selected directory
87
+ this.listModel.setPath(path)
88
+ }
89
+
90
+ handleListNavigation = (path: string) => {
91
+ this.currentPath = path
92
+ this.selectedPath = path
93
+ // Update tree to highlight the directory
94
+ this.treeModel.selectPath(path)
95
+ // Expand tree to make path visible
96
+ this.treeModel.expandToPath(path)
97
+ }
98
+
99
+ handleListSelection = (selectedItems: ListItemData[]) => {
100
+ // If single directory selected, could auto-navigate (optional)
101
+ if (selectedItems.length === 1 && selectedItems[0].type === 'directory') {
102
+ // Optional: auto-navigate to directory
103
+ // this.handleListNavigation(selectedItems[0].path)
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### 2. FileSystemBridge (Adapter)
110
+ **Location**: `/adapters/FileSystemBridge.ts`
111
+
112
+ **Purpose**: Adapter that converts `@anymux/file-system` operations to data structures expected by TreeComponent and ListItemsComponent.
113
+
114
+ ```typescript
115
+ interface FileSystemItem {
116
+ id: string
117
+ path: string
118
+ name: string
119
+ type: 'file' | 'directory'
120
+ size?: number
121
+ modified?: Date
122
+ isDirectory: boolean
123
+ children?: FileSystemItem[]
124
+ }
125
+
126
+ class FileSystemBridge {
127
+ constructor(private fileSystem: IFileSystem) {
128
+ makeAutoObservable(this, {})
129
+ }
130
+
131
+ // Tree operations (directories only)
132
+ async loadDirectoryTree(path: string): Promise<FileSystemItem[]> {
133
+ const dirents = await this.fileSystem.readdir(path, { withFileTypes: true })
134
+
135
+ return dirents
136
+ .filter(dirent => dirent.isDirectory()) // AICODE-NOTE: Filter to directories only for tree
137
+ .map(dirent => ({
138
+ id: `${path}/${dirent.name}`.replace(/\/\//g, '/'),
139
+ path: `${path}/${dirent.name}`.replace(/\/\//g, '/'),
140
+ name: dirent.name,
141
+ type: 'directory' as const,
142
+ isDirectory: true
143
+ }))
144
+ }
145
+
146
+ // List operations (all items)
147
+ async loadDirectoryContents(path: string): Promise<FileSystemItem[]> {
148
+ const dirents = await this.fileSystem.readdir(path, { withFileTypes: true })
149
+
150
+ const items = await Promise.all(
151
+ dirents.map(async (dirent) => {
152
+ const itemPath = `${path}/${dirent.name}`.replace(/\/\//g, '/')
153
+ let stats: Stats | null = null
154
+
155
+ try {
156
+ stats = await this.fileSystem.stat(itemPath)
157
+ } catch (error) {
158
+ // Handle permission errors gracefully
159
+ }
160
+
161
+ return {
162
+ id: itemPath,
163
+ path: itemPath,
164
+ name: dirent.name,
165
+ type: dirent.isDirectory() ? 'directory' as const : 'file' as const,
166
+ size: stats?.size,
167
+ modified: stats?.mtime,
168
+ isDirectory: dirent.isDirectory()
169
+ }
170
+ })
171
+ )
172
+
173
+ // Sort: directories first, then files, both alphabetically
174
+ return items.sort((a, b) => {
175
+ if (a.type !== b.type) {
176
+ return a.type === 'directory' ? -1 : 1
177
+ }
178
+ return a.name.localeCompare(b.name)
179
+ })
180
+ }
181
+
182
+ // File operations
183
+ async readFile(path: string): Promise<Buffer | string> {
184
+ return this.fileSystem.readFile(path)
185
+ }
186
+
187
+ async writeFile(path: string, content: Buffer | string): Promise<void> {
188
+ return this.fileSystem.writeFile(path, content)
189
+ }
190
+
191
+ async deleteItem(path: string, isDirectory: boolean): Promise<void> {
192
+ if (isDirectory) {
193
+ return this.fileSystem.rmdir(path, { recursive: true })
194
+ } else {
195
+ return this.fileSystem.unlink(path)
196
+ }
197
+ }
198
+
199
+ async createDirectory(path: string): Promise<void> {
200
+ return this.fileSystem.mkdir(path, { recursive: true })
201
+ }
202
+
203
+ async renameItem(oldPath: string, newPath: string): Promise<void> {
204
+ return this.fileSystem.rename(oldPath, newPath)
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### 3. FileSystemTreeProvider (Tree Integration)
210
+ **Location**: `/providers/FileSystemTreeProvider.ts`
211
+
212
+ **Purpose**: Bridges FileSystemBridge to TreeComponent provider interface, showing directories only.
213
+
214
+ ```typescript
215
+ class FileSystemTreeProvider implements TreeProvider {
216
+ constructor(
217
+ private bridge: FileSystemBridge,
218
+ private options: {
219
+ showFilesInTree: boolean
220
+ onSelectionChange: (path: string) => void
221
+ }
222
+ ) {
223
+ makeAutoObservable(this, {})
224
+ }
225
+
226
+ // TreeProvider implementation
227
+ async loadNodes(options?: TreeLoadOptions): Promise<TreeLoadResult> {
228
+ const rootItems = await this.bridge.loadDirectoryTree(options?.path || '/')
229
+
230
+ const nodes: TreeNodeData[] = rootItems.map(item => ({
231
+ id: item.id,
232
+ text: item.name,
233
+ data: item,
234
+ hasChildren: true, // Assume directories have children
235
+ isExpanded: false,
236
+ icon: 'folder'
237
+ }))
238
+
239
+ return {
240
+ nodes,
241
+ totalCount: nodes.length
242
+ }
243
+ }
244
+
245
+ async loadChildren(nodeId: string): Promise<TreeNodeData[]> {
246
+ const items = await this.bridge.loadDirectoryTree(nodeId)
247
+
248
+ return items.map(item => ({
249
+ id: item.id,
250
+ text: item.name,
251
+ data: item,
252
+ hasChildren: true,
253
+ isExpanded: false,
254
+ icon: 'folder'
255
+ }))
256
+ }
257
+
258
+ // Selection handling
259
+ onSelectionChange = (selectedNodes: TreeNodeData[]) => {
260
+ if (selectedNodes.length > 0) {
261
+ const selectedNode = selectedNodes[0]
262
+ this.options.onSelectionChange(selectedNode.data.path)
263
+ }
264
+ }
265
+
266
+ // Context menu for directories
267
+ getNodeContextMenu(node: TreeNodeData): ContextMenuItem[] {
268
+ return [
269
+ {
270
+ id: 'open',
271
+ label: 'Open',
272
+ icon: 'folder-open',
273
+ action: () => this.options.onSelectionChange(node.data.path)
274
+ },
275
+ { id: 'separator1', type: 'separator' },
276
+ {
277
+ id: 'new-folder',
278
+ label: 'New Folder',
279
+ icon: 'folder-plus',
280
+ action: () => this.createNewFolder(node.data.path)
281
+ },
282
+ { id: 'separator2', type: 'separator' },
283
+ {
284
+ id: 'rename',
285
+ label: 'Rename',
286
+ icon: 'edit',
287
+ action: () => this.renameDirectory(node.data.path)
288
+ },
289
+ {
290
+ id: 'delete',
291
+ label: 'Delete',
292
+ icon: 'trash',
293
+ action: () => this.deleteDirectory(node.data.path)
294
+ }
295
+ ]
296
+ }
297
+
298
+ private async createNewFolder(parentPath: string) {
299
+ // Implementation for creating new folder
300
+ }
301
+
302
+ private async renameDirectory(path: string) {
303
+ // Implementation for renaming directory
304
+ }
305
+
306
+ private async deleteDirectory(path: string) {
307
+ // Implementation for deleting directory
308
+ }
309
+ }
310
+ ```
311
+
312
+ ### 4. FileSystemListProvider (List Integration)
313
+ **Location**: `/providers/FileSystemListProvider.ts`
314
+
315
+ **Purpose**: Bridges FileSystemBridge to ListItemsComponent provider interface, showing all items with file previews and actions.
316
+
317
+ ```typescript
318
+ class FileSystemListProvider implements ListItemsProvider {
319
+ currentPath: string = '/'
320
+
321
+ constructor(
322
+ private bridge: FileSystemBridge,
323
+ private options: {
324
+ showAllItems: boolean
325
+ onSelectionChange: (items: ListItemData[]) => void
326
+ onNavigation: (path: string) => void
327
+ }
328
+ ) {
329
+ makeAutoObservable(this, {})
330
+ }
331
+
332
+ setPath(path: string) {
333
+ this.currentPath = path
334
+ }
335
+
336
+ // ListItemsProvider implementation
337
+ async loadItems(options?: ListLoadOptions): Promise<ListLoadResult> {
338
+ const items = await this.bridge.loadDirectoryContents(this.currentPath)
339
+
340
+ const listItems: ListItemData[] = items.map(item => ({
341
+ id: item.id,
342
+ name: item.name,
343
+ type: item.type,
344
+ path: item.path,
345
+ size: item.size,
346
+ modified: item.modified,
347
+ isDirectory: item.isDirectory,
348
+ data: item,
349
+ // Thumbnails for images
350
+ thumbnailUrl: this.getThumbnailUrl(item),
351
+ // File type icons
352
+ icon: this.getFileIcon(item)
353
+ }))
354
+
355
+ return {
356
+ items: listItems,
357
+ totalCount: listItems.length
358
+ }
359
+ }
360
+
361
+ // View type support
362
+ getSupportedViewTypes(): ListViewType[] {
363
+ return [
364
+ LIST_VIEW_TYPE,
365
+ GRID_VIEW_TYPE,
366
+ DETAILS_VIEW_TYPE,
367
+ MASONRY_HORIZONTAL_VIEW_TYPE
368
+ ]
369
+ }
370
+
371
+ // Double-click handling
372
+ onItemAction = async (item: ListItemData, action: string) => {
373
+ if (action === 'open' || action === 'double-click') {
374
+ if (item.isDirectory) {
375
+ // Navigate to directory
376
+ this.options.onNavigation(item.path)
377
+ } else {
378
+ // Open file (could trigger preview or download)
379
+ await this.openFile(item.path)
380
+ }
381
+ }
382
+ }
383
+
384
+ // Selection handling
385
+ onSelectionChange = (selectedItems: ListItemData[]) => {
386
+ this.options.onSelectionChange(selectedItems)
387
+ }
388
+
389
+ // Context menu for files and directories
390
+ getItemContextMenu(item: ListItemData): ContextMenuItem[] {
391
+ const baseMenu = [
392
+ {
393
+ id: 'open',
394
+ label: item.isDirectory ? 'Open' : 'Open',
395
+ icon: item.isDirectory ? 'folder-open' : 'eye',
396
+ action: () => this.onItemAction(item, 'open')
397
+ }
398
+ ]
399
+
400
+ if (!item.isDirectory) {
401
+ baseMenu.push(
402
+ { id: 'separator1', type: 'separator' },
403
+ {
404
+ id: 'preview',
405
+ label: 'Preview',
406
+ icon: 'eye',
407
+ action: () => this.previewFile(item.path)
408
+ },
409
+ {
410
+ id: 'download',
411
+ label: 'Download',
412
+ icon: 'download',
413
+ action: () => this.downloadFile(item.path)
414
+ }
415
+ )
416
+ }
417
+
418
+ baseMenu.push(
419
+ { id: 'separator2', type: 'separator' },
420
+ {
421
+ id: 'rename',
422
+ label: 'Rename',
423
+ icon: 'edit',
424
+ action: () => this.renameItem(item.path)
425
+ },
426
+ {
427
+ id: 'delete',
428
+ label: 'Delete',
429
+ icon: 'trash',
430
+ action: () => this.deleteItem(item.path, item.isDirectory)
431
+ }
432
+ )
433
+
434
+ return baseMenu
435
+ }
436
+
437
+ private getThumbnailUrl(item: FileSystemItem): string | undefined {
438
+ if (item.type === 'file' && this.isImageFile(item.name)) {
439
+ // Return thumbnail URL for image files
440
+ return `/api/thumbnail?path=${encodeURIComponent(item.path)}`
441
+ }
442
+ return undefined
443
+ }
444
+
445
+ private getFileIcon(item: FileSystemItem): string {
446
+ if (item.isDirectory) return 'folder'
447
+
448
+ const ext = item.name.split('.').pop()?.toLowerCase()
449
+ const iconMap: Record<string, string> = {
450
+ // Images
451
+ 'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'svg': '🖼️',
452
+ // Documents
453
+ 'txt': '📄', 'md': '📝', 'pdf': '📕', 'doc': '📘', 'docx': '📘',
454
+ // Code
455
+ 'js': '📜', 'ts': '📜', 'tsx': '📜', 'jsx': '📜', 'json': '📋',
456
+ // Other
457
+ 'zip': '📦', 'tar': '📦', 'gz': '📦'
458
+ }
459
+
460
+ return iconMap[ext || ''] || '📄'
461
+ }
462
+
463
+ private isImageFile(filename: string): boolean {
464
+ const ext = filename.split('.').pop()?.toLowerCase()
465
+ return ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(ext || '')
466
+ }
467
+
468
+ private async openFile(path: string) {
469
+ // Implementation for opening files
470
+ }
471
+
472
+ private async previewFile(path: string) {
473
+ // Implementation for file preview
474
+ }
475
+
476
+ private async downloadFile(path: string) {
477
+ // Implementation for file download
478
+ }
479
+
480
+ private async renameItem(path: string) {
481
+ // Implementation for renaming items
482
+ }
483
+
484
+ private async deleteItem(path: string, isDirectory: boolean) {
485
+ await this.bridge.deleteItem(path, isDirectory)
486
+ // Refresh the list
487
+ // Could trigger parent to reload
488
+ }
489
+ }
490
+ ```
491
+
492
+ ## Component Layout
493
+
494
+ ### FileBrowser Component Structure
495
+ ```typescript
496
+ // Main FileBrowser component that combines tree and list
497
+ export const FileBrowser: React.FC<FileBrowserProps> = observer(({
498
+ fileSystem,
499
+ initialPath = '/',
500
+ className
501
+ }) => {
502
+ const [model] = useState(() => new FileBrowserModel(fileSystem))
503
+
504
+ useEffect(() => {
505
+ model.setInitialPath(initialPath)
506
+ }, [initialPath])
507
+
508
+ return (
509
+ <div className={cn("flex h-full w-full", className)}>
510
+ {/* Left Panel - Tree Navigation (Directories Only) */}
511
+ <div className="w-80 border-r bg-background">
512
+ <div className="p-2 border-b bg-muted/10">
513
+ <h3 className="text-sm font-medium">Folders</h3>
514
+ </div>
515
+ <Tree
516
+ provider={model.treeProvider}
517
+ className="h-full"
518
+ />
519
+ </div>
520
+
521
+ {/* Right Panel - Content List (All Items) */}
522
+ <div className="flex-1 flex flex-col min-w-0">
523
+ <div className="p-2 border-b bg-muted/10 flex items-center justify-between">
524
+ <div className="flex items-center gap-2">
525
+ <h3 className="text-sm font-medium">Contents</h3>
526
+ <Badge variant="outline" className="text-xs">
527
+ {model.currentPath}
528
+ </Badge>
529
+ </div>
530
+ <ViewTypeSelector
531
+ viewTypes={model.listProvider.getSupportedViewTypes()}
532
+ currentViewType={model.listModel.currentViewType}
533
+ onViewTypeChange={(viewType) => model.listModel.setViewType(viewType)}
534
+ />
535
+ </div>
536
+ <ListItems
537
+ provider={model.listProvider}
538
+ className="flex-1"
539
+ />
540
+ </div>
541
+ </div>
542
+ )
543
+ })
544
+ ```
545
+
546
+ ## State Synchronization Flow
547
+
548
+ ### Tree Selection → List Update
549
+ 1. User clicks directory in tree
550
+ 2. TreeProvider calls `onSelectionChange(path)`
551
+ 3. FileBrowserModel.handleTreeSelection():
552
+ - Updates `currentPath` and `selectedPath`
553
+ - Calls `listModel.setPath(path)`
554
+ - Triggers list refresh for new directory
555
+
556
+ ### List Navigation → Tree Update
557
+ 1. User double-clicks directory in list
558
+ 2. ListProvider calls `onNavigation(path)`
559
+ 3. FileBrowserModel.handleListNavigation():
560
+ - Updates `currentPath` and `selectedPath`
561
+ - Calls `treeModel.selectPath(path)`
562
+ - Calls `treeModel.expandToPath(path)` to make selection visible
563
+
564
+ ### File Operations Coordination
565
+ 1. User performs file operation (delete, rename, etc.)
566
+ 2. Provider executes operation via FileSystemBridge
567
+ 3. Provider triggers refresh of affected components
568
+ 4. Both tree and list update to reflect changes
569
+
570
+ ## Integration with @anymux/file-system
571
+
572
+ The FileBrowser is designed to work with any implementation of `@anymux/file-system`:
573
+
574
+ ```typescript
575
+ // Usage with different file systems
576
+ import { MemoryFileSystem } from '@anymux/file-system/mem-fs'
577
+ import { NodeFileSystem } from '@anymux/file-system/node-fs'
578
+ import { BrowserFileSystem } from '@anymux/file-system/browser-fs'
579
+
580
+ // Memory file system for demos
581
+ const memFs = new MemoryFileSystem()
582
+ const fileBrowser1 = <FileBrowser fileSystem={memFs} />
583
+
584
+ // Node.js file system for server
585
+ const nodeFs = new NodeFileSystem()
586
+ const fileBrowser2 = <FileBrowser fileSystem={nodeFs} />
587
+
588
+ // Browser file system for web
589
+ const browserFs = new BrowserFileSystem()
590
+ const fileBrowser3 = <FileBrowser fileSystem={browserFs} />
591
+ ```
592
+
593
+ ## File System Adapter Pattern
594
+
595
+ The FileSystemBridge acts as an adapter between the generic `@anymux/file-system` interface and the specific data structures needed by TreeComponent and ListItemsComponent. This allows:
596
+
597
+ 1. **File System Independence**: Any compatible file system can be used
598
+ 2. **Data Structure Mapping**: Converts Dirent/Stats to component-expected formats
599
+ 3. **Error Handling**: Graceful handling of permission errors and missing files
600
+ 4. **Performance Optimization**: Caching and batch operations where beneficial
601
+ 5. **Type Safety**: Strong typing throughout the component chain
602
+
603
+ ## Usage in apps/web/list-samples
604
+
605
+ The FileBrowser will be integrated into the list-samples page as a demonstration:
606
+
607
+ ```typescript
608
+ // In apps/web/app/(fixed)/list-samples/page.tsx
609
+ import { FileBrowser } from '@anymux/fs-ui'
610
+ import { MemoryFileSystem } from '@anymux/file-system/mem-fs'
611
+
612
+ const ListSamplesPage = () => {
613
+ const [memFs] = useState(() => {
614
+ const fs = new MemoryFileSystem()
615
+ // Populate with sample data
616
+ return fs
617
+ })
618
+
619
+ return (
620
+ <Tabs defaultValue="list-only">
621
+ <TabsList>
622
+ <TabsTrigger value="list-only">List Component Only</TabsTrigger>
623
+ <TabsTrigger value="file-browser">File Browser (Tree + List)</TabsTrigger>
624
+ </TabsList>
625
+
626
+ <TabsContent value="list-only">
627
+ {/* Existing ListItems demo */}
628
+ </TabsContent>
629
+
630
+ <TabsContent value="file-browser">
631
+ <FileBrowser fileSystem={memFs} />
632
+ </TabsContent>
633
+ </Tabs>
634
+ )
635
+ }
636
+ ```
637
+
638
+ This architecture provides:
639
+ - ✅ **Component Reuse**: Leverages existing TreeComponent and ListItemsComponent
640
+ - ✅ **File System Agnostic**: Works with any `@anymux/file-system` implementation
641
+ - ✅ **Synchronized Views**: Tree and list maintain consistent state
642
+ - ✅ **Directory-Only Tree**: Tree shows folders only for navigation
643
+ - ✅ **Full Content List**: List shows all items with rich interactions
644
+ - ✅ **MobX Reactive**: All state changes are reactive and efficient
645
+ - ✅ **Provider Pattern**: Clean separation of concerns with bridges/adapters
@@ -0,0 +1,71 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import {
3
+ AlertDialog,
4
+ AlertDialogContent,
5
+ AlertDialogHeader,
6
+ AlertDialogTitle,
7
+ AlertDialogDescription,
8
+ AlertDialogFooter,
9
+ AlertDialogCancel,
10
+ } from '@anymux/ui/components/alert-dialog';
11
+ import { Button } from '@anymux/ui/components/button';
12
+ import { Input } from '@anymux/ui/components/input';
13
+
14
+ interface CreateItemDialogProps {
15
+ type: 'file' | 'folder';
16
+ onConfirm: (name: string) => void;
17
+ onCancel: () => void;
18
+ }
19
+
20
+ export const CreateItemDialog: React.FC<CreateItemDialogProps> = ({
21
+ type,
22
+ onConfirm,
23
+ onCancel,
24
+ }) => {
25
+ const [name, setName] = useState('');
26
+ const inputRef = useRef<HTMLInputElement>(null);
27
+
28
+ useEffect(() => {
29
+ requestAnimationFrame(() => inputRef.current?.focus());
30
+ }, []);
31
+
32
+ const handleSubmit = () => {
33
+ const trimmed = name.trim();
34
+ if (trimmed) {
35
+ onConfirm(trimmed);
36
+ }
37
+ };
38
+
39
+ const handleKeyDown = (e: React.KeyboardEvent) => {
40
+ if (e.key === 'Enter') {
41
+ e.preventDefault();
42
+ handleSubmit();
43
+ }
44
+ };
45
+
46
+ return (
47
+ <AlertDialog open onOpenChange={(open) => { if (!open) onCancel(); }}>
48
+ <AlertDialogContent>
49
+ <AlertDialogHeader>
50
+ <AlertDialogTitle>New {type === 'folder' ? 'Folder' : 'File'}</AlertDialogTitle>
51
+ <AlertDialogDescription>
52
+ Enter a name for the new {type === 'folder' ? 'folder' : 'file'}.
53
+ </AlertDialogDescription>
54
+ </AlertDialogHeader>
55
+ <Input
56
+ ref={inputRef}
57
+ value={name}
58
+ onChange={(e) => setName(e.target.value)}
59
+ onKeyDown={handleKeyDown}
60
+ placeholder={type === 'folder' ? 'Folder name' : 'File name'}
61
+ />
62
+ <AlertDialogFooter>
63
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
64
+ <Button onClick={handleSubmit} disabled={!name.trim()}>
65
+ Create
66
+ </Button>
67
+ </AlertDialogFooter>
68
+ </AlertDialogContent>
69
+ </AlertDialog>
70
+ );
71
+ };