@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,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
|
+
});
|