@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,761 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { getItemAccessibilityProps } from '../utils/ListAccessibility';
4
+ import { ListItemData, ListViewType } from '../types/ListTypes';
5
+ import { ListItemsProvider } from '../providers/ListItemsProvider';
6
+ import { ListItemsModel } from '../models/ListItemsModel';
7
+ import { File } from 'lucide-react';
8
+ import { lucideIconMap, iconColorMap } from '../../icons/iconMap';
9
+ import { useFileBrowserContext } from '../../file-browser/context/FileBrowserContext';
10
+
11
+ // AICODE-NOTE: Basic list item component with click handling and flexible rendering
12
+
13
+ export interface ListItemProps {
14
+ item: ListItemData;
15
+ index: number;
16
+ totalItems: number;
17
+ viewType: ListViewType;
18
+ provider?: ListItemsProvider;
19
+ model?: ListItemsModel; // AICODE-NOTE: Model for debug visualization and other reactive state
20
+ itemWidth?: number;
21
+ itemHeight?: number;
22
+ thumbnailSize?: number; // AICODE-NOTE: Precise thumbnail size from calculator
23
+ isSelected?: boolean;
24
+ isFocused?: boolean;
25
+ isDraggedOver?: boolean;
26
+ dragOverPosition?: 'before' | 'after' | 'inside' | null;
27
+ canDrag?: boolean;
28
+ onClick?: (item: ListItemData, event: React.MouseEvent) => void;
29
+ onDoubleClick?: (item: ListItemData, event: React.MouseEvent) => void;
30
+ onContextMenu?: (item: ListItemData, event: React.MouseEvent) => void;
31
+ onDragStart?: (item: ListItemData, event: React.DragEvent) => void;
32
+ onDragOver?: (item: ListItemData, event: React.DragEvent) => void;
33
+ onDragLeave?: (item: ListItemData, event: React.DragEvent) => void;
34
+ onDrop?: (item: ListItemData, event: React.DragEvent) => void;
35
+ }
36
+
37
+ // Inline rename input component
38
+ const InlineRenameInput: React.FC<{
39
+ currentName: string;
40
+ onCommit: (newName: string) => void;
41
+ onCancel: () => void;
42
+ className?: string;
43
+ }> = ({ currentName, onCommit, onCancel, className }) => {
44
+ const [value, setValue] = useState(currentName);
45
+ const inputRef = useRef<HTMLInputElement>(null);
46
+ const mountTimeRef = useRef(Date.now());
47
+ const committedRef = useRef(false);
48
+
49
+ useEffect(() => {
50
+ mountTimeRef.current = Date.now();
51
+ const raf = requestAnimationFrame(() => {
52
+ const input = inputRef.current;
53
+ if (input) {
54
+ input.focus();
55
+ const dotIndex = currentName.lastIndexOf('.');
56
+ input.setSelectionRange(0, dotIndex > 0 ? dotIndex : currentName.length);
57
+ }
58
+ });
59
+ return () => cancelAnimationFrame(raf);
60
+ }, [currentName]);
61
+
62
+ const commit = () => {
63
+ if (committedRef.current) return;
64
+ committedRef.current = true;
65
+ const trimmed = value.trim();
66
+ if (trimmed && trimmed !== currentName) {
67
+ onCommit(trimmed);
68
+ } else {
69
+ onCancel();
70
+ }
71
+ };
72
+
73
+ const handleBlur = () => {
74
+ if (Date.now() - mountTimeRef.current < 200) return;
75
+ commit();
76
+ };
77
+
78
+ return (
79
+ <input
80
+ ref={inputRef}
81
+ value={value}
82
+ onChange={(e) => setValue(e.target.value)}
83
+ onKeyDown={(e) => {
84
+ e.stopPropagation();
85
+ if (e.key === 'Enter') { e.preventDefault(); commit(); }
86
+ if (e.key === 'Escape') { e.preventDefault(); onCancel(); }
87
+ }}
88
+ onBlur={handleBlur}
89
+ onClick={(e) => e.stopPropagation()}
90
+ onDoubleClick={(e) => e.stopPropagation()}
91
+ className={`bg-background border border-primary rounded px-1 py-0 text-sm outline-none ${className || ''}`}
92
+ style={{ minWidth: 60, maxWidth: '100%' }}
93
+ />
94
+ );
95
+ };
96
+
97
+ // AICODE-NOTE: Memoized component for performance optimization
98
+ const ListItemComponent = observer<ListItemProps>(({
99
+ item,
100
+ index,
101
+ totalItems,
102
+ viewType,
103
+ provider,
104
+ model,
105
+ itemWidth,
106
+ itemHeight,
107
+ thumbnailSize,
108
+ isSelected = false,
109
+ isFocused = false,
110
+ isDraggedOver = false,
111
+ dragOverPosition = null,
112
+ canDrag = false,
113
+ onClick,
114
+ onDoubleClick,
115
+ onContextMenu,
116
+ onDragStart,
117
+ onDragOver,
118
+ onDragLeave,
119
+ onDrop
120
+ }) => {
121
+ // Inline rename support
122
+ const fileBrowserCtx = useFileBrowserContext();
123
+ const isRenaming = fileBrowserCtx?.renameState?.itemId === item.id && fileBrowserCtx?.renameState?.source === 'list';
124
+
125
+ // Resolve thumbnail blob URL from provider cache (reactive via MobX)
126
+ const resolvedThumbnailUrl = item.thumbnailUrl && model ? model.resolveThumbnailUrl(item) : item.imageUrl;
127
+
128
+ const renderNameOrRename = (extraClass?: string) => {
129
+ if (isRenaming && fileBrowserCtx) {
130
+ return (
131
+ <InlineRenameInput
132
+ currentName={fileBrowserCtx.renameState!.currentName}
133
+ onCommit={fileBrowserCtx.onRenameCommit}
134
+ onCancel={fileBrowserCtx.onRenameCancel}
135
+ className={extraClass}
136
+ />
137
+ );
138
+ }
139
+ return null;
140
+ };
141
+
142
+ // AICODE-NOTE: Generate accessibility props
143
+ const accessibilityProps = getItemAccessibilityProps({
144
+ item,
145
+ index,
146
+ totalItems,
147
+ isSelected,
148
+ isFocused,
149
+ viewType: viewType.id
150
+ });
151
+
152
+ // AICODE-NOTE: Handle clicks with proper event handling and modifiers
153
+ const handleClick = (event: React.MouseEvent) => {
154
+ if (onClick) {
155
+ onClick(item, event);
156
+ }
157
+ };
158
+
159
+ const handleDoubleClick = (event: React.MouseEvent) => {
160
+ if (onDoubleClick) {
161
+ onDoubleClick(item, event);
162
+ }
163
+ };
164
+
165
+ const handleContextMenu = (event: React.MouseEvent) => {
166
+ event.preventDefault();
167
+ if (onContextMenu) {
168
+ onContextMenu(item, event);
169
+ }
170
+ };
171
+
172
+ // AICODE-NOTE: Drag and drop event handlers
173
+ const handleDragStart = (event: React.DragEvent) => {
174
+ if (onDragStart) {
175
+ onDragStart(item, event);
176
+ }
177
+ };
178
+
179
+ const handleDragOver = (event: React.DragEvent) => {
180
+ event.preventDefault();
181
+ if (onDragOver) {
182
+ onDragOver(item, event);
183
+ }
184
+ };
185
+
186
+ const handleDragLeave = (event: React.DragEvent) => {
187
+ if (onDragLeave) {
188
+ onDragLeave(item, event);
189
+ }
190
+ };
191
+
192
+ const handleDrop = (event: React.DragEvent) => {
193
+ event.preventDefault();
194
+ if (onDrop) {
195
+ onDrop(item, event);
196
+ }
197
+ };
198
+
199
+ // AICODE-NOTE: Import shared resolveIcon for consistent icon rendering
200
+ const resolveItemIcon = (iconName: string | undefined, sizeClass = 'w-4 h-4') => {
201
+ const name = iconName || 'file';
202
+ const MappedIcon = lucideIconMap[name] || File;
203
+ const colorClass = iconColorMap[name] || 'text-gray-400';
204
+ return <MappedIcon className={`${sizeClass} ${colorClass}`} />;
205
+ };
206
+
207
+ // AICODE-NOTE: Render icon using provider's getItemIcon method
208
+ const renderIcon = (sizeClass = 'w-4 h-4') => {
209
+ const customIcon = provider?.getItemIcon?.(item);
210
+ if (customIcon) {
211
+ if (typeof customIcon === 'string') {
212
+ return resolveItemIcon(customIcon, sizeClass);
213
+ } else {
214
+ const IconComponent = customIcon;
215
+ return <IconComponent className={sizeClass} />;
216
+ }
217
+ }
218
+ if (item.icon) {
219
+ return resolveItemIcon(item.icon, sizeClass);
220
+ }
221
+ return <File className={`${sizeClass} text-muted-foreground`} />;
222
+ };
223
+
224
+ // AICODE-NOTE: Render image or icon for list/details views
225
+ const renderImageOrIcon = (size: 'small' | 'medium' | 'large' = 'medium') => {
226
+ const iconSizeClasses = {
227
+ small: 'w-4 h-4',
228
+ medium: 'w-8 h-8',
229
+ large: 'w-12 h-12'
230
+ };
231
+
232
+ const containerClasses = {
233
+ small: 'h-6 w-6',
234
+ medium: 'h-12 w-12',
235
+ large: 'h-16 w-16'
236
+ };
237
+
238
+ const imgSrc = resolvedThumbnailUrl || item.imageUrl;
239
+ const pxSizes = { small: 24, medium: 48, large: 64 };
240
+ const px = pxSizes[size];
241
+ if (imgSrc) {
242
+ return (
243
+ <img
244
+ src={imgSrc}
245
+ alt={item.name}
246
+ className="object-cover rounded flex-shrink-0"
247
+ style={{ width: px, height: px, maxWidth: px, maxHeight: px }}
248
+ loading="lazy"
249
+ onError={(e) => {
250
+ const target = e.target as HTMLImageElement;
251
+ target.style.display = 'none';
252
+ target.nextElementSibling?.classList.remove('hidden');
253
+ }}
254
+ />
255
+ );
256
+ }
257
+
258
+ return (
259
+ <div className={`${containerClasses[size]} flex items-center justify-center`}>
260
+ {renderIcon(iconSizeClasses[size])}
261
+ </div>
262
+ );
263
+ };
264
+
265
+ // AICODE-NOTE: Get item dimensions for variable sizing
266
+ const getItemDimensions = () => {
267
+ if (item.getDimensions) {
268
+ return item.getDimensions();
269
+ }
270
+
271
+ if (item.aspectRatio && itemWidth) {
272
+ return {
273
+ width: itemWidth,
274
+ height: itemWidth / item.aspectRatio
275
+ };
276
+ }
277
+
278
+ return null;
279
+ };
280
+
281
+ // AICODE-NOTE: Enhanced styles with better selection and focus states
282
+ const baseClasses = `
283
+ flex items-center gap-2 py-1 px-2 text-[13px] cursor-default select-none transition-colors duration-200
284
+ hover:bg-muted/50 border border-transparent rounded-md
285
+ ${isSelected ? 'bg-primary/10 border-primary/20' : ''}
286
+ ${isFocused ? 'ring-2 ring-primary/50' : ''}
287
+ ${isDraggedOver ? 'bg-accent/20 border-accent' : ''}
288
+ ${dragOverPosition === 'before' ? 'border-t-2 border-t-primary' : ''}
289
+ ${dragOverPosition === 'after' ? 'border-b-2 border-b-primary' : ''}
290
+ ${dragOverPosition === 'inside' ? 'bg-primary/5 border-primary/30' : ''}
291
+ `;
292
+
293
+ // AICODE-NOTE: Grid-specific styles with better spacing and sizing
294
+ const gridClasses = `
295
+ flex flex-col items-center justify-start p-3 cursor-default select-none transition-colors duration-200
296
+ hover:bg-muted/50 border border-transparent rounded-lg min-h-[140px] max-w-full
297
+ ${isSelected ? 'bg-primary/10 border-primary/20 shadow-sm' : ''}
298
+ ${isFocused ? 'ring-2 ring-primary/50' : ''}
299
+ ${isDraggedOver ? 'bg-accent/20 border-accent' : ''}
300
+ ${dragOverPosition === 'before' ? 'border-t-2 border-t-primary' : ''}
301
+ ${dragOverPosition === 'after' ? 'border-b-2 border-b-primary' : ''}
302
+ ${dragOverPosition === 'inside' ? 'bg-primary/5 border-primary/30' : ''}
303
+ `;
304
+
305
+ // AICODE-NOTE: Debug styles for layout visualization
306
+ const isDebugMode = model?.debugVisualization ?? false;
307
+
308
+ const debugStyles = isDebugMode ? {
309
+ container: { backgroundColor: 'rgba(0, 255, 255, 0.05)', border: '2px solid rgba(0, 255, 255, 0.3)' },
310
+ imageContainer: { backgroundColor: 'rgba(255, 0, 0, 0.1)', border: '1px solid rgba(255, 0, 0, 0.3)' },
311
+ image: { backgroundColor: 'rgba(0, 255, 0, 0.1)', border: '1px solid rgba(0, 255, 0, 0.5)' },
312
+ icon: { backgroundColor: 'rgba(0, 0, 255, 0.1)', border: '1px solid rgba(0, 0, 255, 0.3)' },
313
+ textContainer: { backgroundColor: 'rgba(255, 255, 0, 0.1)', border: '1px solid rgba(255, 255, 0, 0.3)' },
314
+ text: { backgroundColor: 'rgba(255, 165, 0, 0.1)', border: '1px solid rgba(255, 165, 0, 0.3)' },
315
+ metadata: { backgroundColor: 'rgba(128, 0, 128, 0.1)', border: '1px solid rgba(128, 0, 128, 0.3)' }
316
+ } : {
317
+ container: {},
318
+ imageContainer: {},
319
+ image: {},
320
+ icon: {},
321
+ textContainer: {},
322
+ text: {},
323
+ metadata: {}
324
+ };
325
+
326
+ // AICODE-NOTE: Grid view layout — cell dimensions are uniform, thumbnail adapts to image aspect ratio
327
+ if (viewType.id === 'grid') {
328
+ const containerWidth = itemWidth || 200;
329
+ const containerHeight = itemHeight || 140;
330
+ const maxThumbWidth = containerWidth - 16;
331
+ const maxThumbHeight = thumbnailSize ?? Math.min(maxThumbWidth, containerHeight - 48);
332
+
333
+ // Get image aspect ratio (w/h) from provider cache if available
334
+ const imageAspectRatio = model?.provider?.getAspectRatio?.(item.path);
335
+ // Fit thumbnail to image aspect ratio within the available cell space
336
+ let thumbWidth = maxThumbWidth;
337
+ let thumbHeight = maxThumbHeight;
338
+ if (resolvedThumbnailUrl && imageAspectRatio && imageAspectRatio > 0) {
339
+ // Start with full width, calculate height
340
+ const heightFromWidth = maxThumbWidth / imageAspectRatio;
341
+ if (heightFromWidth <= maxThumbHeight) {
342
+ // Landscape or moderate: full width, shorter height
343
+ thumbWidth = maxThumbWidth;
344
+ thumbHeight = Math.round(heightFromWidth);
345
+ } else {
346
+ // Portrait: full height, narrower width
347
+ thumbHeight = maxThumbHeight;
348
+ thumbWidth = Math.round(maxThumbHeight * imageAspectRatio);
349
+ }
350
+ }
351
+
352
+ return (
353
+ <div
354
+ className={`${gridClasses} flex flex-col items-center`}
355
+ style={{
356
+ width: containerWidth,
357
+ height: containerHeight,
358
+ minHeight: containerHeight,
359
+ boxSizing: 'border-box',
360
+ ...debugStyles.container
361
+ }}
362
+ role={accessibilityProps.role}
363
+ aria-label={accessibilityProps.ariaLabel}
364
+ aria-selected={accessibilityProps.ariaSelected}
365
+ aria-setsize={accessibilityProps.ariaSetSize}
366
+ aria-posinset={accessibilityProps.ariaPosInSet}
367
+ tabIndex={accessibilityProps.tabIndex}
368
+ draggable={canDrag}
369
+ onClick={handleClick}
370
+ onDoubleClick={handleDoubleClick}
371
+ onContextMenu={handleContextMenu}
372
+ onDragStart={handleDragStart}
373
+ onDragOver={handleDragOver}
374
+ onDragLeave={handleDragLeave}
375
+ onDrop={handleDrop}
376
+ >
377
+ {/* AICODE-NOTE: Thumbnail container sized to image aspect ratio */}
378
+ <div
379
+ className="relative rounded-lg overflow-hidden mb-2 flex-shrink-0 flex items-center justify-center"
380
+ style={{
381
+ width: thumbWidth,
382
+ height: thumbHeight,
383
+ ...debugStyles.imageContainer
384
+ }}
385
+ >
386
+ {resolvedThumbnailUrl ? (
387
+ <img
388
+ src={resolvedThumbnailUrl}
389
+ alt={item.name}
390
+ className="object-cover"
391
+ loading="lazy"
392
+ style={{
393
+ width: '100%',
394
+ height: '100%',
395
+ ...debugStyles.image
396
+ }}
397
+ />
398
+ ) : (
399
+ <div
400
+ className="w-full h-full flex items-center justify-center bg-muted/30 rounded-lg"
401
+ style={{
402
+ ...debugStyles.icon
403
+ }}
404
+ >
405
+ {renderIcon('w-12 h-12')}
406
+ </div>
407
+ )}
408
+
409
+ {/* AICODE-NOTE: Selection overlay */}
410
+ {isSelected && (
411
+ <div className="absolute inset-0 bg-primary/20 border-2 border-primary rounded-lg" />
412
+ )}
413
+
414
+ {/* Checkbox overlay for grid items */}
415
+ {(model?.showCheckboxes) && (
416
+ <div className="absolute top-1 left-1 z-10">
417
+ <input
418
+ type="checkbox"
419
+ checked={isSelected}
420
+ onChange={() => {}}
421
+ onClick={(e) => {
422
+ e.stopPropagation();
423
+ model?.selectItem(item, { ctrl: true });
424
+ }}
425
+ className="h-4 w-4 rounded border-muted-foreground/50 accent-primary cursor-pointer bg-background shadow-sm"
426
+ />
427
+ </div>
428
+ )}
429
+ </div>
430
+
431
+ {/* AICODE-NOTE: Item name with debug background */}
432
+ <div
433
+ className="text-sm font-medium text-center px-2 leading-tight"
434
+ style={{
435
+ width: containerWidth - 16,
436
+ maxWidth: containerWidth - 16,
437
+ ...debugStyles.textContainer
438
+ }}
439
+ >
440
+ <div
441
+ className="truncate"
442
+ style={{
443
+ width: '100%',
444
+ maxWidth: '100%',
445
+ overflow: 'hidden',
446
+ textOverflow: 'ellipsis',
447
+ whiteSpace: 'nowrap',
448
+ ...debugStyles.text
449
+ }}
450
+ title={item.name}
451
+ >
452
+ {isRenaming ? renderNameOrRename('text-center') : item.name}
453
+ </div>
454
+ </div>
455
+
456
+ {/* AICODE-NOTE: Optional metadata with debug background */}
457
+ {item.size && (
458
+ <div
459
+ className="text-xs text-muted-foreground mt-1 text-center truncate"
460
+ style={{
461
+ width: containerWidth - 16,
462
+ maxWidth: containerWidth - 16,
463
+ overflow: 'hidden',
464
+ textOverflow: 'ellipsis',
465
+ whiteSpace: 'nowrap',
466
+ ...debugStyles.metadata
467
+ }}
468
+ >
469
+ {item.size > 1024 * 1024
470
+ ? `${(item.size / (1024 * 1024)).toFixed(1)} MB`
471
+ : `${(item.size / 1024).toFixed(1)} KB`
472
+ }
473
+ </div>
474
+ )}
475
+ </div>
476
+ );
477
+ }
478
+
479
+ // AICODE-NOTE: Masonry view layout (similar to grid but with flexible height)
480
+ if (viewType.id === 'masonry-horizontal' || viewType.id === 'masonry-vertical') {
481
+ const customDimensions = getItemDimensions();
482
+ const containerWidth = customDimensions?.width || itemWidth || 200;
483
+ const containerHeight = customDimensions?.height || itemHeight || 140;
484
+
485
+ return (
486
+ <div
487
+ className={gridClasses}
488
+ style={{
489
+ width: containerWidth,
490
+ minHeight: containerHeight + 80,
491
+ boxSizing: 'border-box',
492
+ ...debugStyles.container
493
+ }}
494
+ role={accessibilityProps.role}
495
+ aria-label={accessibilityProps.ariaLabel}
496
+ aria-selected={accessibilityProps.ariaSelected}
497
+ aria-setsize={accessibilityProps.ariaSetSize}
498
+ aria-posinset={accessibilityProps.ariaPosInSet}
499
+ tabIndex={accessibilityProps.tabIndex}
500
+ draggable={canDrag}
501
+ onClick={handleClick}
502
+ onDoubleClick={handleDoubleClick}
503
+ onContextMenu={handleContextMenu}
504
+ onDragStart={handleDragStart}
505
+ onDragOver={handleDragOver}
506
+ onDragLeave={handleDragLeave}
507
+ onDrop={handleDrop}
508
+ >
509
+ {/* AICODE-NOTE: Image or icon with variable size */}
510
+ <div
511
+ className="flex items-center justify-center mb-3 flex-1 overflow-hidden rounded-lg"
512
+ style={{
513
+ width: containerWidth - 16,
514
+ height: containerHeight - 20,
515
+ maxWidth: containerWidth - 16,
516
+ maxHeight: containerHeight - 20,
517
+ ...debugStyles.imageContainer
518
+ }}
519
+ >
520
+ {resolvedThumbnailUrl ? (
521
+ <img
522
+ src={resolvedThumbnailUrl}
523
+ alt={item.name}
524
+ className="object-contain rounded"
525
+ style={{
526
+ width: 'auto',
527
+ height: 'auto',
528
+ maxWidth: '100%',
529
+ maxHeight: '100%',
530
+ ...debugStyles.image
531
+ }}
532
+ loading="lazy"
533
+ />
534
+ ) : (
535
+ <div
536
+ className="text-4xl flex items-center justify-center w-full h-full"
537
+ style={{
538
+ ...debugStyles.icon
539
+ }}
540
+ >
541
+ {renderIcon()}
542
+ </div>
543
+ )}
544
+ </div>
545
+
546
+ {/* AICODE-NOTE: Item name with flexible height for masonry */}
547
+ <div
548
+ className="text-sm font-medium text-center px-2 leading-tight"
549
+ style={{
550
+ width: containerWidth - 16,
551
+ maxWidth: containerWidth - 16,
552
+ ...debugStyles.textContainer
553
+ }}
554
+ >
555
+ <div
556
+ className="break-words"
557
+ style={{
558
+ width: '100%',
559
+ maxWidth: '100%',
560
+ ...debugStyles.text
561
+ }}
562
+ title={item.name}
563
+ >
564
+ {isRenaming ? renderNameOrRename('text-center') : item.name}
565
+ </div>
566
+ </div>
567
+
568
+ {/* AICODE-NOTE: Extended metadata for masonry view */}
569
+ <div
570
+ className="text-xs text-muted-foreground mt-2 space-y-1 text-center"
571
+ style={{
572
+ width: containerWidth - 16,
573
+ maxWidth: containerWidth - 16,
574
+ ...debugStyles.metadata
575
+ }}
576
+ >
577
+ {item.size && (
578
+ <div className="truncate">
579
+ {item.size > 1024 * 1024
580
+ ? `${(item.size / (1024 * 1024)).toFixed(1)} MB`
581
+ : `${(item.size / 1024).toFixed(1)} KB`
582
+ }
583
+ </div>
584
+ )}
585
+ {item.modified && (
586
+ <div className="truncate">{item.modified.toLocaleDateString()}</div>
587
+ )}
588
+ </div>
589
+ </div>
590
+ );
591
+ }
592
+
593
+ // AICODE-NOTE: Details view layout — uses CSS grid matching the header in VirtualizedDetailsView
594
+ if (viewType.id === 'details') {
595
+ // Selection/focus classes without flex/gap from baseClasses
596
+ const detailsStateClasses = `
597
+ items-center py-1 px-4 text-[13px] cursor-default select-none transition-colors duration-200
598
+ hover:bg-muted/50 border border-transparent
599
+ ${isSelected ? 'bg-primary/10 border-primary/20' : ''}
600
+ ${isFocused ? 'ring-2 ring-primary/50' : ''}
601
+ ${isDraggedOver ? 'bg-accent/20 border-accent' : ''}
602
+ ${dragOverPosition === 'before' ? 'border-t-2 border-t-primary' : ''}
603
+ ${dragOverPosition === 'after' ? 'border-b-2 border-b-primary' : ''}
604
+ ${dragOverPosition === 'inside' ? 'bg-primary/5 border-primary/30' : ''}
605
+ `;
606
+
607
+ const isCompact = model?.compactMode ?? false;
608
+ const colVis = model?.columnVisibility ?? { type: true, modified: true, size: true };
609
+ const gridCols = model?.detailsGridTemplateColumns ?? '24px 1fr 96px 128px 80px';
610
+ const showCb = model?.showCheckboxes ?? false;
611
+
612
+ return (
613
+ <div
614
+ className={`grid ${detailsStateClasses}`}
615
+ style={{
616
+ gridTemplateColumns: gridCols,
617
+ columnGap: 16,
618
+ ...debugStyles.container
619
+ }}
620
+ draggable={canDrag}
621
+ onClick={handleClick}
622
+ onDoubleClick={handleDoubleClick}
623
+ onContextMenu={handleContextMenu}
624
+ onDragStart={handleDragStart}
625
+ onDragOver={handleDragOver}
626
+ onDragLeave={handleDragLeave}
627
+ onDrop={handleDrop}
628
+ >
629
+ {showCb && (
630
+ <div className="flex items-center justify-center">
631
+ <input
632
+ type="checkbox"
633
+ checked={isSelected}
634
+ onChange={() => {}}
635
+ onClick={(e) => {
636
+ e.stopPropagation();
637
+ model?.selectItem(item, { ctrl: true });
638
+ }}
639
+ className="h-3.5 w-3.5 rounded border-muted-foreground/50 accent-primary cursor-pointer"
640
+ />
641
+ </div>
642
+ )}
643
+
644
+ <div className="flex items-center justify-center" style={debugStyles.icon}>
645
+ {renderImageOrIcon('small')}
646
+ </div>
647
+
648
+ <div className="truncate font-medium min-w-0" style={debugStyles.text} title={item.name}>
649
+ {isRenaming ? renderNameOrRename() : item.name}
650
+ </div>
651
+
652
+ {!isCompact && colVis.type && (
653
+ <div className="text-sm text-muted-foreground truncate min-w-0" style={debugStyles.metadata}>
654
+ {item.type || 'Unknown'}
655
+ </div>
656
+ )}
657
+
658
+ {!isCompact && colVis.modified && (
659
+ <div className="text-sm text-muted-foreground truncate min-w-0" style={debugStyles.metadata}>
660
+ {item.modified ? item.modified.toLocaleDateString() : '-'}
661
+ </div>
662
+ )}
663
+
664
+ {!isCompact && colVis.size && (
665
+ <div className="text-sm text-muted-foreground text-right truncate min-w-0" style={debugStyles.metadata}>
666
+ {item.size ? `${(item.size / 1024).toFixed(1)} KB` : '-'}
667
+ </div>
668
+ )}
669
+ </div>
670
+ );
671
+ }
672
+
673
+ // AICODE-NOTE: Default list view layout
674
+ const listCompact = model?.compactMode ?? false;
675
+ const listShowCb = model?.showCheckboxes ?? false;
676
+ return (
677
+ <div
678
+ className={baseClasses}
679
+ style={{
680
+ ...debugStyles.container
681
+ }}
682
+ draggable={canDrag}
683
+ onClick={handleClick}
684
+ onDoubleClick={handleDoubleClick}
685
+ onContextMenu={handleContextMenu}
686
+ onDragStart={handleDragStart}
687
+ onDragOver={handleDragOver}
688
+ onDragLeave={handleDragLeave}
689
+ onDrop={handleDrop}
690
+ >
691
+ {listShowCb && (
692
+ <div className="flex items-center flex-shrink-0">
693
+ <input
694
+ type="checkbox"
695
+ checked={isSelected}
696
+ onChange={() => {}}
697
+ onClick={(e) => {
698
+ e.stopPropagation();
699
+ model?.selectItem(item, { ctrl: true });
700
+ }}
701
+ className="h-3.5 w-3.5 rounded border-muted-foreground/50 accent-primary cursor-pointer"
702
+ />
703
+ </div>
704
+ )}
705
+
706
+ <div
707
+ className="text-lg flex-shrink-0 flex items-center"
708
+ style={{
709
+ ...debugStyles.icon
710
+ }}
711
+ >
712
+ {renderImageOrIcon()}
713
+ </div>
714
+
715
+ <div
716
+ className="flex-1 min-w-0"
717
+ style={{
718
+ ...debugStyles.textContainer
719
+ }}
720
+ >
721
+ <div
722
+ className="font-medium truncate"
723
+ style={{
724
+ ...debugStyles.text
725
+ }}
726
+ title={item.name}
727
+ >
728
+ {isRenaming ? renderNameOrRename() : item.name}
729
+ </div>
730
+ {!listCompact && item.size && (
731
+ <div
732
+ className="text-sm text-muted-foreground"
733
+ style={{
734
+ ...debugStyles.metadata
735
+ }}
736
+ >
737
+ {(item.size / 1024).toFixed(1)} KB
738
+ </div>
739
+ )}
740
+ </div>
741
+
742
+ {!listCompact && item.modified && (
743
+ <div
744
+ className="text-xs text-muted-foreground flex-shrink-0"
745
+ style={{
746
+ ...debugStyles.metadata
747
+ }}
748
+ >
749
+ {item.modified.toLocaleDateString()}
750
+ </div>
751
+ )}
752
+ </div>
753
+ );
754
+ });
755
+
756
+ ListItemComponent.displayName = 'ListItem';
757
+
758
+ // AICODE-NOTE: observer handles efficient re-rendering via MobX observable tracking.
759
+ // Custom React.memo was removed because it blocked MobX-triggered re-renders
760
+ // (e.g., thumbnail cache updates) since resolvedThumbnailUrl is computed inside render.
761
+ export const ListItem = ListItemComponent;