@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,205 @@
1
+ import React from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { ListItemsModel } from '../models/ListItemsModel';
4
+
5
+ // AICODE-NOTE: View size controls component for adjusting item sizes
6
+ export interface ViewSizeControlsProps {
7
+ model: ListItemsModel;
8
+ className?: string;
9
+ showSizePresets?: boolean;
10
+ showCustomSliders?: boolean;
11
+ showDebugToggle?: boolean;
12
+ }
13
+
14
+ // AICODE-NOTE: Extract size preset button into separate observer component for MobX reactivity
15
+ interface SizePresetButtonProps {
16
+ preset: { value: 'small' | 'medium' | 'large' | 'extra-large'; label: string };
17
+ isActive: boolean;
18
+ onSizeChange: (size: 'small' | 'medium' | 'large' | 'extra-large') => void;
19
+ }
20
+
21
+ const SizePresetButton = observer<SizePresetButtonProps>(({ preset, isActive, onSizeChange }) => (
22
+ <button
23
+ onClick={() => onSizeChange(preset.value)}
24
+ className={`
25
+ px-3 py-1 text-xs rounded-md border transition-colors
26
+ ${isActive
27
+ ? 'bg-primary text-primary-foreground border-primary'
28
+ : 'bg-background hover:bg-muted border-border'
29
+ }
30
+ `}
31
+ >
32
+ {preset.label}
33
+ </button>
34
+ ));
35
+
36
+ SizePresetButton.displayName = 'SizePresetButton';
37
+
38
+ const ViewSizeControlsComponent: React.FC<ViewSizeControlsProps> = ({
39
+ model,
40
+ className = '',
41
+ showSizePresets = true,
42
+ showCustomSliders = false,
43
+ showDebugToggle = false
44
+ }) => {
45
+ // AICODE-NOTE: Check if current view supports size controls
46
+ const supportsSize = ['grid', 'masonry-horizontal', 'masonry-vertical'].includes(model.currentViewType.id);
47
+
48
+ if (!supportsSize && !showDebugToggle) {
49
+ return null;
50
+ }
51
+
52
+ // AICODE-NOTE: Size preset options
53
+ const sizePresets: Array<{ value: 'small' | 'medium' | 'large' | 'extra-large'; label: string }> = [
54
+ { value: 'small', label: 'Small' },
55
+ { value: 'medium', label: 'Medium' },
56
+ { value: 'large', label: 'Large' },
57
+ { value: 'extra-large', label: 'Extra Large' }
58
+ ];
59
+
60
+ // AICODE-NOTE: Handle size preset change
61
+ const handleSizePresetChange = (size: 'small' | 'medium' | 'large' | 'extra-large') => {
62
+ model.setItemSize(size);
63
+ };
64
+
65
+ // AICODE-NOTE: Handle custom width change
66
+ const handleWidthChange = (event: React.ChangeEvent<HTMLInputElement>) => {
67
+ const width = parseInt(event.target.value, 10);
68
+ model.setCustomItemWidth(width);
69
+ };
70
+
71
+ // AICODE-NOTE: Handle custom height change
72
+ const handleHeightChange = (event: React.ChangeEvent<HTMLInputElement>) => {
73
+ const height = parseInt(event.target.value, 10);
74
+ model.setCustomItemHeight(height);
75
+ };
76
+
77
+ // AICODE-NOTE: Handle items per row change
78
+ const handleItemsPerRowChange = (event: React.ChangeEvent<HTMLInputElement>) => {
79
+ const value = parseInt(event.target.value, 10);
80
+ model.setItemsPerRow(value);
81
+ };
82
+
83
+ // AICODE-NOTE: Handle auto items per row
84
+ const handleAutoItemsPerRow = () => {
85
+ model.setItemsPerRow('auto');
86
+ };
87
+
88
+ return (
89
+ <div className={`view-size-controls space-y-3 ${className}`}>
90
+ {/* AICODE-NOTE: Debug toggle for all views */}
91
+ {showDebugToggle && (
92
+ <div className="debug-toggle flex items-center gap-2">
93
+ <input
94
+ type="checkbox"
95
+ id="debug-visualization"
96
+ checked={model.debugVisualization || false}
97
+ onChange={(e) => model.setDebugVisualization?.(e.target.checked)}
98
+ className="w-4 h-4 text-primary bg-background border-border rounded focus:ring-primary focus:ring-2"
99
+ />
100
+ <label htmlFor="debug-visualization" className="text-sm font-medium text-foreground">
101
+ Debug Visualization
102
+ </label>
103
+ <span className="text-xs text-muted-foreground">
104
+ Show colored borders for layout debugging
105
+ </span>
106
+ </div>
107
+ )}
108
+
109
+ {/* AICODE-NOTE: Size preset buttons */}
110
+ {supportsSize && showSizePresets && (
111
+ <div className="size-presets flex items-center gap-2">
112
+ <span className="text-sm font-medium text-muted-foreground">Size:</span>
113
+ <div className="flex gap-1">
114
+ {sizePresets.map((preset) => (
115
+ <SizePresetButton
116
+ key={preset.value}
117
+ preset={preset}
118
+ isActive={model.itemSize === preset.value}
119
+ onSizeChange={handleSizePresetChange}
120
+ />
121
+ ))}
122
+ </div>
123
+ </div>
124
+ )}
125
+
126
+ {/* AICODE-NOTE: Items per row slider */}
127
+ {supportsSize && (
128
+ <div className="items-per-row flex items-center gap-2">
129
+ <span className="text-sm font-medium text-muted-foreground">Items per row:</span>
130
+ <button
131
+ onClick={handleAutoItemsPerRow}
132
+ className={`
133
+ px-2 py-1 text-xs rounded border transition-colors
134
+ ${model.itemsPerRow === 'auto'
135
+ ? 'bg-primary text-primary-foreground border-primary'
136
+ : 'bg-background hover:bg-muted border-border'
137
+ }
138
+ `}
139
+ >
140
+ Auto
141
+ </button>
142
+ <input
143
+ type="range"
144
+ min="1"
145
+ max="8"
146
+ step="1"
147
+ value={typeof model.itemsPerRow === 'number' ? model.itemsPerRow : 4}
148
+ onChange={handleItemsPerRowChange}
149
+ className="w-24 h-2 bg-muted rounded-lg appearance-none cursor-pointer"
150
+ />
151
+ <span className="text-xs text-muted-foreground w-4">
152
+ {typeof model.itemsPerRow === 'number' ? model.itemsPerRow : 'Auto'}
153
+ </span>
154
+ </div>
155
+ )}
156
+
157
+ {/* AICODE-NOTE: Custom size sliders */}
158
+ {supportsSize && showCustomSliders && (
159
+ <div className="custom-sliders flex items-center gap-4">
160
+ <div className="width-slider flex items-center gap-2">
161
+ <span className="text-xs text-muted-foreground">W:</span>
162
+ <input
163
+ type="range"
164
+ min="120"
165
+ max="600"
166
+ step="10"
167
+ value={model.customItemWidth}
168
+ onChange={handleWidthChange}
169
+ className="w-20 h-2 bg-muted rounded-lg appearance-none cursor-pointer"
170
+ />
171
+ <span className="text-xs text-muted-foreground w-8">
172
+ {model.customItemWidth}
173
+ </span>
174
+ </div>
175
+
176
+ <div className="height-slider flex items-center gap-2">
177
+ <span className="text-xs text-muted-foreground">H:</span>
178
+ <input
179
+ type="range"
180
+ min="80"
181
+ max="500"
182
+ step="10"
183
+ value={model.customItemHeight}
184
+ onChange={handleHeightChange}
185
+ className="w-20 h-2 bg-muted rounded-lg appearance-none cursor-pointer"
186
+ />
187
+ <span className="text-xs text-muted-foreground w-8">
188
+ {model.customItemHeight}
189
+ </span>
190
+ </div>
191
+ </div>
192
+ )}
193
+
194
+ {/* AICODE-NOTE: Current dimensions display */}
195
+ {supportsSize && (
196
+ <div className="dimensions-display text-xs text-muted-foreground">
197
+ {model.itemDimensions.width} × {model.itemDimensions.height}
198
+ </div>
199
+ )}
200
+ </div>
201
+ );
202
+ };
203
+
204
+ // AICODE-NOTE: Export memoized component for performance
205
+ export const ViewSizeControls = observer(ViewSizeControlsComponent);
@@ -0,0 +1,312 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { ListViewType } from '../types/ListTypes';
4
+ import { List, Grid, Table, LayoutList, Columns, MoreHorizontal, ChevronDown, SquareStack } from 'lucide-react';
5
+
6
+ export interface ViewTypeSelectorProps {
7
+ viewTypes: ListViewType[];
8
+ currentViewType: ListViewType;
9
+ onViewTypeChange: (viewType: ListViewType) => void;
10
+ variant?: 'buttons' | 'tabs' | 'dropdown';
11
+ size?: 'sm' | 'md' | 'lg';
12
+ className?: string;
13
+ }
14
+
15
+ const PRIMARY_VIEW_IDS = new Set(['list', 'grid', 'details']);
16
+
17
+ const getViewTypeIcon = (viewTypeId: string) => {
18
+ switch (viewTypeId) {
19
+ case 'list':
20
+ return List;
21
+ case 'grid':
22
+ return Grid;
23
+ case 'details':
24
+ return Table;
25
+ case 'masonry-horizontal':
26
+ return LayoutList;
27
+ case 'masonry-vertical':
28
+ return Columns;
29
+ case 'treemap':
30
+ return SquareStack;
31
+ default:
32
+ return List;
33
+ }
34
+ };
35
+
36
+ interface ViewTypeButtonProps {
37
+ viewType: ListViewType;
38
+ isActive: boolean;
39
+ onViewTypeChange: (viewType: ListViewType) => void;
40
+ sizeClasses: string;
41
+ variant: 'buttons' | 'tabs' | 'dropdown';
42
+ }
43
+
44
+ const ViewTypeButton = observer<ViewTypeButtonProps>(({
45
+ viewType,
46
+ isActive,
47
+ onViewTypeChange,
48
+ sizeClasses,
49
+ variant
50
+ }) => {
51
+ const Icon = getViewTypeIcon(viewType.id);
52
+
53
+ if (variant === 'buttons') {
54
+ return (
55
+ <button
56
+ onClick={() => onViewTypeChange(viewType)}
57
+ className={`
58
+ ${sizeClasses}
59
+ flex items-center gap-2 font-medium transition-all
60
+ first:rounded-l-lg last:rounded-r-lg
61
+ ${isActive
62
+ ? 'bg-primary text-primary-foreground shadow-sm'
63
+ : 'hover:bg-muted text-muted-foreground hover:text-foreground'
64
+ }
65
+ `}
66
+ title={viewType.name}
67
+ >
68
+ <Icon className="w-4 h-4" />
69
+ <span className="hidden sm:inline">{viewType.name}</span>
70
+ </button>
71
+ );
72
+ }
73
+
74
+ if (variant === 'tabs') {
75
+ return (
76
+ <button
77
+ onClick={() => onViewTypeChange(viewType)}
78
+ className={`
79
+ ${sizeClasses}
80
+ flex items-center gap-2 font-medium transition-all
81
+ border-b-2 -mb-px
82
+ ${isActive
83
+ ? 'border-primary text-primary'
84
+ : 'border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground'
85
+ }
86
+ `}
87
+ >
88
+ <Icon className="w-4 h-4" />
89
+ {viewType.name}
90
+ </button>
91
+ );
92
+ }
93
+
94
+ return null;
95
+ });
96
+
97
+ ViewTypeButton.displayName = 'ViewTypeButton';
98
+
99
+ /** "More views" dropdown for advanced view types (masonry, treemap, etc.) */
100
+ const MoreViewsDropdown = observer<{
101
+ viewTypes: ListViewType[];
102
+ currentViewType: ListViewType;
103
+ onViewTypeChange: (viewType: ListViewType) => void;
104
+ sizeClasses: string;
105
+ }>(({ viewTypes, currentViewType, onViewTypeChange, sizeClasses }) => {
106
+ const [open, setOpen] = useState(false);
107
+ const ref = useRef<HTMLDivElement>(null);
108
+ const isAdvancedActive = viewTypes.some(vt => vt.id === currentViewType.id);
109
+
110
+ useEffect(() => {
111
+ if (!open) return;
112
+ const handleClickOutside = (e: MouseEvent) => {
113
+ if (ref.current && !ref.current.contains(e.target as Node)) {
114
+ setOpen(false);
115
+ }
116
+ };
117
+ document.addEventListener('mousedown', handleClickOutside);
118
+ return () => document.removeEventListener('mousedown', handleClickOutside);
119
+ }, [open]);
120
+
121
+ if (viewTypes.length === 0) return null;
122
+
123
+ // If an advanced view is active, show its icon instead of the generic "more" icon
124
+ const ActiveIcon = isAdvancedActive ? getViewTypeIcon(currentViewType.id) : MoreHorizontal;
125
+
126
+ return (
127
+ <div ref={ref} className="relative">
128
+ <button
129
+ onClick={() => setOpen(prev => !prev)}
130
+ className={`
131
+ ${sizeClasses}
132
+ flex items-center gap-1 font-medium transition-all rounded-lg
133
+ ${isAdvancedActive
134
+ ? 'bg-primary text-primary-foreground shadow-sm'
135
+ : 'hover:bg-muted text-muted-foreground hover:text-foreground'
136
+ }
137
+ `}
138
+ title={isAdvancedActive ? currentViewType.name : 'More views'}
139
+ >
140
+ <ActiveIcon className="w-4 h-4" />
141
+ {isAdvancedActive && <span className="hidden sm:inline">{currentViewType.name}</span>}
142
+ <ChevronDown className="w-3 h-3" />
143
+ </button>
144
+
145
+ {open && (
146
+ <div className="absolute top-full right-0 mt-1 z-50 min-w-[180px] border rounded-lg bg-background shadow-md py-1">
147
+ {viewTypes.map((viewType) => {
148
+ const Icon = getViewTypeIcon(viewType.id);
149
+ const isActive = currentViewType.id === viewType.id;
150
+ return (
151
+ <button
152
+ key={viewType.id}
153
+ className={`
154
+ w-full flex items-center gap-2 px-3 py-2 text-sm transition-colors
155
+ ${isActive
156
+ ? 'bg-primary text-primary-foreground'
157
+ : 'text-foreground hover:bg-muted'
158
+ }
159
+ `}
160
+ onClick={() => {
161
+ onViewTypeChange(viewType);
162
+ setOpen(false);
163
+ }}
164
+ >
165
+ <Icon className="w-4 h-4" />
166
+ <span>{viewType.name}</span>
167
+ </button>
168
+ );
169
+ })}
170
+ </div>
171
+ )}
172
+ </div>
173
+ );
174
+ });
175
+
176
+ MoreViewsDropdown.displayName = 'MoreViewsDropdown';
177
+
178
+ const ViewTypeSelectorComponent = observer<ViewTypeSelectorProps>(({
179
+ viewTypes,
180
+ currentViewType,
181
+ onViewTypeChange,
182
+ variant = 'buttons',
183
+ size = 'md',
184
+ className = ''
185
+ }) => {
186
+ const sizeClasses = {
187
+ sm: 'px-2 py-1 text-xs',
188
+ md: 'px-3 py-2 text-sm',
189
+ lg: 'px-4 py-3 text-base'
190
+ };
191
+
192
+ if (variant === 'buttons') {
193
+ const primaryViews = viewTypes.filter(vt => PRIMARY_VIEW_IDS.has(vt.id));
194
+ const advancedViews = viewTypes.filter(vt => !PRIMARY_VIEW_IDS.has(vt.id));
195
+
196
+ return (
197
+ <div className={`flex items-center gap-1 ${className}`}>
198
+ <div className="flex rounded-lg border bg-background">
199
+ {primaryViews.map((viewType) => (
200
+ <ViewTypeButton
201
+ key={viewType.id}
202
+ viewType={viewType}
203
+ isActive={currentViewType.id === viewType.id}
204
+ onViewTypeChange={onViewTypeChange}
205
+ sizeClasses={sizeClasses[size]}
206
+ variant={variant}
207
+ />
208
+ ))}
209
+ </div>
210
+ <MoreViewsDropdown
211
+ viewTypes={advancedViews}
212
+ currentViewType={currentViewType}
213
+ onViewTypeChange={onViewTypeChange}
214
+ sizeClasses={sizeClasses[size]}
215
+ />
216
+ </div>
217
+ );
218
+ }
219
+
220
+ if (variant === 'tabs') {
221
+ return (
222
+ <div className={`flex border-b ${className}`}>
223
+ {viewTypes.map((viewType) => (
224
+ <ViewTypeButton
225
+ key={viewType.id}
226
+ viewType={viewType}
227
+ isActive={currentViewType.id === viewType.id}
228
+ onViewTypeChange={onViewTypeChange}
229
+ sizeClasses={sizeClasses[size]}
230
+ variant={variant}
231
+ />
232
+ ))}
233
+ </div>
234
+ );
235
+ }
236
+
237
+ if (variant === 'dropdown') {
238
+ const [dropdownOpen, setDropdownOpen] = useState(false);
239
+ const dropdownRef = useRef<HTMLDivElement>(null);
240
+ const CurrentIcon = getViewTypeIcon(currentViewType.id);
241
+
242
+ useEffect(() => {
243
+ if (!dropdownOpen) return;
244
+ const handleClickOutside = (e: MouseEvent) => {
245
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
246
+ setDropdownOpen(false);
247
+ }
248
+ };
249
+ document.addEventListener('mousedown', handleClickOutside);
250
+ return () => document.removeEventListener('mousedown', handleClickOutside);
251
+ }, [dropdownOpen]);
252
+
253
+ return (
254
+ <div ref={dropdownRef} className={`relative ${className}`}>
255
+ <button
256
+ className={`
257
+ ${sizeClasses[size]}
258
+ flex items-center gap-2 font-medium border rounded-lg bg-background hover:bg-muted transition-colors
259
+ `}
260
+ onClick={() => setDropdownOpen((prev) => !prev)}
261
+ >
262
+ <CurrentIcon className="w-4 h-4" />
263
+ <span>{currentViewType.name}</span>
264
+ <ChevronDown className="w-4 h-4" />
265
+ </button>
266
+
267
+ {dropdownOpen && (
268
+ <div className="absolute top-full left-0 mt-1 z-50 min-w-[160px] border rounded-lg bg-background shadow-md py-1">
269
+ {viewTypes.map((viewType) => {
270
+ const Icon = getViewTypeIcon(viewType.id);
271
+ const isActive = currentViewType.id === viewType.id;
272
+ return (
273
+ <button
274
+ key={viewType.id}
275
+ className={`
276
+ w-full flex items-center gap-2 px-3 py-2 text-sm transition-colors
277
+ ${isActive
278
+ ? 'bg-primary text-primary-foreground'
279
+ : 'text-foreground hover:bg-muted'
280
+ }
281
+ `}
282
+ onClick={() => {
283
+ onViewTypeChange(viewType);
284
+ setDropdownOpen(false);
285
+ }}
286
+ >
287
+ <Icon className="w-4 h-4" />
288
+ <span>{viewType.name}</span>
289
+ </button>
290
+ );
291
+ })}
292
+ </div>
293
+ )}
294
+ </div>
295
+ );
296
+ }
297
+
298
+ return null;
299
+ });
300
+
301
+ ViewTypeSelectorComponent.displayName = 'ViewTypeSelector';
302
+
303
+ export const ViewTypeSelector = React.memo(ViewTypeSelectorComponent, (prevProps, nextProps) => {
304
+ return (
305
+ prevProps.currentViewType.id === nextProps.currentViewType.id &&
306
+ prevProps.viewTypes.length === nextProps.viewTypes.length &&
307
+ prevProps.variant === nextProps.variant &&
308
+ prevProps.className === nextProps.className &&
309
+ prevProps.size === nextProps.size &&
310
+ prevProps.viewTypes.every((vt, index) => vt.id === nextProps.viewTypes[index]?.id)
311
+ );
312
+ });