@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,344 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import type { ListItemsModel } from '../models/ListItemsModel';
4
+ import type { ListItemData } from '../types/ListTypes';
5
+ import type { MasonryItemPosition } from '../utils/MasonryLayoutEngine';
6
+
7
+ // AICODE-NOTE: Virtualized masonry view that only renders visible items for performance with large datasets
8
+ // Uses viewport-based culling to handle thousands of items efficiently
9
+
10
+ interface VirtualizedMasonryViewProps {
11
+ model: ListItemsModel;
12
+ items: ListItemData[];
13
+ containerWidth: number;
14
+ containerHeight: number;
15
+ isHorizontal?: boolean; // true for horizontal masonry, false for vertical
16
+ overscanCount?: number; // Extra items to render outside viewport
17
+ className?: string;
18
+ }
19
+
20
+ interface MasonryItemProps {
21
+ item: ListItemData;
22
+ position: MasonryItemPosition;
23
+ model: ListItemsModel;
24
+ onClick: (item: ListItemData) => void;
25
+ onDoubleClick: (item: ListItemData) => void;
26
+ }
27
+
28
+ const MasonryItem: React.FC<MasonryItemProps> = observer(({ item, position, model, onClick, onDoubleClick }) => {
29
+ const isSelected = model.isItemSelected(item.id);
30
+ const isFocused = model.focusedItem === item.id;
31
+ const thumbnailUrl = item.thumbnailUrl ? model.resolveThumbnailUrl(item) : undefined;
32
+
33
+ const handleClick = (e: React.MouseEvent) => {
34
+ e.preventDefault();
35
+ onClick(item);
36
+ };
37
+
38
+ const handleDoubleClick = (e: React.MouseEvent) => {
39
+ e.preventDefault();
40
+ onDoubleClick(item);
41
+ };
42
+
43
+ const debugStyles = model.debugVisualization ? {
44
+ border: '2px solid lightcyan',
45
+ boxSizing: 'border-box' as const
46
+ } : {};
47
+
48
+ return (
49
+ <div
50
+ style={{
51
+ position: 'absolute',
52
+ left: position.x,
53
+ top: position.y,
54
+ width: position.width,
55
+ height: position.height,
56
+ cursor: 'pointer',
57
+ backgroundColor: isSelected ? 'rgba(0, 123, 255, 0.1)' : 'transparent',
58
+ borderRadius: model.debugVisualization ? '0px' : '2px',
59
+ overflow: 'hidden',
60
+ transition: 'background-color 0.15s ease, box-shadow 0.15s ease',
61
+ boxShadow: isSelected
62
+ ? '0 0 0 2px rgba(0, 123, 255, 0.5)'
63
+ : 'none',
64
+ ...debugStyles
65
+ }}
66
+ onClick={handleClick}
67
+ onDoubleClick={handleDoubleClick}
68
+ data-item-id={item.id}
69
+ >
70
+ {thumbnailUrl ? (
71
+ <>
72
+ <img
73
+ src={thumbnailUrl}
74
+ alt={item.name}
75
+ style={{
76
+ width: '100%',
77
+ height: '100%',
78
+ objectFit: 'cover',
79
+ display: 'block',
80
+ ...(model.debugVisualization ? { border: '1px solid lightblue' } : {})
81
+ }}
82
+ loading="lazy"
83
+ />
84
+
85
+ {/* Overlay label at the bottom */}
86
+ <div
87
+ style={{
88
+ position: 'absolute',
89
+ bottom: 0,
90
+ left: 0,
91
+ right: 0,
92
+ background: 'linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.3), transparent)',
93
+ color: 'white',
94
+ padding: '12px 8px 8px 8px',
95
+ fontSize: '12px',
96
+ fontWeight: '500',
97
+ textAlign: 'left',
98
+ lineHeight: '1.3',
99
+ maxHeight: '40px',
100
+ overflow: 'hidden',
101
+ textOverflow: 'ellipsis',
102
+ whiteSpace: 'nowrap',
103
+ pointerEvents: 'none',
104
+ ...(model.debugVisualization ? { border: '1px solid yellow' } : {})
105
+ }}
106
+ >
107
+ {item.name}
108
+ </div>
109
+ </>
110
+ ) : (
111
+ // Fallback for items without thumbnails
112
+ <div
113
+ style={{
114
+ width: '100%',
115
+ height: '100%',
116
+ backgroundColor: '#f0f0f0',
117
+ display: 'flex',
118
+ flexDirection: 'column',
119
+ alignItems: 'center',
120
+ justifyContent: 'center',
121
+ fontSize: '24px',
122
+ color: '#888',
123
+ position: 'relative',
124
+ ...(model.debugVisualization ? { border: '1px solid orange' } : {})
125
+ }}
126
+ >
127
+ <div>📄</div>
128
+ {/* Label for non-image items */}
129
+ <div
130
+ style={{
131
+ position: 'absolute',
132
+ bottom: 0,
133
+ left: 0,
134
+ right: 0,
135
+ background: 'rgba(0, 0, 0, 0.6)',
136
+ color: 'white',
137
+ padding: '8px',
138
+ fontSize: '11px',
139
+ fontWeight: '500',
140
+ textAlign: 'center',
141
+ lineHeight: '1.3',
142
+ maxHeight: '32px',
143
+ overflow: 'hidden',
144
+ textOverflow: 'ellipsis',
145
+ whiteSpace: 'nowrap',
146
+ pointerEvents: 'none'
147
+ }}
148
+ >
149
+ {item.name}
150
+ </div>
151
+ </div>
152
+ )}
153
+ </div>
154
+ );
155
+ });
156
+
157
+ export const VirtualizedMasonryView: React.FC<VirtualizedMasonryViewProps> = observer(({
158
+ model,
159
+ items,
160
+ containerWidth,
161
+ containerHeight,
162
+ isHorizontal = false,
163
+ overscanCount = 10,
164
+ className = ''
165
+ }) => {
166
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
167
+ const [scrollTop, setScrollTop] = useState(0);
168
+
169
+ // Update container size in model via effect (not during render)
170
+ useEffect(() => {
171
+ if (containerWidth > 0 && containerHeight > 0) {
172
+ model.updateContainerSize(containerWidth, containerHeight);
173
+ }
174
+ }, [model, containerWidth, containerHeight]);
175
+
176
+ // Calculate layout directly (MobX observer tracks dependencies)
177
+ let masonryLayout: { layout: MasonryItemPosition[]; totalHeight: number } = { layout: [], totalHeight: 0 };
178
+
179
+ if (items.length > 0 && containerWidth > 0 && model.containerSize.width > 0) {
180
+ const result = isHorizontal
181
+ ? model.getHorizontalMasonryLayout()
182
+ : model.getVerticalMasonryLayout();
183
+
184
+ if (result) {
185
+ masonryLayout = result;
186
+ }
187
+ }
188
+
189
+ // Calculate visible items for viewport culling
190
+ let visibleItems: MasonryItemPosition[] = [];
191
+ if (masonryLayout.layout.length > 0) {
192
+ const overscan = overscanCount * 50;
193
+ visibleItems = model.getVisibleMasonryItems(scrollTop, containerHeight, isHorizontal, overscan);
194
+ }
195
+
196
+ // AICODE-NOTE: Handle scroll events with throttling
197
+ const handleScroll = useCallback(() => {
198
+ const container = scrollContainerRef.current;
199
+ if (!container) return;
200
+
201
+ const newScrollTop = container.scrollTop;
202
+ setScrollTop(newScrollTop);
203
+
204
+ // Update model viewport range for potential dynamic loading
205
+ const startIndex = Math.floor(newScrollTop / 200); // Rough estimate
206
+ const endIndex = Math.min(items.length - 1, startIndex + Math.ceil(containerHeight / 200) + overscanCount);
207
+ model.updateViewportRange(startIndex, endIndex);
208
+ }, [containerHeight, items.length, overscanCount, model]);
209
+
210
+ // AICODE-NOTE: Set up scroll listener with throttling
211
+ useEffect(() => {
212
+ const container = scrollContainerRef.current;
213
+ if (!container) return;
214
+
215
+ let ticking = false;
216
+ const throttledScroll = () => {
217
+ if (!ticking) {
218
+ requestAnimationFrame(() => {
219
+ handleScroll();
220
+ ticking = false;
221
+ });
222
+ ticking = true;
223
+ }
224
+ };
225
+
226
+ container.addEventListener('scroll', throttledScroll);
227
+
228
+ // Calculate initial visible items
229
+ handleScroll();
230
+
231
+ return () => {
232
+ container.removeEventListener('scroll', throttledScroll);
233
+ };
234
+ }, [handleScroll]);
235
+
236
+ // Scroll to item when model requests it
237
+ useEffect(() => {
238
+ const targetId = model.scrollToItemId;
239
+ if (!targetId || !scrollContainerRef.current) return;
240
+ const el = scrollContainerRef.current.querySelector(`[data-item-id="${CSS.escape(targetId)}"]`);
241
+ if (el) {
242
+ el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
243
+ } else {
244
+ // Item not rendered yet — find position from layout and scroll to it
245
+ const pos = masonryLayout.layout.find(p => p.id === targetId);
246
+ if (pos) {
247
+ scrollContainerRef.current.scrollTop = Math.max(0, pos.y - containerHeight / 2);
248
+ }
249
+ }
250
+ model.clearScrollToItem();
251
+ }, [model.scrollToItemId, model, masonryLayout.layout, containerHeight]);
252
+
253
+ // AICODE-NOTE: Handle item interactions
254
+ const handleItemClick = useCallback((item: ListItemData) => {
255
+ model.selectItem(item);
256
+ }, [model]);
257
+
258
+ const handleItemDoubleClick = useCallback((item: ListItemData) => {
259
+ if (model.provider.onItemDoubleClick) {
260
+ model.provider.onItemDoubleClick(item);
261
+ }
262
+ }, [model]);
263
+
264
+ if (items.length === 0) {
265
+ return (
266
+ <div style={{
267
+ width: '100%',
268
+ height: '100%',
269
+ display: 'flex',
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ color: '#6c757d',
273
+ fontSize: '16px'
274
+ }}>
275
+ No items to display
276
+ </div>
277
+ );
278
+ }
279
+
280
+ return (
281
+ <div
282
+ ref={scrollContainerRef}
283
+ className={`overflow-y-auto overflow-x-hidden ${className}`}
284
+ style={{
285
+ width: containerWidth,
286
+ height: containerHeight
287
+ }}
288
+ >
289
+ {/* AICODE-NOTE: Container with total height for proper scrollbar */}
290
+ <div
291
+ style={{
292
+ position: 'relative',
293
+ width: containerWidth,
294
+ height: Math.max(masonryLayout.totalHeight, containerHeight),
295
+ overflow: 'hidden'
296
+ }}
297
+ >
298
+ {/* AICODE-NOTE: Render only visible items */}
299
+ {visibleItems.map((position) => {
300
+ const item = items.find(i => i.id === position.id);
301
+ if (!item) return null;
302
+
303
+ return (
304
+ <MasonryItem
305
+ key={item.id}
306
+ item={item}
307
+ position={position}
308
+ model={model}
309
+ onClick={handleItemClick}
310
+ onDoubleClick={handleItemDoubleClick}
311
+ />
312
+ );
313
+ })}
314
+
315
+ {/* AICODE-NOTE: Debug info when debug visualization is enabled */}
316
+ {model.debugVisualization && (
317
+ <div
318
+ style={{
319
+ position: 'fixed',
320
+ top: 10,
321
+ right: 10,
322
+ background: 'rgba(0, 0, 0, 0.8)',
323
+ color: 'white',
324
+ padding: '8px 12px',
325
+ borderRadius: '4px',
326
+ fontSize: '12px',
327
+ fontFamily: 'monospace',
328
+ zIndex: 1000,
329
+ pointerEvents: 'none'
330
+ }}
331
+ >
332
+ <div>Total: {masonryLayout.layout.length}</div>
333
+ <div>Visible: {visibleItems.length}</div>
334
+ <div>Scroll: {Math.round(scrollTop)}px</div>
335
+ <div>Height: {Math.round(masonryLayout.totalHeight)}px</div>
336
+ </div>
337
+ )}
338
+ </div>
339
+ </div>
340
+ );
341
+ });
342
+
343
+ MasonryItem.displayName = 'MasonryItem';
344
+ VirtualizedMasonryView.displayName = 'VirtualizedMasonryView';
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { EmptyState as UIEmptyState } from '@anymux/ui/components/empty-state';
4
+
5
+ const SIZE_MAP = { small: 'sm', medium: 'md', large: 'lg' } as const;
6
+
7
+ export interface EmptyStateProps {
8
+ title?: string;
9
+ message?: string;
10
+ icon?: React.ReactNode;
11
+ actions?: React.ReactNode;
12
+ className?: string;
13
+ size?: 'small' | 'medium' | 'large';
14
+ }
15
+
16
+ const EmptyStateComponent: React.FC<EmptyStateProps> = ({
17
+ title = 'No items found',
18
+ message = 'There are no items to display.',
19
+ icon,
20
+ actions,
21
+ className = '',
22
+ size = 'medium',
23
+ }) => {
24
+ return (
25
+ <UIEmptyState
26
+ preset="generic"
27
+ title={title}
28
+ description={message}
29
+ icon={icon}
30
+ size={SIZE_MAP[size]}
31
+ className={className}
32
+ >
33
+ {actions && <div className="mt-6">{actions}</div>}
34
+ </UIEmptyState>
35
+ );
36
+ };
37
+
38
+ export interface NoItemsProps {
39
+ onRefresh?: () => void;
40
+ className?: string;
41
+ }
42
+
43
+ const NoItemsComponent: React.FC<NoItemsProps> = ({ onRefresh, className = '' }) => {
44
+ return (
45
+ <UIEmptyState
46
+ preset="empty-folder"
47
+ title="No items found"
48
+ description="This folder appears to be empty."
49
+ {...(onRefresh ? { action: { label: 'Refresh', onClick: onRefresh } } : {})}
50
+ className={className}
51
+ />
52
+ );
53
+ };
54
+
55
+ export interface NoSearchResultsProps {
56
+ searchQuery?: string;
57
+ onClearSearch?: () => void;
58
+ className?: string;
59
+ }
60
+
61
+ const NoSearchResultsComponent: React.FC<NoSearchResultsProps> = ({
62
+ searchQuery,
63
+ onClearSearch,
64
+ className = '',
65
+ }) => {
66
+ const message = searchQuery
67
+ ? `No items match "${searchQuery}". Try adjusting your search terms.`
68
+ : 'No items match your search criteria.';
69
+
70
+ return (
71
+ <UIEmptyState
72
+ preset="no-results"
73
+ title="No search results"
74
+ description={message}
75
+ {...(onClearSearch ? { action: { label: 'Clear Search', onClick: onClearSearch } } : {})}
76
+ className={className}
77
+ />
78
+ );
79
+ };
80
+
81
+ export interface NoSelectionProps {
82
+ message?: string;
83
+ className?: string;
84
+ }
85
+
86
+ const NoSelectionComponent: React.FC<NoSelectionProps> = ({
87
+ message = 'Select items to see details and available actions.',
88
+ className = '',
89
+ }) => {
90
+ return (
91
+ <UIEmptyState
92
+ preset="no-selection"
93
+ description={message}
94
+ size="sm"
95
+ className={className}
96
+ />
97
+ );
98
+ };
99
+
100
+ export const EmptyState = observer(EmptyStateComponent);
101
+ export const NoItems = observer(NoItemsComponent);
102
+ export const NoSearchResults = observer(NoSearchResultsComponent);
103
+ export const NoSelection = observer(NoSelectionComponent);
@@ -0,0 +1,123 @@
1
+ import React, { Component, ErrorInfo, ReactNode } from 'react';
2
+
3
+ // AICODE-NOTE: Error boundary for catching and displaying React errors
4
+ export interface ErrorBoundaryProps {
5
+ children: ReactNode;
6
+ fallback?: (error: Error, errorInfo: ErrorInfo) => ReactNode;
7
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
8
+ className?: string;
9
+ }
10
+
11
+ export interface ErrorBoundaryState {
12
+ hasError: boolean;
13
+ error: Error | null;
14
+ errorInfo: ErrorInfo | null;
15
+ }
16
+
17
+ export class ListErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
18
+ constructor(props: ErrorBoundaryProps) {
19
+ super(props);
20
+ this.state = {
21
+ hasError: false,
22
+ error: null,
23
+ errorInfo: null
24
+ };
25
+ }
26
+
27
+ static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
28
+ // AICODE-NOTE: Update state to show error UI
29
+ return {
30
+ hasError: true,
31
+ error
32
+ };
33
+ }
34
+
35
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
36
+ // AICODE-NOTE: Log error and update state
37
+ this.setState({
38
+ error,
39
+ errorInfo
40
+ });
41
+
42
+ // AICODE-NOTE: Call optional error handler
43
+ if (this.props.onError) {
44
+ this.props.onError(error, errorInfo);
45
+ }
46
+
47
+ // AICODE-NOTE: Log to console for debugging
48
+ console.error('ListItemsComponent Error:', error, errorInfo);
49
+ }
50
+
51
+ handleRetry = () => {
52
+ // AICODE-NOTE: Reset error state to retry rendering
53
+ this.setState({
54
+ hasError: false,
55
+ error: null,
56
+ errorInfo: null
57
+ });
58
+ };
59
+
60
+ render() {
61
+ if (this.state.hasError) {
62
+ // AICODE-NOTE: Use custom fallback if provided
63
+ if (this.props.fallback && this.state.error && this.state.errorInfo) {
64
+ return this.props.fallback(this.state.error, this.state.errorInfo);
65
+ }
66
+
67
+ // AICODE-NOTE: Default error UI
68
+ return (
69
+ <div className={`flex items-center justify-center p-8 ${this.props.className || ''}`}>
70
+ <div className="text-center space-y-4 max-w-md">
71
+ <div className="text-destructive">
72
+ <svg
73
+ className="w-12 h-12 mx-auto mb-4"
74
+ fill="none"
75
+ stroke="currentColor"
76
+ viewBox="0 0 24 24"
77
+ >
78
+ <path
79
+ strokeLinecap="round"
80
+ strokeLinejoin="round"
81
+ strokeWidth={2}
82
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
83
+ />
84
+ </svg>
85
+ </div>
86
+
87
+ <div>
88
+ <h3 className="text-lg font-semibold text-foreground mb-2">
89
+ Something went wrong
90
+ </h3>
91
+ <p className="text-sm text-muted-foreground mb-4">
92
+ An error occurred while displaying the list items.
93
+ </p>
94
+ </div>
95
+
96
+ <div className="space-y-2">
97
+ <button
98
+ onClick={this.handleRetry}
99
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
100
+ >
101
+ Try Again
102
+ </button>
103
+
104
+ {typeof window !== 'undefined' && this.state.error && (
105
+ <details className="text-left">
106
+ <summary className="text-xs text-muted-foreground cursor-pointer hover:text-foreground">
107
+ Show Error Details
108
+ </summary>
109
+ <pre className="text-xs text-destructive mt-2 p-2 bg-muted rounded overflow-auto max-h-32">
110
+ {this.state.error.toString()}
111
+ {this.state.errorInfo?.componentStack}
112
+ </pre>
113
+ </details>
114
+ )}
115
+ </div>
116
+ </div>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ return this.props.children;
122
+ }
123
+ }
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { ErrorDisplay as UIErrorDisplay } from '@anymux/ui/components/error-display';
4
+
5
+ const sizeMap = { small: 'sm', medium: 'md', large: 'lg' } as const;
6
+
7
+ export interface ErrorDisplayProps {
8
+ error: Error | string;
9
+ title?: string;
10
+ message?: string;
11
+ onRetry?: () => void;
12
+ onDismiss?: () => void;
13
+ className?: string;
14
+ size?: 'small' | 'medium' | 'large';
15
+ variant?: 'default' | 'destructive' | 'warning';
16
+ }
17
+
18
+ const ErrorDisplayComponent: React.FC<ErrorDisplayProps> = ({
19
+ error,
20
+ title = 'Error',
21
+ message,
22
+ onRetry,
23
+ onDismiss,
24
+ className = '',
25
+ size = 'medium',
26
+ variant = 'destructive'
27
+ }) => {
28
+ const mappedSize = sizeMap[size];
29
+ const mappedVariant = variant === 'default' ? undefined : variant;
30
+
31
+ // If message override is provided, create a modified error preserving stack
32
+ let mappedError: Error | string = error;
33
+ if (message) {
34
+ if (typeof error === 'object') {
35
+ mappedError = Object.assign(new Error(message), { stack: error.stack });
36
+ } else {
37
+ mappedError = message;
38
+ }
39
+ }
40
+
41
+ return (
42
+ <UIErrorDisplay
43
+ error={mappedError}
44
+ title={title}
45
+ size={mappedSize}
46
+ variant={mappedVariant}
47
+ showDetails={typeof error === 'object' && !!error.stack}
48
+ className={className}
49
+ {...(onRetry != null ? { onRetry } : {})}
50
+ {...(onDismiss != null ? { onDismiss } : {})}
51
+ />
52
+ );
53
+ };
54
+
55
+ export interface NetworkErrorProps {
56
+ onRetry?: () => void;
57
+ className?: string;
58
+ }
59
+
60
+ const NetworkErrorComponent: React.FC<NetworkErrorProps> = ({
61
+ onRetry,
62
+ className = ''
63
+ }) => {
64
+ return (
65
+ <ErrorDisplayComponent
66
+ error="Network request failed"
67
+ title="Connection Error"
68
+ message="Unable to load items. Please check your internet connection and try again."
69
+ variant="warning"
70
+ className={className}
71
+ {...(onRetry != null ? { onRetry } : {})}
72
+ />
73
+ );
74
+ };
75
+
76
+ export interface LoadErrorProps {
77
+ error: Error | string;
78
+ onRetry?: () => void;
79
+ className?: string;
80
+ }
81
+
82
+ const LoadErrorComponent: React.FC<LoadErrorProps> = ({
83
+ error,
84
+ onRetry,
85
+ className = ''
86
+ }) => {
87
+ return (
88
+ <ErrorDisplayComponent
89
+ error={error}
90
+ title="Failed to Load Items"
91
+ variant="destructive"
92
+ className={className}
93
+ {...(onRetry != null ? { onRetry } : {})}
94
+ />
95
+ );
96
+ };
97
+
98
+ export const ErrorDisplay = observer(ErrorDisplayComponent);
99
+ export const NetworkError = observer(NetworkErrorComponent);
100
+ export const LoadError = observer(LoadErrorComponent);