@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.
- package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
- package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
- package/dist/FileBrowserContext-B6jixa2j.js +11 -0
- package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
- package/dist/calendar-DSlrbHoj.js +761 -0
- package/dist/calendar-DSlrbHoj.js.map +1 -0
- package/dist/calendar.d.ts +3 -0
- package/dist/calendar.js +3 -0
- package/dist/contacts-DQXTZzHc.js +539 -0
- package/dist/contacts-DQXTZzHc.js.map +1 -0
- package/dist/contacts.d.ts +3 -0
- package/dist/contacts.js +3 -0
- package/dist/file-browser-m5atC3kF.js +6755 -0
- package/dist/file-browser-m5atC3kF.js.map +1 -0
- package/dist/file-browser.d.ts +11 -0
- package/dist/file-browser.js +9 -0
- package/dist/git-B55e6LL-.js +561 -0
- package/dist/git-B55e6LL-.js.map +1 -0
- package/dist/git.d.ts +2 -0
- package/dist/git.js +3 -0
- package/dist/iconMap-V4B8P-Uh.js +206 -0
- package/dist/iconMap-V4B8P-Uh.js.map +1 -0
- package/dist/icons-CIsIOZXR.js +0 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index-BNmNIWBL.d.ts +71 -0
- package/dist/index-BNmNIWBL.d.ts.map +1 -0
- package/dist/index-Bryv_GCG.d.ts +1481 -0
- package/dist/index-Bryv_GCG.d.ts.map +1 -0
- package/dist/index-CuQIjSXs.d.ts +134 -0
- package/dist/index-CuQIjSXs.d.ts.map +1 -0
- package/dist/index-DSu19mq0.d.ts +153 -0
- package/dist/index-DSu19mq0.d.ts.map +1 -0
- package/dist/index-DmsyeHFr.d.ts +149 -0
- package/dist/index-DmsyeHFr.d.ts.map +1 -0
- package/dist/index-DxnJ8FYM.d.ts +17 -0
- package/dist/index-DxnJ8FYM.d.ts.map +1 -0
- package/dist/index-DzfY1Tok.d.ts +32 -0
- package/dist/index-DzfY1Tok.d.ts.map +1 -0
- package/dist/index-Ml_SgiKa.d.ts +1847 -0
- package/dist/index-Ml_SgiKa.d.ts.map +1 -0
- package/dist/index-kHr9udZD.d.ts +1025 -0
- package/dist/index-kHr9udZD.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/layout-Ca_4r8ka.js +89 -0
- package/dist/layout-Ca_4r8ka.js.map +1 -0
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +5 -0
- package/dist/list-CxfT6hix.js +6831 -0
- package/dist/list-CxfT6hix.js.map +1 -0
- package/dist/list.d.ts +2 -0
- package/dist/list.js +5 -0
- package/dist/media-DZ292aKK.js +557 -0
- package/dist/media-DZ292aKK.js.map +1 -0
- package/dist/media.d.ts +3 -0
- package/dist/media.js +3 -0
- package/dist/tree-Dd9Z0Aso.js +3351 -0
- package/dist/tree-Dd9Z0Aso.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +6 -0
- package/dist/types-common-CB3kRek8.d.ts +26 -0
- package/dist/types-common-CB3kRek8.d.ts.map +1 -0
- package/dist/utils-B4fdKKsy.js +3 -0
- package/package.json +109 -0
- package/src/calendar/AgendaView.tsx +37 -0
- package/src/calendar/CalendarBrowser.tsx +90 -0
- package/src/calendar/CalendarModel.ts +142 -0
- package/src/calendar/CalendarSidebar.tsx +81 -0
- package/src/calendar/DayView.tsx +76 -0
- package/src/calendar/EventCard.tsx +51 -0
- package/src/calendar/MockCalendarProvider.ts +98 -0
- package/src/calendar/MonthView.tsx +77 -0
- package/src/calendar/WeekView.tsx +129 -0
- package/src/calendar/index.ts +18 -0
- package/src/calendar/types.ts +25 -0
- package/src/contacts/ContactAvatar.tsx +35 -0
- package/src/contacts/ContactBrowser.tsx +56 -0
- package/src/contacts/ContactCard.tsx +37 -0
- package/src/contacts/ContactDetail.tsx +63 -0
- package/src/contacts/ContactGroupSidebar.tsx +40 -0
- package/src/contacts/ContactList.tsx +32 -0
- package/src/contacts/ContactListModel.ts +120 -0
- package/src/contacts/MockContactProvider.ts +77 -0
- package/src/contacts/index.ts +17 -0
- package/src/contacts/types.ts +26 -0
- package/src/demos/CalendarBrowserDemo.tsx +15 -0
- package/src/demos/ContactBrowserDemo.tsx +15 -0
- package/src/demos/MediaBrowserDemo.tsx +15 -0
- package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
- package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
- package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
- package/src/file-browser/adapters/README.md +504 -0
- package/src/file-browser/adapters/index.ts +27 -0
- package/src/file-browser/adapters/types.ts +70 -0
- package/src/file-browser/architecture.md +645 -0
- package/src/file-browser/components/CreateItemDialog.tsx +71 -0
- package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
- package/src/file-browser/components/FileBrowser.tsx +473 -0
- package/src/file-browser/components/FileBrowserContent.tsx +209 -0
- package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
- package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
- package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
- package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
- package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
- package/src/file-browser/components/PreviewPane.tsx +146 -0
- package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
- package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
- package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
- package/src/file-browser/components/UploadProgress.tsx +123 -0
- package/src/file-browser/components/ViewerHost.tsx +208 -0
- package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
- package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
- package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
- package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
- package/src/file-browser/components/shared/FileIcon.tsx +169 -0
- package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
- package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
- package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
- package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
- package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
- package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
- package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
- package/src/file-browser/context/FileBrowserContext.tsx +13 -0
- package/src/file-browser/examples/BasicUsage.tsx +20 -0
- package/src/file-browser/index.ts +98 -0
- package/src/file-browser/models/FileBrowserModel.ts +623 -0
- package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
- package/src/file-browser/models/NavigationManagerModel.ts +312 -0
- package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
- package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
- package/src/file-browser/models/SelectionManagerModel.ts +252 -0
- package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
- package/src/file-browser/models/UploadModel.ts +147 -0
- package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
- package/src/file-browser/models/ViewerHostModel.ts +44 -0
- package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
- package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
- package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
- package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
- package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
- package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
- package/src/file-browser/providers/FileSystemProvider.ts +401 -0
- package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
- package/src/file-browser/providers/GitProvider.ts +337 -0
- package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
- package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
- package/src/file-browser/providers/MemoryProvider.ts +303 -0
- package/src/file-browser/providers/index.ts +4 -0
- package/src/file-browser/registry/ViewerRegistry.ts +551 -0
- package/src/file-browser/registry/types.ts +144 -0
- package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
- package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
- package/src/file-browser/tasks.md +537 -0
- package/src/file-browser/types/FileBrowserTypes.ts +126 -0
- package/src/file-browser/types/ProviderTypes.ts +155 -0
- package/src/file-browser/types/UITypes.ts +235 -0
- package/src/file-browser/types/ViewModeTypes.ts +150 -0
- package/src/file-browser/utils/gestures.ts +327 -0
- package/src/file-browser/utils/performance.ts +563 -0
- package/src/file-browser/viewers/ImageViewer.tsx +163 -0
- package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
- package/src/file-browser/viewers/TextViewer.tsx +95 -0
- package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
- package/src/file-browser/viewers/index.ts +61 -0
- package/src/git/BranchList.tsx +128 -0
- package/src/git/CommitGraph.tsx +239 -0
- package/src/git/CommitList.tsx +258 -0
- package/src/git/DiffViewer.tsx +219 -0
- package/src/git/index.ts +4 -0
- package/src/icons/iconMap.ts +146 -0
- package/src/icons/index.ts +9 -0
- package/src/index.ts +13 -0
- package/src/layout/README.md +307 -0
- package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
- package/src/layout/examples/SimpleExample.tsx +60 -0
- package/src/layout/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/list/README.md +303 -0
- package/src/list/architecture.md +807 -0
- package/src/list/components/CalculatedGridView.tsx +252 -0
- package/src/list/components/DragPreview.tsx +102 -0
- package/src/list/components/ListContextMenu.tsx +274 -0
- package/src/list/components/ListItem.tsx +761 -0
- package/src/list/components/ListItems.tsx +919 -0
- package/src/list/components/MasonryView.tsx +241 -0
- package/src/list/components/SearchFilter.tsx +44 -0
- package/src/list/components/TreemapView.tsx +709 -0
- package/src/list/components/ViewSizeControls.tsx +205 -0
- package/src/list/components/ViewTypeSelector.tsx +312 -0
- package/src/list/components/VirtualizedDetailsView.tsx +231 -0
- package/src/list/components/VirtualizedGrid.tsx +164 -0
- package/src/list/components/VirtualizedList.tsx +154 -0
- package/src/list/components/VirtualizedMasonryView.tsx +344 -0
- package/src/list/components/shared/EmptyState.tsx +103 -0
- package/src/list/components/shared/ErrorBoundary.tsx +123 -0
- package/src/list/components/shared/ErrorDisplay.tsx +100 -0
- package/src/list/components/shared/ListLoader.tsx +146 -0
- package/src/list/components/shared/LoadingIndicator.tsx +80 -0
- package/src/list/index.ts +92 -0
- package/src/list/models/ListItemsModel.ts +1301 -0
- package/src/list/models/TreemapModel.ts +204 -0
- package/src/list/providers/ListItemsProvider.ts +313 -0
- package/src/list/providers/TestListProvider.ts +604 -0
- package/src/list/tasks.md +937 -0
- package/src/list/types/ListTypes.ts +178 -0
- package/src/list/utils/BenchmarkLogger.ts +243 -0
- package/src/list/utils/DragDropManager.ts +320 -0
- package/src/list/utils/GridLayoutCalculator.ts +290 -0
- package/src/list/utils/ListAccessibility.ts +367 -0
- package/src/list/utils/ListKeyboard.ts +414 -0
- package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
- package/src/list/utils/MasonryLayoutEngine.ts +401 -0
- package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
- package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
- package/src/media/AlbumSidebar.tsx +48 -0
- package/src/media/MediaBrowser.tsx +92 -0
- package/src/media/MediaBrowserModel.ts +138 -0
- package/src/media/MediaGrid.tsx +50 -0
- package/src/media/MediaList.tsx +49 -0
- package/src/media/MediaPreview.tsx +63 -0
- package/src/media/MediaTimeline.tsx +38 -0
- package/src/media/MockMediaProvider.ts +70 -0
- package/src/media/index.ts +18 -0
- package/src/media/types.ts +21 -0
- package/src/styles/variables.css +60 -0
- package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
- package/src/tree/__tests__/TreeModel.test.ts +16 -0
- package/src/tree/architecture.md +530 -0
- package/src/tree/components/Tree.tsx +283 -0
- package/src/tree/components/TreeCheckbox.tsx +147 -0
- package/src/tree/components/TreeContextMenu.tsx +139 -0
- package/src/tree/components/TreeNodeList.tsx +329 -0
- package/src/tree/components/TreeTable.tsx +382 -0
- package/src/tree/index.ts +58 -0
- package/src/tree/models/TreeModel.ts +839 -0
- package/src/tree/providers/SimpleTreeProvider.ts +463 -0
- package/src/tree/providers/TestTreeProvider.ts +946 -0
- package/src/tree/providers/TreeProvider.ts +308 -0
- package/src/tree/tasks.md +2046 -0
- package/src/tree/types/TreeTypes.ts +279 -0
- package/src/tree/utils/SelectionTheme.ts +150 -0
- package/src/tree/utils/logger.ts +203 -0
- package/src/types-common.ts +21 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
# ListItemsComponent Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The ListItemsComponent is a high-performance, virtualized list component built with React, MobX, and TypeScript. It provides a flexible, accessible, and extensible list interface that can work with any data source through the Provider pattern. The component supports multiple view types (list, grid, details, thumbnails) and can handle massive datasets through virtualization. The provider can work with or without React, supporting both web and non-web environments.
|
|
6
|
+
|
|
7
|
+
**🎯 Primary Use Cases**: File explorers (Windows Explorer, macOS Finder), media galleries, data tables, search results, any large dataset display requiring multiple view modes.
|
|
8
|
+
|
|
9
|
+
## Design Principles
|
|
10
|
+
|
|
11
|
+
- **Provider-Driven Architecture**: ListItemsProvider model drives ALL behavior (selection, rendering, virtualization, view types)
|
|
12
|
+
- **Direct Provider Injection**: No registry pattern - provider passed directly to ListItems component
|
|
13
|
+
- **MobX Best Practices**:
|
|
14
|
+
- Use `makeAutoObservable(this, {})` in constructors (NO decorators)
|
|
15
|
+
- Use `flow` for async actions
|
|
16
|
+
- Use `observable.map()` instead of plain Map for reactive collections
|
|
17
|
+
- Use composition over inheritance
|
|
18
|
+
- **All State in Provider**: multiSelect, dragDrop, virtualization, view types configured in provider
|
|
19
|
+
- **Callback-Based**: ListItems component calls provider callbacks for all interactions
|
|
20
|
+
- **Item-Level Customization**: Per-item context menus and render overrides
|
|
21
|
+
- **Environment Agnostic**: Provider works with and without React
|
|
22
|
+
- **Component Composition**: Small, focused components for maintainability
|
|
23
|
+
- **Accessibility First**: Full ARIA support for screen readers and keyboard navigation
|
|
24
|
+
- **Performance**: Mandatory virtualization support for huge datasets (react-window/react-virtualized)
|
|
25
|
+
- **Type Safety**: Comprehensive TypeScript coverage
|
|
26
|
+
- **Responsive Design**: Grid layouts adapt to container width, thumbnail sizes configurable
|
|
27
|
+
|
|
28
|
+
## MobX Best Practices (CRITICAL)
|
|
29
|
+
|
|
30
|
+
**⚠️ STRICT REQUIREMENTS - These patterns must be followed exactly:**
|
|
31
|
+
|
|
32
|
+
### 1. No Decorators - Use makeAutoObservable
|
|
33
|
+
```typescript
|
|
34
|
+
// ❌ NEVER use decorators
|
|
35
|
+
class ListItemsModel {
|
|
36
|
+
@observable items = []
|
|
37
|
+
@action selectItem() {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ✅ ALWAYS use makeAutoObservable
|
|
41
|
+
class ListItemsModel {
|
|
42
|
+
items = []
|
|
43
|
+
|
|
44
|
+
constructor() {
|
|
45
|
+
makeAutoObservable(this, {}); // Empty overrides object
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
selectItem() {} // Automatically becomes action
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Observable Collections - Use observable.map()
|
|
53
|
+
```typescript
|
|
54
|
+
// ❌ NEVER use plain Map/Set
|
|
55
|
+
selectedItems = new Map<string, ListItemData>()
|
|
56
|
+
itemStates = new Set<string>()
|
|
57
|
+
|
|
58
|
+
// ✅ ALWAYS use observable.map()
|
|
59
|
+
selectedItems = observable.map<string, ListItemData>()
|
|
60
|
+
itemStates = observable.map<string, ItemState>()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Async Actions - Use flow
|
|
64
|
+
```typescript
|
|
65
|
+
// ❌ NEVER use async/await directly in actions
|
|
66
|
+
async loadItems() {
|
|
67
|
+
this.isLoading = true;
|
|
68
|
+
const result = await api.loadItems();
|
|
69
|
+
this.items = result; // ⚠️ Not in action!
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ✅ ALWAYS use flow for async operations
|
|
73
|
+
loadItems = flow(function* (this: ListItemsModel) {
|
|
74
|
+
this.isLoading = true;
|
|
75
|
+
const result = yield api.loadItems();
|
|
76
|
+
this.items = result; // ✅ Automatically in action
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Composition Over Inheritance
|
|
81
|
+
```typescript
|
|
82
|
+
// ❌ NEVER use inheritance (MobX issues)
|
|
83
|
+
class BaseModel {}
|
|
84
|
+
class ListItemsModel extends BaseModel {}
|
|
85
|
+
|
|
86
|
+
// ✅ ALWAYS use composition
|
|
87
|
+
class ListItemsModel {
|
|
88
|
+
selectionManager: ListSelectionModel;
|
|
89
|
+
viewManager: ViewTypeManager;
|
|
90
|
+
virtualizationManager: VirtualizationModel;
|
|
91
|
+
|
|
92
|
+
constructor() {
|
|
93
|
+
makeAutoObservable(this, {});
|
|
94
|
+
this.selectionManager = new ListSelectionModel(this);
|
|
95
|
+
this.viewManager = new ViewTypeManager(this);
|
|
96
|
+
this.virtualizationManager = new VirtualizationModel(this);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 5. Observer Components for Lists
|
|
102
|
+
```typescript
|
|
103
|
+
// ❌ NEVER render observables directly in map
|
|
104
|
+
const ItemList = observer(() => (
|
|
105
|
+
<div>
|
|
106
|
+
{items.map(item => <div key={item.id}>{item.name}</div>)} {/* Won't react! */}
|
|
107
|
+
</div>
|
|
108
|
+
))
|
|
109
|
+
|
|
110
|
+
// ✅ ALWAYS extract to separate observer components
|
|
111
|
+
const ListItem = observer(({ item }) => <div>{item.name}</div>)
|
|
112
|
+
|
|
113
|
+
const ItemList = observer(() => (
|
|
114
|
+
<div>
|
|
115
|
+
{items.map(item => <ListItem key={item.id} item={item} />)}
|
|
116
|
+
</div>
|
|
117
|
+
))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 6. Icon Handling Pattern (CRITICAL)
|
|
121
|
+
```typescript
|
|
122
|
+
// AICODE-NOTE: Icon handling pattern to avoid React component rendering bugs
|
|
123
|
+
|
|
124
|
+
// ❌ NEVER try to render React components directly from string props
|
|
125
|
+
{menuItem.icon && <menuItem.icon className="w-4 h-4" />} // JSX element type error!
|
|
126
|
+
|
|
127
|
+
// ✅ ALWAYS check type and handle appropriately
|
|
128
|
+
{menuItem.icon && (
|
|
129
|
+
<span className="w-4 h-4 flex-shrink-0 flex items-center justify-center">
|
|
130
|
+
{typeof menuItem.icon === 'string' ? (
|
|
131
|
+
<span className="text-sm">{getIconEmoji(menuItem.icon)}</span>
|
|
132
|
+
) : (
|
|
133
|
+
React.createElement(menuItem.icon, { className: 'w-4 h-4' })
|
|
134
|
+
)}
|
|
135
|
+
</span>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
// ✅ Provider pattern: Return string identifiers, map to emojis/components in UI
|
|
139
|
+
const getIconEmoji = (iconName: string): string => {
|
|
140
|
+
const iconMap: Record<string, string> = {
|
|
141
|
+
'eye': '👁️', 'edit': '✏️', 'trash': '🗑️', 'folder-open': '📂',
|
|
142
|
+
'copy': '📋', 'scissors': '✂️', 'archive': '📦'
|
|
143
|
+
};
|
|
144
|
+
return iconMap[iconName] || iconName;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// ✅ For complex icons, use React.createElement instead of JSX
|
|
148
|
+
React.createElement(IconComponent, { className: 'w-4 h-4' })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Architecture Overview
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
ListItemsComponent
|
|
155
|
+
↓ receives
|
|
156
|
+
ListItemsProvider (MobX Model)
|
|
157
|
+
↓ provides data & callbacks
|
|
158
|
+
ListItemsModel (MobX State)
|
|
159
|
+
↓ manages
|
|
160
|
+
ListItems Components (React UI + Virtualization)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Core Components
|
|
164
|
+
|
|
165
|
+
### 1. ListItemsProvider (Model Layer)
|
|
166
|
+
**Location**: `/providers/ListItemsProvider.ts`
|
|
167
|
+
|
|
168
|
+
**Purpose**: Defines the contract for data loading, item rendering, view types, and interaction callbacks. Works independently of React.
|
|
169
|
+
|
|
170
|
+
**Key Features**:
|
|
171
|
+
- Data loading methods (`loadItems`, `loadRange`, `refresh`, `search`)
|
|
172
|
+
- View type management (`getSupportedViewTypes`, `getViewTypeConfig`)
|
|
173
|
+
- Per-item context menus (`getItemContextMenu`, `getMultiItemContextMenu`)
|
|
174
|
+
- Item rendering overrides (`getItemRenderer`, `getDefaultRenderer`)
|
|
175
|
+
- Capability checks (`canSelect`, `canDrag`, `canDrop`, `canEdit`)
|
|
176
|
+
- Event callbacks (`onSelectionChange`, `onDragDrop`, `onContextMenuAction`, `onViewTypeChange`)
|
|
177
|
+
- Non-React item rendering (`ListItemRenderer.renderText`, `renderIcon`, `renderThumbnail`)
|
|
178
|
+
- Virtualization configuration (`getVirtualizationConfig`, `getItemHeight`)
|
|
179
|
+
|
|
180
|
+
### 2. ListItemsModel (State Layer)
|
|
181
|
+
**Location**: `/models/ListItemsModel.ts`
|
|
182
|
+
|
|
183
|
+
**Purpose**: MobX observable state container that manages list data, selection, view types, and UI state.
|
|
184
|
+
|
|
185
|
+
**State Management**:
|
|
186
|
+
```typescript
|
|
187
|
+
class ListItemsModel {
|
|
188
|
+
// Core Data - using observable collections
|
|
189
|
+
items: ListItemData[] = []
|
|
190
|
+
itemMap = observable.map<string, ListItemData>()
|
|
191
|
+
totalItemCount: number = 0
|
|
192
|
+
|
|
193
|
+
// View State
|
|
194
|
+
currentViewType: ListViewType = LIST_VIEW_TYPE
|
|
195
|
+
availableViewTypes: ListViewType[] = []
|
|
196
|
+
gridColumnsCount: number = 4
|
|
197
|
+
thumbnailSize: ThumbnailSize = 'medium'
|
|
198
|
+
|
|
199
|
+
// UI State - using observable.map instead of Set/Map
|
|
200
|
+
selectedItems = observable.map<string, ListItemData>()
|
|
201
|
+
focusedItem: string | null = null
|
|
202
|
+
hoveredItem: string | null = null
|
|
203
|
+
|
|
204
|
+
// Loading State
|
|
205
|
+
isLoading: boolean = false
|
|
206
|
+
loadingRanges = observable.map<string, boolean>()
|
|
207
|
+
errors = observable.map<string, Error>()
|
|
208
|
+
|
|
209
|
+
// Virtualization State
|
|
210
|
+
viewportRange: { start: number; end: number } = { start: 0, end: 0 }
|
|
211
|
+
scrollPosition: number = 0
|
|
212
|
+
containerSize: { width: number; height: number } = { width: 0, height: 0 }
|
|
213
|
+
|
|
214
|
+
// Interaction State
|
|
215
|
+
draggedItems: ListItemData[] = []
|
|
216
|
+
dropTarget: ListItemData | null = null
|
|
217
|
+
contextMenuItem: ListItemData | null = null
|
|
218
|
+
isEditing: string | null = null
|
|
219
|
+
|
|
220
|
+
constructor(public provider: ListItemsProvider) {
|
|
221
|
+
makeAutoObservable(this, {});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Computed properties for derived state
|
|
225
|
+
get selectedItemsArray(): ListItemData[] {
|
|
226
|
+
return Array.from(this.selectedItems.values());
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get hasSelection(): boolean {
|
|
230
|
+
return this.selectedItems.size > 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
get isItemSelected() {
|
|
234
|
+
return (itemId: string) => this.selectedItems.has(itemId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
get itemHeight(): number {
|
|
238
|
+
return this.provider.getItemHeight(this.currentViewType);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get gridItemsPerRow(): number {
|
|
242
|
+
if (this.currentViewType.id !== 'grid') return 1;
|
|
243
|
+
const itemWidth = this.provider.getItemWidth?.(this.currentViewType) ?? 200;
|
|
244
|
+
return Math.floor(this.containerSize.width / itemWidth);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Async actions using flow
|
|
248
|
+
loadItems = flow(function* (this: ListItemsModel, options?: ListLoadOptions) {
|
|
249
|
+
this.isLoading = true;
|
|
250
|
+
this.errors.clear();
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const result = yield this.provider.loadItems(options);
|
|
254
|
+
this.items = result.items;
|
|
255
|
+
this.totalItemCount = result.totalCount ?? result.items.length;
|
|
256
|
+
|
|
257
|
+
// Update item map for fast lookups
|
|
258
|
+
this.itemMap.clear();
|
|
259
|
+
result.items.forEach(item => this.itemMap.set(item.id, item));
|
|
260
|
+
|
|
261
|
+
} catch (error) {
|
|
262
|
+
this.errors.set('loadItems', error);
|
|
263
|
+
} finally {
|
|
264
|
+
this.isLoading = false;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
loadItemRange = flow(function* (this: ListItemsModel, start: number, end: number) {
|
|
269
|
+
const rangeKey = `${start}-${end}`;
|
|
270
|
+
this.loadingRanges.set(rangeKey, true);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const result = yield this.provider.loadItemRange?.(start, end) ?? { items: [] };
|
|
274
|
+
|
|
275
|
+
// Merge items into existing array at correct positions
|
|
276
|
+
result.items.forEach((item, index) => {
|
|
277
|
+
const absoluteIndex = start + index;
|
|
278
|
+
this.items[absoluteIndex] = item;
|
|
279
|
+
this.itemMap.set(item.id, item);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
} catch (error) {
|
|
283
|
+
this.errors.set(rangeKey, error);
|
|
284
|
+
} finally {
|
|
285
|
+
this.loadingRanges.delete(rangeKey);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 3. ListItems Component (UI Layer)
|
|
292
|
+
**Location**: `/components/ListItems.tsx`
|
|
293
|
+
|
|
294
|
+
**Purpose**: Main React component that receives ListItemsProvider as prop and renders the virtualized list UI.
|
|
295
|
+
|
|
296
|
+
**Component Interface**:
|
|
297
|
+
```typescript
|
|
298
|
+
interface ListItemsProps {
|
|
299
|
+
provider: ListItemsProvider;
|
|
300
|
+
className?: string;
|
|
301
|
+
height?: number;
|
|
302
|
+
width?: number;
|
|
303
|
+
// All behavior controlled by provider - no separate props
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const ListItems = observer(({ provider, className, height, width }: ListItemsProps) => {
|
|
307
|
+
const [model] = useState(() => new ListItemsModel(provider))
|
|
308
|
+
|
|
309
|
+
// All configuration comes from provider:
|
|
310
|
+
// - provider.supportedViewTypes
|
|
311
|
+
// - provider.isMultiSelectEnabled
|
|
312
|
+
// - provider.isDragDropEnabled
|
|
313
|
+
// - provider.isVirtualizationEnabled
|
|
314
|
+
// - provider.getVirtualizationConfig()
|
|
315
|
+
|
|
316
|
+
// Component delegates all actions to model,
|
|
317
|
+
// which calls provider callbacks
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## View Types System
|
|
322
|
+
|
|
323
|
+
### Built-in View Types
|
|
324
|
+
```typescript
|
|
325
|
+
export const LIST_VIEW_TYPE: ListViewType = {
|
|
326
|
+
id: "list",
|
|
327
|
+
name: "List",
|
|
328
|
+
icon: "list",
|
|
329
|
+
itemsPerRow: 1,
|
|
330
|
+
showDetails: false
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const GRID_VIEW_TYPE: ListViewType = {
|
|
334
|
+
id: "grid",
|
|
335
|
+
name: "Grid",
|
|
336
|
+
icon: "grid",
|
|
337
|
+
itemsPerRow: 'auto', // Calculated based on container width
|
|
338
|
+
showDetails: false
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export const DETAILS_VIEW_TYPE: ListViewType = {
|
|
342
|
+
id: "details",
|
|
343
|
+
name: "Details",
|
|
344
|
+
icon: "table",
|
|
345
|
+
itemsPerRow: 1,
|
|
346
|
+
showDetails: true,
|
|
347
|
+
columns: ['name', 'size', 'modified', 'type']
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export const THUMBNAILS_VIEW_TYPE: ListViewType = {
|
|
351
|
+
id: "thumbnails",
|
|
352
|
+
name: "Thumbnails",
|
|
353
|
+
icon: "images",
|
|
354
|
+
itemsPerRow: 'auto',
|
|
355
|
+
showThumbnails: true,
|
|
356
|
+
thumbnailSizes: ['small', 'medium', 'large', 'extra-large']
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export const MASONRY_HORIZONTAL_VIEW_TYPE: ListViewType = {
|
|
360
|
+
id: "masonry-horizontal",
|
|
361
|
+
name: "Masonry Horizontal",
|
|
362
|
+
icon: "masonry-horizontal",
|
|
363
|
+
itemsPerRow: 'auto',
|
|
364
|
+
showDetails: false,
|
|
365
|
+
masonry: true
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export const MASONRY_VERTICAL_VIEW_TYPE: ListViewType = {
|
|
369
|
+
id: "masonry-vertical",
|
|
370
|
+
name: "Masonry Vertical",
|
|
371
|
+
icon: "masonry-vertical",
|
|
372
|
+
itemsPerRow: 'auto',
|
|
373
|
+
showDetails: false,
|
|
374
|
+
masonry: true
|
|
375
|
+
};
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Custom View Types
|
|
379
|
+
```typescript
|
|
380
|
+
export const CUSTOM_VIEW_TYPE: ListViewType = {
|
|
381
|
+
id: "custom-cushion-tree-map",
|
|
382
|
+
name: "Cushion Tree Map",
|
|
383
|
+
icon: "custom",
|
|
384
|
+
itemsPerRow: 'custom',
|
|
385
|
+
customRenderer: CushionTreeMapRenderer
|
|
386
|
+
};
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## File & Folder Structure
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
fs-ui/src/ListItemsComponent/
|
|
393
|
+
├── architecture.md
|
|
394
|
+
├── providers/
|
|
395
|
+
│ └── ListItemsProvider.ts # Provider interface
|
|
396
|
+
├── models/
|
|
397
|
+
│ ├── ListItemsModel.ts # Main MobX state model
|
|
398
|
+
│ ├── ListSelection.ts # Selection state management
|
|
399
|
+
│ ├── ViewTypeManager.ts # View type state management
|
|
400
|
+
│ ├── VirtualizationModel.ts # Virtualization state management
|
|
401
|
+
│ └── ListLoading.ts # Loading state management
|
|
402
|
+
├── components/
|
|
403
|
+
│ ├── ListItems.tsx # Main list component
|
|
404
|
+
│ ├── ListItem.tsx # Individual item component
|
|
405
|
+
│ ├── VirtualizedList.tsx # Virtualization wrapper
|
|
406
|
+
│ ├── ViewTypeSelector.tsx # View type switcher
|
|
407
|
+
│ ├── ListContextMenu.tsx # Context menu component
|
|
408
|
+
│ ├── GridView.tsx # Grid layout component
|
|
409
|
+
│ ├── DetailsView.tsx # Details/table view component
|
|
410
|
+
│ ├── ThumbnailsView.tsx # Thumbnails view component
|
|
411
|
+
│ └── shared/
|
|
412
|
+
│ ├── ListIcon.tsx # Icon rendering
|
|
413
|
+
│ ├── ListLoader.tsx # Loading indicators
|
|
414
|
+
│ ├── ListThumbnail.tsx # Thumbnail rendering
|
|
415
|
+
│ └── ListVirtualization.tsx # Virtual scrolling utilities
|
|
416
|
+
├── renderers/
|
|
417
|
+
│ ├── DefaultItemRenderer.ts # Default item rendering
|
|
418
|
+
│ ├── TextItemRenderer.ts # Text-only rendering
|
|
419
|
+
│ ├── ThumbnailItemRenderer.ts # Thumbnail rendering
|
|
420
|
+
│ └── CustomItemRenderer.tsx # Custom React rendering
|
|
421
|
+
├── types/
|
|
422
|
+
│ ├── ListTypes.ts # Core type definitions
|
|
423
|
+
│ ├── ListEvents.ts # Event type definitions
|
|
424
|
+
│ ├── ListViewTypes.ts # View type definitions
|
|
425
|
+
│ └── ListOptions.ts # Configuration types
|
|
426
|
+
├── utils/
|
|
427
|
+
│ ├── ListUtils.ts # List manipulation utilities
|
|
428
|
+
│ ├── ListKeyboard.ts # Keyboard navigation
|
|
429
|
+
│ ├── ListAccessibility.ts # ARIA utilities
|
|
430
|
+
│ ├── ListVirtualization.ts # Virtualization calculations
|
|
431
|
+
│ └── ListTextEllipsis.ts # Text truncation utilities
|
|
432
|
+
└── index.ts # Public exports
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Data Flow
|
|
436
|
+
|
|
437
|
+
### 1. Initialization
|
|
438
|
+
```
|
|
439
|
+
1. ListItems component receives ListItemsProvider
|
|
440
|
+
2. ListItemsModel created with provider
|
|
441
|
+
3. ListItemsModel calls provider.initialize()
|
|
442
|
+
4. ListItemsModel calls provider.loadItems()
|
|
443
|
+
5. ListItems renders with initial data and view type
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 2. View Type Changes
|
|
447
|
+
```
|
|
448
|
+
User clicks view type button
|
|
449
|
+
↓
|
|
450
|
+
ViewTypeSelector component handles event
|
|
451
|
+
↓
|
|
452
|
+
Calls ListItemsModel.setViewType()
|
|
453
|
+
↓
|
|
454
|
+
ListItemsModel updates @observable currentViewType
|
|
455
|
+
↓
|
|
456
|
+
ListItemsModel calls provider.onViewTypeChange()
|
|
457
|
+
↓
|
|
458
|
+
UI re-renders with new layout via MobX observer
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### 3. Virtualization Flow
|
|
462
|
+
```
|
|
463
|
+
User scrolls list
|
|
464
|
+
↓
|
|
465
|
+
VirtualizedList component updates viewport
|
|
466
|
+
↓
|
|
467
|
+
Calls ListItemsModel.updateViewport()
|
|
468
|
+
↓
|
|
469
|
+
ListItemsModel calculates visible range
|
|
470
|
+
↓
|
|
471
|
+
If new items needed: calls loadItemRange()
|
|
472
|
+
↓
|
|
473
|
+
UI renders only visible items
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 4. Selection Interactions
|
|
477
|
+
```
|
|
478
|
+
User clicks item (with modifiers)
|
|
479
|
+
↓
|
|
480
|
+
ListItem component handles event
|
|
481
|
+
↓
|
|
482
|
+
Calls ListItemsModel.selectItem(modifiers)
|
|
483
|
+
↓
|
|
484
|
+
ListItemsModel updates @observable selectedItems
|
|
485
|
+
↓
|
|
486
|
+
ListItemsModel calls provider.onSelectionChange()
|
|
487
|
+
↓
|
|
488
|
+
UI re-renders selection state via MobX observer
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Virtualization Strategy
|
|
492
|
+
|
|
493
|
+
### Requirements
|
|
494
|
+
- **Mandatory**: Must handle 100k+ items without performance degradation
|
|
495
|
+
- **Library**: Use `react-window` or `react-virtualized-auto-sizer`
|
|
496
|
+
- **Height**: Fixed item heights per view type for optimal performance
|
|
497
|
+
- **Windowing**: Only render visible items + small overscan buffer
|
|
498
|
+
- **Dynamic Loading**: Load item ranges on-demand as user scrolls
|
|
499
|
+
|
|
500
|
+
### Implementation
|
|
501
|
+
```typescript
|
|
502
|
+
interface VirtualizationConfig {
|
|
503
|
+
/** Fixed item height for current view type */
|
|
504
|
+
itemHeight: number;
|
|
505
|
+
/** Items to render outside visible area */
|
|
506
|
+
overscan: number;
|
|
507
|
+
/** Minimum items before enabling virtualization */
|
|
508
|
+
threshold: number;
|
|
509
|
+
/** Dynamic loading range size */
|
|
510
|
+
rangeSize: number;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
class VirtualizationModel {
|
|
514
|
+
viewportHeight: number = 0;
|
|
515
|
+
scrollTop: number = 0;
|
|
516
|
+
itemHeight: number = 40;
|
|
517
|
+
|
|
518
|
+
constructor(private owner: ListItemsModel) {
|
|
519
|
+
makeAutoObservable(this, {});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
get visibleRange(): { start: number; end: number } {
|
|
523
|
+
const start = Math.floor(this.scrollTop / this.itemHeight);
|
|
524
|
+
const end = Math.min(
|
|
525
|
+
start + Math.ceil(this.viewportHeight / this.itemHeight),
|
|
526
|
+
this.owner.totalItemCount
|
|
527
|
+
);
|
|
528
|
+
return { start, end };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
get visibleItems(): ListItemData[] {
|
|
532
|
+
const { start, end } = this.visibleRange;
|
|
533
|
+
return this.owner.items.slice(start, end + 1);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Performance Optimizations
|
|
539
|
+
|
|
540
|
+
### MobX Optimizations
|
|
541
|
+
- **Computed Properties**: Use for derived state calculations
|
|
542
|
+
- **Observable Maps**: For O(1) lookups instead of array.find()
|
|
543
|
+
- **Granular Observables**: Separate selection, loading, and UI state
|
|
544
|
+
- **Reaction Cleanup**: Dispose of unused reactions
|
|
545
|
+
|
|
546
|
+
### Virtualization Optimizations
|
|
547
|
+
- **Fixed Heights**: Avoid dynamic height calculations
|
|
548
|
+
- **Overscan Buffer**: Prevent white space during fast scrolling
|
|
549
|
+
- **Range Loading**: Load items in batches, cache aggressively
|
|
550
|
+
- **Memory Management**: Unload items outside viewport bounds
|
|
551
|
+
|
|
552
|
+
### Rendering Optimizations
|
|
553
|
+
- **Observer Components**: Extract list items to separate observer components
|
|
554
|
+
- **Memoization**: Use React.memo for expensive item renderers
|
|
555
|
+
- **CSS Containment**: Use CSS `contain` property for layout isolation
|
|
556
|
+
- **Image Lazy Loading**: Load thumbnails only when visible
|
|
557
|
+
|
|
558
|
+
## Accessibility Features
|
|
559
|
+
|
|
560
|
+
### Keyboard Navigation
|
|
561
|
+
- **Arrow Keys**: Navigate between items in all view types
|
|
562
|
+
- **Home/End**: Jump to first/last item
|
|
563
|
+
- **Page Up/Down**: Scroll by viewport height
|
|
564
|
+
- **Space**: Toggle selection (multi-select mode)
|
|
565
|
+
- **Enter**: Activate/open item
|
|
566
|
+
- **F2**: Start inline editing (if supported)
|
|
567
|
+
|
|
568
|
+
### Screen Reader Support
|
|
569
|
+
- **Role Definitions**: `listbox`, `option`, `grid`, `gridcell`
|
|
570
|
+
- **ARIA Labels**: Descriptive labels for all interactive elements
|
|
571
|
+
- **Selection Announcements**: Announce selection changes
|
|
572
|
+
- **Position Information**: "Item 5 of 1000" for context
|
|
573
|
+
- **View Type Announcements**: Announce view changes
|
|
574
|
+
|
|
575
|
+
### Focus Management
|
|
576
|
+
- **Visual Focus**: Clear focus indicators
|
|
577
|
+
- **Focus Trap**: Keep focus within component
|
|
578
|
+
- **Restore Focus**: Remember focus position on re-entry
|
|
579
|
+
|
|
580
|
+
## Usage Examples
|
|
581
|
+
|
|
582
|
+
### Basic File Explorer
|
|
583
|
+
```typescript
|
|
584
|
+
const fileSystemProvider = new FileSystemListProvider(fileSystem, {
|
|
585
|
+
supportedViewTypes: [LIST_VIEW_TYPE, GRID_VIEW_TYPE, DETAILS_VIEW_TYPE],
|
|
586
|
+
multiSelectEnabled: true,
|
|
587
|
+
dragDropEnabled: true,
|
|
588
|
+
virtualizationEnabled: true,
|
|
589
|
+
defaultViewType: DETAILS_VIEW_TYPE
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
<ListItems provider={fileSystemProvider} height={600} />
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Media Gallery
|
|
596
|
+
```typescript
|
|
597
|
+
const mediaProvider = new MediaGalleryProvider(mediaService, {
|
|
598
|
+
supportedViewTypes: [THUMBNAILS_VIEW_TYPE, GRID_VIEW_TYPE],
|
|
599
|
+
multiSelectEnabled: true,
|
|
600
|
+
virtualizationEnabled: true,
|
|
601
|
+
defaultThumbnailSize: 'large'
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
<ListItems provider={mediaProvider} />
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Custom Data Grid
|
|
608
|
+
```typescript
|
|
609
|
+
class DataGridProvider implements ListItemsProvider {
|
|
610
|
+
constructor(private dataSource: DataSource) {}
|
|
611
|
+
|
|
612
|
+
async loadItems(options?: ListLoadOptions) {
|
|
613
|
+
return {
|
|
614
|
+
items: await this.dataSource.query(options),
|
|
615
|
+
totalCount: await this.dataSource.count()
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
getSupportedViewTypes() {
|
|
620
|
+
return [DETAILS_VIEW_TYPE, LIST_VIEW_TYPE];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
getItemRenderer(item: ListItemData) {
|
|
624
|
+
return new CustomDataItemRenderer(item.type);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
onSelectionChange(selectionInfo: ListSelectionInfo) {
|
|
628
|
+
// Handle selection for parent application
|
|
629
|
+
this.notifySelectionChanged(selectionInfo.selectedItems);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Integration Points
|
|
635
|
+
|
|
636
|
+
### With File System
|
|
637
|
+
```typescript
|
|
638
|
+
const fileListProvider = new FileSystemListProvider(fileSystem, {
|
|
639
|
+
supportedViewTypes: [LIST_VIEW_TYPE, DETAILS_VIEW_TYPE, THUMBNAILS_VIEW_TYPE],
|
|
640
|
+
showHiddenFiles: false,
|
|
641
|
+
sortBy: 'name'
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
<ListItems provider={fileListProvider} />
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### With Search Results
|
|
648
|
+
```typescript
|
|
649
|
+
const searchProvider = new SearchResultsProvider(searchService, {
|
|
650
|
+
supportedViewTypes: [LIST_VIEW_TYPE, DETAILS_VIEW_TYPE],
|
|
651
|
+
highlightSearchTerms: true,
|
|
652
|
+
maxResults: 1000
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
<ListItems provider={searchProvider} />
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### With Database Query
|
|
659
|
+
```typescript
|
|
660
|
+
const dbProvider = new DatabaseListProvider(dbConnection, {
|
|
661
|
+
table: 'products',
|
|
662
|
+
supportedViewTypes: [DETAILS_VIEW_TYPE],
|
|
663
|
+
virtualizationEnabled: true,
|
|
664
|
+
pageSize: 100
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
<ListItems provider={dbProvider} />
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Error Handling & Loading States
|
|
671
|
+
|
|
672
|
+
### Loading States
|
|
673
|
+
- **Initial Load**: Full component loading spinner
|
|
674
|
+
- **Range Loading**: Skeleton items in viewport
|
|
675
|
+
- **Background Refresh**: Subtle loading indicator
|
|
676
|
+
- **Search Loading**: Search spinner with debouncing
|
|
677
|
+
|
|
678
|
+
### Error States
|
|
679
|
+
- **Load Errors**: Retry button with error message
|
|
680
|
+
- **Network Errors**: Offline indicator with retry
|
|
681
|
+
- **Permission Errors**: Clear access denied message
|
|
682
|
+
- **Partial Errors**: Show available items, note errors
|
|
683
|
+
|
|
684
|
+
### Empty States
|
|
685
|
+
- **No Items**: Helpful empty state illustration
|
|
686
|
+
- **No Search Results**: Search suggestions and filters
|
|
687
|
+
- **Filter No Results**: Clear active filters button
|
|
688
|
+
|
|
689
|
+
## Testing Strategy
|
|
690
|
+
|
|
691
|
+
### Unit Tests
|
|
692
|
+
- **Models**: MobX state transitions and computed properties
|
|
693
|
+
- **Providers**: Data loading and callback functionality
|
|
694
|
+
- **Utils**: Virtualization calculations and utilities
|
|
695
|
+
|
|
696
|
+
### Integration Tests
|
|
697
|
+
- **Provider + Model**: Data flow and state management
|
|
698
|
+
- **Component + Model**: UI interactions and updates
|
|
699
|
+
- **Virtualization**: Scroll behavior and range loading
|
|
700
|
+
|
|
701
|
+
### E2E Tests
|
|
702
|
+
- **View Switching**: All view types render correctly
|
|
703
|
+
- **Large Datasets**: Performance with 10k+ items
|
|
704
|
+
- **Selection**: Multi-select with keyboard and mouse
|
|
705
|
+
- **Accessibility**: Screen reader and keyboard navigation
|
|
706
|
+
|
|
707
|
+
## Migration Notes
|
|
708
|
+
|
|
709
|
+
### From Legacy Components
|
|
710
|
+
1. **Wrap existing data sources** in ListItemsProvider interface
|
|
711
|
+
2. **Migrate selection logic** to provider callbacks
|
|
712
|
+
3. **Update view switching** to use view type system
|
|
713
|
+
4. **Add virtualization** for performance with large datasets
|
|
714
|
+
5. **Enhance accessibility** with proper ARIA attributes
|
|
715
|
+
|
|
716
|
+
### Breaking Changes
|
|
717
|
+
- **Selection API**: New selection event structure
|
|
718
|
+
- **View Types**: Standardized view type definitions
|
|
719
|
+
- **Rendering**: New renderer interface for customization
|
|
720
|
+
- **Performance**: Mandatory virtualization for large lists
|
|
721
|
+
|
|
722
|
+
This architecture provides a solid foundation for building high-performance, accessible list components that can handle massive datasets while maintaining a clean, extensible codebase following MobX best practices.
|
|
723
|
+
|
|
724
|
+
## Size Controls
|
|
725
|
+
|
|
726
|
+
The `ViewSizeControls` component provides user interface for adjusting item sizes in grid and masonry views:
|
|
727
|
+
|
|
728
|
+
### Features
|
|
729
|
+
|
|
730
|
+
- **Size Presets**: Quick buttons for small, medium, large, and extra-large sizes
|
|
731
|
+
- **Custom Sliders**: Fine-grained width and height adjustment sliders
|
|
732
|
+
- **Live Preview**: Real-time dimension display
|
|
733
|
+
- **View-Aware**: Only shows for views that support sizing (grid, masonry)
|
|
734
|
+
|
|
735
|
+
### Usage
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
<ViewSizeControls
|
|
739
|
+
model={listModel}
|
|
740
|
+
showSizePresets={true}
|
|
741
|
+
showCustomSliders={false}
|
|
742
|
+
className="size-controls"
|
|
743
|
+
/>
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Size Management in Model
|
|
747
|
+
|
|
748
|
+
The `ListItemsModel` provides size management through:
|
|
749
|
+
|
|
750
|
+
- `itemSize`: Current preset size ('small' | 'medium' | 'large' | 'extra-large')
|
|
751
|
+
- `customItemWidth`: Custom width in pixels (80-500px)
|
|
752
|
+
- `customItemHeight`: Custom height in pixels (60-400px)
|
|
753
|
+
- `itemsPerRow`: Number of items per row (1-8, or 'auto')
|
|
754
|
+
- `itemDimensions`: Computed property returning current dimensions
|
|
755
|
+
- `setItemSize()`: Set preset size with automatic dimension updates
|
|
756
|
+
- `setCustomItemWidth()` / `setCustomItemHeight()`: Fine-grained control
|
|
757
|
+
- `setItemsPerRow()`: Control column count in grid and masonry views
|
|
758
|
+
|
|
759
|
+
## Variable Item Dimensions
|
|
760
|
+
|
|
761
|
+
Items can have custom dimensions for realistic masonry layouts:
|
|
762
|
+
|
|
763
|
+
### Item Dimension Interface
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
interface ListItemData {
|
|
767
|
+
// ... other properties
|
|
768
|
+
getDimensions?(): { width: number; height: number };
|
|
769
|
+
imageUrl?: string;
|
|
770
|
+
aspectRatio?: number;
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Features
|
|
775
|
+
|
|
776
|
+
- **Dynamic Sizing**: Items can return custom dimensions via `getDimensions()`
|
|
777
|
+
- **Aspect Ratio Preservation**: Images maintain their aspect ratios in layouts
|
|
778
|
+
- **Fake Image Integration**: Uses picsum.photos for varied placeholder images
|
|
779
|
+
- **Performance Optimized**: Efficient rendering with large datasets (10,000+ items)
|
|
780
|
+
|
|
781
|
+
### Image Service Integration
|
|
782
|
+
|
|
783
|
+
The component integrates with picsum.photos for realistic image placeholders:
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
// Generate varied image URLs
|
|
787
|
+
const imageUrl = `https://picsum.photos/${width}/${height}?random=${seed}`;
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
## Large Dataset Support
|
|
791
|
+
|
|
792
|
+
The component is optimized for large datasets:
|
|
793
|
+
|
|
794
|
+
### Performance Features
|
|
795
|
+
|
|
796
|
+
- **Virtualization Ready**: Supports react-window for 10,000+ items
|
|
797
|
+
- **Efficient Rendering**: Memoized components and computed properties
|
|
798
|
+
- **Memory Management**: Proper cleanup and garbage collection
|
|
799
|
+
- **Smooth Scrolling**: Optimized for large lists without performance degradation
|
|
800
|
+
|
|
801
|
+
### Test Dataset
|
|
802
|
+
|
|
803
|
+
The `TestListProvider` can generate large datasets with:
|
|
804
|
+
- 10,000 varied items
|
|
805
|
+
- Random dimensions and aspect ratios
|
|
806
|
+
- Fake images with different sizes
|
|
807
|
+
- Realistic file metadata
|