@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,227 @@
1
+ import React, { useRef, useEffect } from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { X, ChevronLeft, ChevronRight } from 'lucide-react';
4
+ import { ResponsiveLayoutManagerModel } from '../../models/ResponsiveLayoutManagerModel';
5
+ import { useGestures } from '../../utils/gestures';
6
+
7
+ // Utility for class name concatenation
8
+ const cn = (...classes: (string | undefined | boolean)[]) => {
9
+ return classes.filter(Boolean).join(' ');
10
+ };
11
+
12
+ interface MobileNavigationProps {
13
+ responsiveLayout: ResponsiveLayoutManagerModel;
14
+ children: React.ReactNode;
15
+ className?: string;
16
+ }
17
+
18
+ export const MobileNavigation = observer(({
19
+ responsiveLayout,
20
+ children,
21
+ className
22
+ }: MobileNavigationProps) => {
23
+ const overlayRef = useRef<HTMLDivElement>(null);
24
+ const panelRef = useRef<HTMLDivElement>(null);
25
+
26
+ // Setup gesture handling
27
+ useGestures(
28
+ overlayRef as React.RefObject<HTMLElement>,
29
+ {
30
+ onSwipeDown: () => {
31
+ if (responsiveLayout.isMobileMenuOpen) {
32
+ responsiveLayout.hideMobilePanel();
33
+ }
34
+ },
35
+ onSwipeLeft: () => {
36
+ if (responsiveLayout.activePanel === 'left') {
37
+ responsiveLayout.showMobilePanel('right');
38
+ } else {
39
+ responsiveLayout.hideMobilePanel();
40
+ }
41
+ },
42
+ onSwipeRight: () => {
43
+ if (responsiveLayout.activePanel === 'right') {
44
+ responsiveLayout.showMobilePanel('left');
45
+ }
46
+ }
47
+ }
48
+ );
49
+
50
+ // Focus management
51
+ useEffect(() => {
52
+ if (responsiveLayout.isMobileMenuOpen && panelRef.current) {
53
+ // Focus first focusable element in panel
54
+ const focusableElements = panelRef.current.querySelectorAll(
55
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
56
+ );
57
+ const firstFocusable = focusableElements[0] as HTMLElement;
58
+ firstFocusable?.focus();
59
+ }
60
+ }, [responsiveLayout.isMobileMenuOpen, responsiveLayout.activePanel]);
61
+
62
+ // Handle escape key
63
+ useEffect(() => {
64
+ const handleEscape = (e: KeyboardEvent) => {
65
+ if (e.key === 'Escape' && responsiveLayout.isMobileMenuOpen) {
66
+ responsiveLayout.hideMobilePanel();
67
+ }
68
+ };
69
+
70
+ if (responsiveLayout.trapFocus) {
71
+ document.addEventListener('keydown', handleEscape);
72
+ return () => document.removeEventListener('keydown', handleEscape);
73
+ }
74
+ }, [responsiveLayout.trapFocus, responsiveLayout.isMobileMenuOpen]);
75
+
76
+ // Prevent body scroll when overlay is open
77
+ useEffect(() => {
78
+ if (responsiveLayout.isMobileMenuOpen) {
79
+ document.body.style.overflow = 'hidden';
80
+ } else {
81
+ document.body.style.overflow = '';
82
+ }
83
+
84
+ return () => {
85
+ document.body.style.overflow = '';
86
+ };
87
+ }, [responsiveLayout.isMobileMenuOpen]);
88
+
89
+ if (!responsiveLayout.isMobile) {
90
+ return null;
91
+ }
92
+
93
+ return (
94
+ <>
95
+ {/* Mobile Navigation Button */}
96
+ <button
97
+ onClick={() => responsiveLayout.toggleMobileMenu()}
98
+ className={cn(
99
+ // Touch-friendly size (44px minimum)
100
+ 'w-11 h-11 flex items-center justify-center',
101
+ 'bg-background border border-border rounded-md',
102
+ 'text-foreground hover:bg-accent hover:text-accent-foreground',
103
+ 'transition-all duration-200',
104
+ 'active:scale-95', // Touch feedback
105
+ responsiveLayout.isMobileMenuOpen && 'bg-accent text-accent-foreground'
106
+ )}
107
+ aria-label={responsiveLayout.isMobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}
108
+ aria-expanded={responsiveLayout.isMobileMenuOpen}
109
+ >
110
+ {responsiveLayout.isMobileMenuOpen ? (
111
+ <X className="w-5 h-5" />
112
+ ) : (
113
+ <div className="flex flex-col space-y-1">
114
+ <div className="w-4 h-0.5 bg-current" />
115
+ <div className="w-4 h-0.5 bg-current" />
116
+ <div className="w-4 h-0.5 bg-current" />
117
+ </div>
118
+ )}
119
+ </button>
120
+
121
+ {/* Mobile Overlay */}
122
+ {responsiveLayout.isMobileMenuOpen && (
123
+ <div
124
+ ref={overlayRef}
125
+ className={cn(
126
+ 'fixed inset-0 z-50',
127
+ 'bg-background/80 backdrop-blur-sm',
128
+ 'transition-all duration-300',
129
+ responsiveLayout.isAnimating && 'transition-all duration-300'
130
+ )}
131
+ onClick={(e) => {
132
+ // Close on backdrop click
133
+ if (e.target === e.currentTarget) {
134
+ responsiveLayout.hideMobilePanel();
135
+ }
136
+ }}
137
+ >
138
+ {/* Panel Container */}
139
+ <div
140
+ ref={panelRef}
141
+ className={cn(
142
+ 'absolute top-0 bottom-0 w-80 max-w-[85vw]',
143
+ 'bg-background border-r border-border',
144
+ 'shadow-xl',
145
+ 'transition-transform duration-300 ease-out',
146
+ 'overflow-y-auto',
147
+ // Panel positioning and animation
148
+ responsiveLayout.activePanel === 'left' && 'left-0 transform-gpu translate-x-0',
149
+ responsiveLayout.activePanel === 'right' && 'right-0 transform-gpu translate-x-0',
150
+ !responsiveLayout.activePanel && 'translate-x-full',
151
+ className
152
+ )}
153
+ role="dialog"
154
+ aria-modal="true"
155
+ aria-label={`${responsiveLayout.activePanel} navigation panel`}
156
+ >
157
+ {/* Panel Header */}
158
+ <div className="flex items-center justify-between p-4 border-b border-border">
159
+ <h2 className="text-lg font-semibold">
160
+ {responsiveLayout.activePanel === 'left' ? 'Navigation' : 'Content'}
161
+ </h2>
162
+
163
+ <div className="flex items-center space-x-2">
164
+ {/* Panel switcher */}
165
+ {responsiveLayout.activePanel === 'left' && (
166
+ <button
167
+ onClick={() => responsiveLayout.showMobilePanel('right')}
168
+ className={cn(
169
+ 'w-9 h-9 flex items-center justify-center',
170
+ 'bg-muted hover:bg-muted-foreground/10 rounded-md',
171
+ 'transition-colors duration-200'
172
+ )}
173
+ aria-label="Switch to content panel"
174
+ >
175
+ <ChevronRight className="w-4 h-4" />
176
+ </button>
177
+ )}
178
+
179
+ {responsiveLayout.activePanel === 'right' && (
180
+ <button
181
+ onClick={() => responsiveLayout.showMobilePanel('left')}
182
+ className={cn(
183
+ 'w-9 h-9 flex items-center justify-center',
184
+ 'bg-muted hover:bg-muted-foreground/10 rounded-md',
185
+ 'transition-colors duration-200'
186
+ )}
187
+ aria-label="Switch to navigation panel"
188
+ >
189
+ <ChevronLeft className="w-4 h-4" />
190
+ </button>
191
+ )}
192
+
193
+ {/* Close button */}
194
+ <button
195
+ onClick={() => responsiveLayout.hideMobilePanel()}
196
+ className={cn(
197
+ 'w-9 h-9 flex items-center justify-center',
198
+ 'bg-muted hover:bg-muted-foreground/10 rounded-md',
199
+ 'transition-colors duration-200'
200
+ )}
201
+ aria-label="Close panel"
202
+ >
203
+ <X className="w-4 h-4" />
204
+ </button>
205
+ </div>
206
+ </div>
207
+
208
+ {/* Panel Content */}
209
+ <div className="flex-1">
210
+ {children}
211
+ </div>
212
+
213
+ {/* Swipe Indicator */}
214
+ <div className="p-4 border-t border-border">
215
+ <div className="flex items-center justify-center space-x-1 text-xs text-muted-foreground">
216
+ <div className="w-6 h-1 bg-muted rounded-full" />
217
+ <span>Swipe to navigate</span>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ )}
223
+ </>
224
+ );
225
+ });
226
+
227
+ MobileNavigation.displayName = 'MobileNavigation';
@@ -0,0 +1,171 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { ArrowLeft, ArrowRight, ArrowUp } from 'lucide-react';
4
+ import { cn } from '../../../lib/utils';
5
+
6
+ export interface NavigationButtonsProps {
7
+ canGoBack?: boolean;
8
+ canGoForward?: boolean;
9
+ canGoUp?: boolean;
10
+ onGoBack?: () => void;
11
+ onGoForward?: () => void;
12
+ onGoUp?: () => void;
13
+ className?: string;
14
+ variant?: 'default' | 'compact';
15
+ showLabels?: boolean;
16
+ disabled?: boolean;
17
+ }
18
+
19
+ interface NavigationButtonProps {
20
+ icon: React.ReactNode;
21
+ label: string;
22
+ onClick?: () => void;
23
+ disabled?: boolean;
24
+ shortcut?: string;
25
+ className?: string;
26
+ showLabel?: boolean;
27
+ }
28
+
29
+ const NavigationButton: React.FC<NavigationButtonProps> = ({
30
+ icon,
31
+ label,
32
+ onClick,
33
+ disabled = false,
34
+ shortcut,
35
+ className,
36
+ showLabel = false,
37
+ }) => {
38
+ const tooltipText = shortcut ? `${label} (${shortcut})` : label;
39
+
40
+ return (
41
+ <button
42
+ onClick={onClick}
43
+ disabled={disabled}
44
+ className={cn(
45
+ 'flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors',
46
+ 'hover:bg-muted focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
47
+ 'disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent',
48
+ className
49
+ )}
50
+ title={tooltipText}
51
+ aria-label={tooltipText}
52
+ >
53
+ {icon}
54
+ {showLabel && <span className="hidden sm:inline">{label}</span>}
55
+ </button>
56
+ );
57
+ };
58
+
59
+ const NavigationButtons: React.FC<NavigationButtonsProps> = observer(({
60
+ canGoBack = false,
61
+ canGoForward = false,
62
+ canGoUp = false,
63
+ onGoBack,
64
+ onGoForward,
65
+ onGoUp,
66
+ className,
67
+ variant = 'default',
68
+ showLabels = false,
69
+ disabled = false,
70
+ }) => {
71
+ // Handle keyboard shortcuts
72
+ React.useEffect(() => {
73
+ const handleKeyDown = (e: KeyboardEvent) => {
74
+ // Only handle shortcuts when not typing in inputs
75
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
76
+ return;
77
+ }
78
+
79
+ // Check for Alt key combinations
80
+ if (e.altKey) {
81
+ switch (e.key) {
82
+ case 'ArrowLeft':
83
+ e.preventDefault();
84
+ if (canGoBack && onGoBack) {
85
+ onGoBack();
86
+ }
87
+ break;
88
+ case 'ArrowRight':
89
+ e.preventDefault();
90
+ if (canGoForward && onGoForward) {
91
+ onGoForward();
92
+ }
93
+ break;
94
+ case 'ArrowUp':
95
+ e.preventDefault();
96
+ if (canGoUp && onGoUp) {
97
+ onGoUp();
98
+ }
99
+ break;
100
+ }
101
+ }
102
+
103
+ // Browser-style shortcuts
104
+ if ((e.ctrlKey || e.metaKey) && e.key === '[') {
105
+ e.preventDefault();
106
+ if (canGoBack && onGoBack) {
107
+ onGoBack();
108
+ }
109
+ }
110
+
111
+ if ((e.ctrlKey || e.metaKey) && e.key === ']') {
112
+ e.preventDefault();
113
+ if (canGoForward && onGoForward) {
114
+ onGoForward();
115
+ }
116
+ }
117
+ };
118
+
119
+ window.addEventListener('keydown', handleKeyDown);
120
+ return () => window.removeEventListener('keydown', handleKeyDown);
121
+ }, [canGoBack, canGoForward, canGoUp, onGoBack, onGoForward, onGoUp]);
122
+
123
+ const isCompact = variant === 'compact';
124
+ const buttonSize = isCompact ? 'w-8 h-8 p-1' : 'px-3 py-2';
125
+
126
+ return (
127
+ <div
128
+ className={cn(
129
+ 'flex items-center',
130
+ isCompact ? 'gap-1' : 'gap-2',
131
+ className
132
+ )}
133
+ role="toolbar"
134
+ aria-label="Navigation buttons"
135
+ >
136
+ <NavigationButton
137
+ icon={<ArrowLeft className={isCompact ? 'w-4 h-4' : 'w-4 h-4'} />}
138
+ label="Back"
139
+ onClick={onGoBack}
140
+ disabled={disabled || !canGoBack}
141
+ shortcut="Alt+←"
142
+ className={buttonSize}
143
+ showLabel={showLabels && !isCompact}
144
+ />
145
+
146
+ <NavigationButton
147
+ icon={<ArrowRight className={isCompact ? 'w-4 h-4' : 'w-4 h-4'} />}
148
+ label="Forward"
149
+ onClick={onGoForward}
150
+ disabled={disabled || !canGoForward}
151
+ shortcut="Alt+→"
152
+ className={buttonSize}
153
+ showLabel={showLabels && !isCompact}
154
+ />
155
+
156
+ <NavigationButton
157
+ icon={<ArrowUp className={isCompact ? 'w-4 h-4' : 'w-4 h-4'} />}
158
+ label="Up"
159
+ onClick={onGoUp}
160
+ disabled={disabled || !canGoUp}
161
+ shortcut="Alt+↑"
162
+ className={buttonSize}
163
+ showLabel={showLabels && !isCompact}
164
+ />
165
+ </div>
166
+ );
167
+ });
168
+
169
+ NavigationButtons.displayName = 'NavigationButtons';
170
+
171
+ export default NavigationButtons;
@@ -0,0 +1,116 @@
1
+ import React, { Component, ReactNode } from 'react';
2
+ import { AlertTriangle } from 'lucide-react';
3
+ import { cn } from '../../../lib/utils';
4
+
5
+ export interface ErrorBoundaryProps {
6
+ children: ReactNode;
7
+ fallback?: (error: Error, errorInfo: string, onReset: () => void) => ReactNode;
8
+ onError?: (error: Error, errorInfo: string) => void;
9
+ className?: string;
10
+ }
11
+
12
+ interface ErrorBoundaryState {
13
+ hasError: boolean;
14
+ error: Error | null;
15
+ errorInfo: string | null;
16
+ }
17
+
18
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
19
+ constructor(props: ErrorBoundaryProps) {
20
+ super(props);
21
+ this.state = {
22
+ hasError: false,
23
+ error: null,
24
+ errorInfo: null,
25
+ };
26
+ }
27
+
28
+ static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
29
+ return {
30
+ hasError: true,
31
+ error,
32
+ };
33
+ }
34
+
35
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
36
+ const errorInfoString = errorInfo.componentStack || '';
37
+
38
+ this.setState({
39
+ errorInfo: errorInfoString,
40
+ });
41
+
42
+ // Call optional error reporting callback
43
+ this.props.onError?.(error, errorInfoString);
44
+
45
+ // Log error for debugging
46
+ console.error('FileBrowser Error Boundary caught an error:', error);
47
+ console.error('Error Info:', errorInfo);
48
+ }
49
+
50
+ handleReset = () => {
51
+ this.setState({
52
+ hasError: false,
53
+ error: null,
54
+ errorInfo: null,
55
+ });
56
+ };
57
+
58
+ render() {
59
+ if (this.state.hasError) {
60
+ const { fallback, className } = this.props;
61
+ const { error, errorInfo } = this.state;
62
+
63
+ // Use custom fallback if provided
64
+ if (fallback && error) {
65
+ return fallback(error, errorInfo || '', this.handleReset);
66
+ }
67
+
68
+ // Default error UI
69
+ return (
70
+ <div
71
+ className={cn(
72
+ 'flex flex-col items-center justify-center p-8 text-center border border-destructive/20 rounded-lg bg-destructive/5',
73
+ className
74
+ )}
75
+ role="alert"
76
+ aria-live="assertive"
77
+ >
78
+ <AlertTriangle
79
+ className="w-12 h-12 text-destructive mb-4"
80
+ aria-hidden="true"
81
+ />
82
+
83
+ <h2 className="text-lg font-semibold text-destructive mb-2">
84
+ Something went wrong
85
+ </h2>
86
+
87
+ <p className="text-sm text-muted-foreground mb-4 max-w-md">
88
+ {error?.message || 'An unexpected error occurred while rendering the file browser.'}
89
+ </p>
90
+
91
+ <div className="flex gap-2">
92
+ <button
93
+ onClick={this.handleReset}
94
+ 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"
95
+ >
96
+ Try Again
97
+ </button>
98
+
99
+ <button
100
+ onClick={() => window.location.reload()}
101
+ 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"
102
+ >
103
+ Reload Page
104
+ </button>
105
+ </div>
106
+
107
+ {/* Error details removed to prevent build issues */}
108
+ </div>
109
+ );
110
+ }
111
+
112
+ return this.props.children;
113
+ }
114
+ }
115
+
116
+ export default ErrorBoundary;
@@ -0,0 +1,195 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { cn } from '../../../lib/utils';
4
+ import { FileBrowserItem as FileBrowserItemType } from '../../types/FileBrowserTypes';
5
+ import FileIcon from './FileIcon';
6
+
7
+ export interface FileBrowserItemProps {
8
+ item: FileBrowserItemType;
9
+ isSelected?: boolean;
10
+ isHovered?: boolean;
11
+ showDetails?: boolean;
12
+ showIcon?: boolean;
13
+ showSize?: boolean;
14
+ showDate?: boolean;
15
+ iconSize?: 'sm' | 'md' | 'lg';
16
+ layout?: 'horizontal' | 'vertical';
17
+ onClick?: (item: FileBrowserItemType, event: React.MouseEvent) => void;
18
+ onDoubleClick?: (item: FileBrowserItemType, event: React.MouseEvent) => void;
19
+ onContextMenu?: (item: FileBrowserItemType, event: React.MouseEvent) => void;
20
+ onKeyDown?: (item: FileBrowserItemType, event: React.KeyboardEvent) => void;
21
+ className?: string;
22
+ style?: React.CSSProperties;
23
+ children?: React.ReactNode;
24
+ }
25
+
26
+ const iconSizeClasses = {
27
+ sm: 'w-4 h-4',
28
+ md: 'w-6 h-6',
29
+ lg: 'w-8 h-8',
30
+ };
31
+
32
+ const FileBrowserItemComponent: React.FC<FileBrowserItemProps> = observer(({
33
+ item,
34
+ isSelected = false,
35
+ isHovered = false,
36
+ showDetails = false,
37
+ showIcon = true,
38
+ showSize = false,
39
+ showDate = false,
40
+ iconSize = 'md',
41
+ layout = 'horizontal',
42
+ onClick,
43
+ onDoubleClick,
44
+ onContextMenu,
45
+ onKeyDown,
46
+ className,
47
+ style,
48
+ children,
49
+ }) => {
50
+ const handleClick = (event: React.MouseEvent) => {
51
+ onClick?.(item, event);
52
+ };
53
+
54
+ const handleDoubleClick = (event: React.MouseEvent) => {
55
+ onDoubleClick?.(item, event);
56
+ };
57
+
58
+ const handleContextMenu = (event: React.MouseEvent) => {
59
+ event.preventDefault();
60
+ onContextMenu?.(item, event);
61
+ };
62
+
63
+ const handleKeyDown = (event: React.KeyboardEvent) => {
64
+ if (event.key === 'Enter' || event.key === ' ') {
65
+ event.preventDefault();
66
+ onClick?.(item, event as any);
67
+ }
68
+ onKeyDown?.(item, event);
69
+ };
70
+
71
+ const formatFileSize = (bytes?: number): string => {
72
+ if (!bytes || bytes === 0) return '';
73
+
74
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
75
+ let size = bytes;
76
+ let unitIndex = 0;
77
+
78
+ while (size >= 1024 && unitIndex < units.length - 1) {
79
+ size /= 1024;
80
+ unitIndex++;
81
+ }
82
+
83
+ return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
84
+ };
85
+
86
+ const formatDate = (date?: Date | string): string => {
87
+ if (!date) return '';
88
+
89
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
90
+ return dateObj.toLocaleDateString(undefined, {
91
+ year: 'numeric',
92
+ month: 'short',
93
+ day: 'numeric',
94
+ });
95
+ };
96
+
97
+ const isVertical = layout === 'vertical';
98
+
99
+ return (
100
+ <div
101
+ className={cn(
102
+ 'group relative flex items-center gap-2 p-2 rounded-md cursor-pointer transition-colors',
103
+ 'hover:bg-muted/50 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
104
+ isSelected && 'bg-primary/10 border border-primary/20',
105
+ isHovered && !isSelected && 'bg-muted/30',
106
+ isVertical && 'flex-col text-center',
107
+ className
108
+ )}
109
+ style={style}
110
+ onClick={handleClick}
111
+ onDoubleClick={handleDoubleClick}
112
+ onContextMenu={handleContextMenu}
113
+ onKeyDown={handleKeyDown}
114
+ tabIndex={0}
115
+ role="button"
116
+ aria-label={`${item.type === 'directory' ? 'Folder' : 'File'}: ${item.name}`}
117
+ aria-selected={isSelected}
118
+ >
119
+ {/* Icon */}
120
+ {showIcon && (
121
+ <div className={cn('flex-shrink-0', isVertical && 'mb-1')}>
122
+ <FileIcon
123
+ item={item}
124
+ size={iconSize}
125
+ className={iconSizeClasses[iconSize]}
126
+ />
127
+ </div>
128
+ )}
129
+
130
+ {/* Content */}
131
+ <div className={cn(
132
+ 'flex-1 min-w-0',
133
+ isVertical ? 'text-center' : 'flex items-center justify-between'
134
+ )}>
135
+ {/* Name and details */}
136
+ <div className={cn('min-w-0', !isVertical && 'flex-1')}>
137
+ <div className={cn(
138
+ 'font-medium text-sm truncate',
139
+ item.type === 'directory' && 'text-primary',
140
+ isVertical && 'text-center'
141
+ )} title={item.name}>
142
+ {item.name}
143
+ </div>
144
+
145
+ {showDetails && (
146
+ <div className={cn(
147
+ 'text-xs text-muted-foreground mt-0.5',
148
+ isVertical && 'text-center'
149
+ )}>
150
+ {item.type === 'directory' ? 'Folder' : 'File'}
151
+ </div>
152
+ )}
153
+ </div>
154
+
155
+ {/* Size and date */}
156
+ {(showSize || showDate) && !isVertical && (
157
+ <div className="flex-shrink-0 text-xs text-muted-foreground ml-2">
158
+ <div className="flex flex-col items-end gap-0.5">
159
+ {showSize && item.size !== undefined && (
160
+ <span>{formatFileSize(item.size)}</span>
161
+ )}
162
+ {showDate && item.lastModified && (
163
+ <span>{formatDate(item.lastModified)}</span>
164
+ )}
165
+ </div>
166
+ </div>
167
+ )}
168
+
169
+ {/* Vertical layout size/date */}
170
+ {(showSize || showDate) && isVertical && (
171
+ <div className="text-xs text-muted-foreground mt-1 text-center">
172
+ {showSize && item.size !== undefined && (
173
+ <div>{formatFileSize(item.size)}</div>
174
+ )}
175
+ {showDate && item.lastModified && (
176
+ <div>{formatDate(item.lastModified)}</div>
177
+ )}
178
+ </div>
179
+ )}
180
+ </div>
181
+
182
+ {/* Custom children */}
183
+ {children}
184
+
185
+ {/* Selection indicator */}
186
+ {isSelected && (
187
+ <div className="absolute inset-0 border-2 border-primary rounded-md pointer-events-none" />
188
+ )}
189
+ </div>
190
+ );
191
+ });
192
+
193
+ FileBrowserItemComponent.displayName = 'FileBrowserItem';
194
+
195
+ export default FileBrowserItemComponent;