@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c

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 (170) hide show
  1. package/README.md +1 -1
  2. package/dist/components/AIIcon.d.ts +16 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
  7. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
  8. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
  9. package/dist/components/EntityCollectionView/Board.d.ts +2 -0
  10. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  11. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  12. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  13. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  14. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  15. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  16. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  18. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  19. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  20. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
  21. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  22. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  23. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  24. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  25. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  26. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
  27. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  28. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  29. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  30. package/dist/components/VirtualTable/types.d.ts +2 -0
  31. package/dist/components/index.d.ts +3 -0
  32. package/dist/contexts/index.d.ts +10 -0
  33. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  34. package/dist/core/index.d.ts +1 -0
  35. package/dist/form/validation.d.ts +3 -2
  36. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  37. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  38. package/dist/index.es.js +5239 -1590
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.umd.js +5233 -1585
  41. package/dist/index.umd.js.map +1 -1
  42. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  43. package/dist/preview/components/DatePreview.d.ts +13 -3
  44. package/dist/preview/components/ImagePreview.d.ts +5 -1
  45. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  46. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  47. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  48. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  49. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  50. package/dist/types/collections.d.ts +42 -2
  51. package/dist/types/datasource.d.ts +0 -1
  52. package/dist/types/plugins.d.ts +46 -1
  53. package/dist/types/properties.d.ts +259 -4
  54. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  55. package/dist/util/__tests__/objects.test.d.ts +1 -0
  56. package/dist/util/conditions.d.ts +26 -0
  57. package/dist/util/entities.d.ts +1 -2
  58. package/dist/util/index.d.ts +2 -1
  59. package/dist/util/property_utils.d.ts +2 -1
  60. package/dist/util/resolutions.d.ts +1 -1
  61. package/package.json +10 -7
  62. package/src/app/Scaffold.tsx +14 -15
  63. package/src/components/AIIcon.tsx +39 -0
  64. package/src/components/ArrayContainer.tsx +1 -4
  65. package/src/components/ClearFilterSortButton.tsx +19 -16
  66. package/src/components/ConfirmationDialog.tsx +0 -2
  67. package/src/components/DeleteEntityDialog.tsx +2 -4
  68. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  69. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  70. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  71. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  72. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  73. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  74. package/src/components/EntityCollectionView/Board.tsx +324 -0
  75. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  76. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  77. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  78. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  79. package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
  80. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
  81. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  82. package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
  83. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  84. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  85. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  86. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  87. package/src/components/EntityCollectionView/board_types.ts +113 -0
  88. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  89. package/src/components/ErrorTooltip.tsx +2 -1
  90. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  91. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  92. package/src/components/HomePage/NavigationCard.tsx +20 -18
  93. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  94. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  95. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  96. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  97. package/src/components/ReferenceWidget.tsx +2 -4
  98. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  99. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  100. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  101. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  102. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  103. package/src/components/UnsavedChangesDialog.tsx +0 -2
  104. package/src/components/UserDisplay.tsx +4 -4
  105. package/src/components/VirtualTable/VirtualTable.tsx +170 -19
  106. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  107. package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
  108. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  109. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  110. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  111. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  112. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
  113. package/src/components/VirtualTable/types.tsx +2 -0
  114. package/src/components/common/useColumnsIds.tsx +95 -3
  115. package/src/components/index.tsx +4 -0
  116. package/src/contexts/BreacrumbsContext.tsx +15 -8
  117. package/src/contexts/index.ts +10 -0
  118. package/src/core/DefaultAppBar.tsx +39 -26
  119. package/src/core/DefaultDrawer.tsx +42 -56
  120. package/src/core/DrawerNavigationGroup.tsx +118 -0
  121. package/src/core/DrawerNavigationItem.tsx +4 -3
  122. package/src/core/EntityEditView.tsx +41 -43
  123. package/src/core/SideDialogs.tsx +4 -2
  124. package/src/core/index.tsx +1 -0
  125. package/src/form/PropertyFieldBinding.tsx +58 -43
  126. package/src/form/components/StorageItemPreview.tsx +2 -1
  127. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  128. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  129. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  130. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  131. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
  132. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  133. package/src/form/validation.ts +245 -160
  134. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  135. package/src/hooks/useBuildNavigationController.tsx +42 -19
  136. package/src/hooks/useCollapsedGroups.ts +12 -4
  137. package/src/internal/useBuildDataSource.ts +69 -34
  138. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  139. package/src/internal/useBuildSideEntityController.tsx +2 -4
  140. package/src/internal/useRestoreScroll.tsx +26 -14
  141. package/src/preview/PropertyPreview.tsx +40 -32
  142. package/src/preview/PropertyPreviewProps.tsx +6 -0
  143. package/src/preview/components/DatePreview.tsx +72 -4
  144. package/src/preview/components/EmptyValue.tsx +1 -1
  145. package/src/preview/components/ImagePreview.tsx +37 -21
  146. package/src/preview/components/StorageThumbnail.tsx +16 -12
  147. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  148. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  149. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  150. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  151. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  152. package/src/routes/CustomCMSRoute.tsx +1 -0
  153. package/src/routes/FireCMSRoute.tsx +26 -13
  154. package/src/types/collections.ts +48 -3
  155. package/src/types/datasource.ts +54 -56
  156. package/src/types/plugins.tsx +51 -1
  157. package/src/types/properties.ts +347 -27
  158. package/src/util/__tests__/conditions.test.ts +506 -0
  159. package/src/util/__tests__/objects.test.ts +196 -0
  160. package/src/util/callbacks.ts +6 -3
  161. package/src/util/collections.ts +51 -6
  162. package/src/util/conditions.ts +339 -0
  163. package/src/util/entities.ts +28 -29
  164. package/src/util/entity_cache.ts +2 -1
  165. package/src/util/index.ts +2 -1
  166. package/src/util/objects.ts +31 -13
  167. package/src/util/{references.ts → previews.ts} +14 -0
  168. package/src/util/property_utils.tsx +36 -10
  169. package/src/util/resolutions.ts +57 -55
  170. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -0,0 +1,212 @@
1
+ import React, { memo, useCallback, useMemo } from "react";
2
+ import { Entity, EntityCollection, ResolvedProperty } from "../../types";
3
+ import {
4
+ getEntityImagePreviewPropertyKey,
5
+ getEntityTitlePropertyKey,
6
+ getValueInPath,
7
+ IconForView,
8
+ resolveCollection
9
+ } from "../../util";
10
+ import { Checkbox, cls, defaultBorderMixin } from "@firecms/ui";
11
+ import { PropertyPreview } from "../../preview";
12
+ import { useAuthController, useCustomizationController } from "../../hooks";
13
+ import { BoardItemViewProps } from "./board_types";
14
+
15
+ export type EntityBoardCardProps<M extends Record<string, any> = any> = BoardItemViewProps<M> & {
16
+ collection: EntityCollection<M>;
17
+ onClick?: (entity: Entity<M>) => void;
18
+ selected?: boolean;
19
+ onSelectionChange?: (entity: Entity<M>, selected: boolean) => void;
20
+ selectionEnabled?: boolean;
21
+ };
22
+
23
+ /**
24
+ * Compact card component for displaying an entity in a Kanban board.
25
+ * Shows thumbnail, title, and optional selection checkbox.
26
+ */
27
+ function EntityBoardCardInner<M extends Record<string, any> = any>({
28
+ item,
29
+ isDragging,
30
+ isGroupedOver,
31
+ style,
32
+ collection,
33
+ onClick,
34
+ selected,
35
+ onSelectionChange,
36
+ selectionEnabled = false
37
+ }: EntityBoardCardProps<M>) {
38
+ const entity = item.entity;
39
+ const authController = useAuthController();
40
+ const customizationController = useCustomizationController();
41
+
42
+ const resolvedCollection = useMemo(() => resolveCollection({
43
+ collection,
44
+ path: entity.path,
45
+ values: entity.values,
46
+ propertyConfigs: customizationController.propertyConfigs,
47
+ authController
48
+ }), [collection, entity.path, entity.values, customizationController.propertyConfigs, authController]);
49
+
50
+ const titlePropertyKey = useMemo(
51
+ () => getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs),
52
+ [resolvedCollection, customizationController.propertyConfigs]
53
+ );
54
+
55
+ const imagePropertyKey = useMemo(
56
+ () => getEntityImagePreviewPropertyKey(resolvedCollection),
57
+ [resolvedCollection]
58
+ );
59
+
60
+ const imageProperty = imagePropertyKey ? resolvedCollection.properties[imagePropertyKey] : undefined;
61
+ const usedImageProperty = imageProperty && "of" in imageProperty ? imageProperty.of : imageProperty;
62
+
63
+ const imageValue = imagePropertyKey ? getValueInPath(entity.values, imagePropertyKey) : undefined;
64
+ const usedImageValue = imageProperty !== undefined
65
+ ? ("of" in imageProperty
66
+ ? ((imageValue ?? []).length > 0 ? imageValue[0] : undefined)
67
+ : imageValue)
68
+ : undefined;
69
+
70
+ const titleValue = titlePropertyKey ? getValueInPath(entity.values, titlePropertyKey) : undefined;
71
+ const titleProperty = titlePropertyKey ? resolvedCollection.properties[titlePropertyKey] as ResolvedProperty : undefined;
72
+
73
+ const handleClick = useCallback((e: React.MouseEvent) => {
74
+ // Cmd+click (Mac) or Ctrl+click (Windows) toggles selection
75
+ if ((e.metaKey || e.ctrlKey) && selectionEnabled) {
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ onSelectionChange?.(entity, !selected);
79
+ return;
80
+ }
81
+ if (onClick) {
82
+ e.stopPropagation();
83
+ onClick(entity);
84
+ }
85
+ }, [entity, onClick, onSelectionChange, selected, selectionEnabled]);
86
+
87
+ const handleCheckboxClick = useCallback((e: React.MouseEvent) => {
88
+ e.stopPropagation();
89
+ }, []);
90
+
91
+ const handleSelectionChange = useCallback((checked: boolean) => {
92
+ onSelectionChange?.(entity, checked);
93
+ }, [entity, onSelectionChange]);
94
+
95
+ // Memoize className computations
96
+ const backgroundColor = useMemo((): string => {
97
+ if (isDragging) {
98
+ return "bg-surface-100 dark:bg-surface-800";
99
+ }
100
+ if (isGroupedOver) {
101
+ return "bg-surface-200";
102
+ }
103
+ return "bg-white dark:bg-surface-900 hover:bg-surface-100 dark:hover:bg-surface-800";
104
+ }, [isDragging, isGroupedOver]);
105
+
106
+ const borderColor = useMemo((): string =>
107
+ isDragging ? "ring-2 ring-primary" : "", [isDragging]);
108
+
109
+ // Memoize the card className
110
+ const cardClassName = useMemo(() => cls(
111
+ "p-2 flex items-start border rounded-lg cursor-pointer transition-colors",
112
+ defaultBorderMixin,
113
+ borderColor,
114
+ backgroundColor,
115
+ selected && "ring-2 ring-primary"
116
+ ), [borderColor, backgroundColor, selected]);
117
+
118
+ return (
119
+ <div
120
+ style={style}
121
+ className="py-1"
122
+ data-is-dragging={isDragging}
123
+ data-testid={item.id}
124
+ onClick={handleClick}
125
+ >
126
+ <div className={cardClassName}>
127
+ {/* Thumbnail */}
128
+ {usedImageProperty && usedImageValue ? (
129
+ <div className="w-10 h-10 rounded-md overflow-hidden shrink-0 mr-2">
130
+ <PropertyPreview
131
+ property={usedImageProperty}
132
+ propertyKey={imagePropertyKey as string}
133
+ size="small"
134
+ value={usedImageValue}
135
+ fill={true}
136
+ />
137
+ </div>
138
+ ) : (
139
+ <div
140
+ className="w-10 h-10 rounded-md bg-surface-100 dark:bg-surface-800 shrink-0 mr-2 flex items-center justify-center">
141
+ <IconForView
142
+ collectionOrView={collection}
143
+ color="disabled"
144
+ size="small"
145
+ />
146
+ </div>
147
+ )}
148
+
149
+ {/* Content */}
150
+ <div className="flex-1 min-w-0">
151
+ {/* Title */}
152
+ <div className="truncate text-sm font-medium">
153
+ {titleProperty && titleValue ? (
154
+ <PropertyPreview
155
+ propertyKey={titlePropertyKey as string}
156
+ value={titleValue}
157
+ property={titleProperty}
158
+ size="small"
159
+ />
160
+ ) : (
161
+ <span className="text-surface-500">{entity.id}</span>
162
+ )}
163
+ </div>
164
+ {/* ID */}
165
+ <div className="text-xs text-surface-500 font-mono truncate">
166
+ {entity.id}
167
+ </div>
168
+ </div>
169
+
170
+ {/* Selection checkbox */}
171
+ {selectionEnabled && (
172
+ <div className="ml-2 shrink-0" onClick={handleCheckboxClick}>
173
+ <Checkbox
174
+ checked={selected ?? false}
175
+ onCheckedChange={handleSelectionChange}
176
+ size="smallest"
177
+ />
178
+ </div>
179
+ )}
180
+ </div>
181
+ </div>
182
+ );
183
+ }
184
+
185
+ // Memoized to prevent unnecessary re-renders when other cards in the board change
186
+ export const EntityBoardCard = memo(EntityBoardCardInner) as typeof EntityBoardCardInner;
187
+
188
+ /**
189
+ * Wrapper component that adapts EntityBoardCard to BoardItemViewProps interface
190
+ */
191
+ export function createEntityBoardCardComponent<M extends Record<string, any>>(
192
+ collection: EntityCollection<M>,
193
+ options: {
194
+ onClick?: (entity: Entity<M>) => void;
195
+ isEntitySelected?: (entity: Entity<M>) => boolean;
196
+ onSelectionChange?: (entity: Entity<M>, selected: boolean) => void;
197
+ selectionEnabled?: boolean;
198
+ }
199
+ ): React.ComponentType<BoardItemViewProps<M>> {
200
+ return function EntityBoardCardWrapper(props: BoardItemViewProps<M>) {
201
+ return (
202
+ <EntityBoardCard
203
+ {...props}
204
+ collection={collection}
205
+ onClick={options.onClick}
206
+ selected={options.isEntitySelected?.(props.item.entity)}
207
+ onSelectionChange={options.onSelectionChange}
208
+ selectionEnabled={options.selectionEnabled}
209
+ />
210
+ );
211
+ };
212
+ }
@@ -0,0 +1,231 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ CollectionSize,
4
+ Entity,
5
+ EntityCollection,
6
+ ResolvedProperty
7
+ } from "../../types";
8
+ import {
9
+ getEntityImagePreviewPropertyKey,
10
+ getEntityPreviewKeys,
11
+ getEntityTitlePropertyKey,
12
+ getValueInPath,
13
+ IconForView,
14
+ resolveCollection
15
+ } from "../../util";
16
+ import {
17
+ Card,
18
+ Checkbox,
19
+ cls,
20
+ KeyboardTabIcon,
21
+ IconButton,
22
+ Skeleton,
23
+ Tooltip,
24
+ Typography
25
+ } from "@firecms/ui";
26
+ import { PropertyPreview, SkeletonPropertyComponent } from "../../preview";
27
+ import {
28
+ useAuthController,
29
+ useCustomizationController,
30
+ useNavigationController,
31
+ useSideEntityController
32
+ } from "../../hooks";
33
+ import { useAnalyticsController } from "../../hooks/useAnalyticsController";
34
+
35
+ export type EntityCardProps<M extends Record<string, any> = any> = {
36
+ entity: Entity<M>;
37
+ collection: EntityCollection<M>;
38
+ onClick?: (entity: Entity<M>) => void;
39
+ selected?: boolean;
40
+ highlighted?: boolean;
41
+ onSelectionChange?: (entity: Entity<M>, selected: boolean) => void;
42
+ selectionEnabled?: boolean;
43
+ /**
44
+ * Size of the card - affects checkbox styling
45
+ */
46
+ size?: CollectionSize;
47
+ };
48
+
49
+ /**
50
+ * Card component for displaying an entity in a grid view.
51
+ * Shows thumbnail, title, and preview properties.
52
+ */
53
+ export function EntityCard<M extends Record<string, any> = any>({
54
+ entity,
55
+ collection,
56
+ onClick,
57
+ selected,
58
+ highlighted,
59
+ onSelectionChange,
60
+ selectionEnabled,
61
+ size = "m"
62
+ }: EntityCardProps<M>) {
63
+ const authController = useAuthController();
64
+ const analyticsController = useAnalyticsController();
65
+ const sideEntityController = useSideEntityController();
66
+ const customizationController = useCustomizationController();
67
+ const navigationController = useNavigationController();
68
+
69
+ const resolvedCollection = useMemo(() => resolveCollection({
70
+ collection,
71
+ path: entity.path,
72
+ values: entity.values,
73
+ propertyConfigs: customizationController.propertyConfigs,
74
+ authController
75
+ }), [collection, entity.path, entity.values]);
76
+
77
+ const titlePropertyKey = useMemo(
78
+ () => getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs),
79
+ [resolvedCollection, customizationController.propertyConfigs]
80
+ );
81
+
82
+ const imagePropertyKey = useMemo(
83
+ () => getEntityImagePreviewPropertyKey(resolvedCollection),
84
+ [resolvedCollection]
85
+ );
86
+
87
+ const previewKeys = useMemo(
88
+ () => getEntityPreviewKeys(authController, resolvedCollection, customizationController.propertyConfigs, undefined, 2)
89
+ .filter(key => key !== titlePropertyKey && key !== imagePropertyKey),
90
+ [authController, resolvedCollection, customizationController.propertyConfigs, titlePropertyKey, imagePropertyKey]
91
+ );
92
+
93
+ const imageProperty = imagePropertyKey ? resolvedCollection.properties[imagePropertyKey] : undefined;
94
+ const usedImageProperty = imageProperty && "of" in imageProperty ? imageProperty.of : imageProperty;
95
+
96
+ const imageValue = imagePropertyKey ? getValueInPath(entity.values, imagePropertyKey) : undefined;
97
+ const usedImageValue = imageProperty !== undefined
98
+ ? ("of" in imageProperty
99
+ ? ((imageValue ?? []).length > 0 ? imageValue[0] : undefined)
100
+ : imageValue)
101
+ : undefined;
102
+
103
+ const titleValue = titlePropertyKey ? getValueInPath(entity.values, titlePropertyKey) : undefined;
104
+ const titleProperty = titlePropertyKey ? resolvedCollection.properties[titlePropertyKey] as ResolvedProperty : undefined;
105
+
106
+ const handleClick = (e?: React.MouseEvent) => {
107
+ // Cmd+click (Mac) or Ctrl+click (Windows) toggles selection
108
+ if (e && (e.metaKey || e.ctrlKey) && selectionEnabled) {
109
+ e.preventDefault();
110
+ onSelectionChange?.(entity, !selected);
111
+ return;
112
+ }
113
+ if (onClick) {
114
+ onClick(entity);
115
+ }
116
+ };
117
+
118
+ const handleCheckboxClick = (e: React.MouseEvent) => {
119
+ e.stopPropagation();
120
+ };
121
+
122
+ const handleSelectionChange = (checked: boolean) => {
123
+ onSelectionChange?.(entity, checked);
124
+ };
125
+
126
+
127
+ return (
128
+ <Card
129
+ className={cls(
130
+ "cursor-pointer overflow-hidden group relative",
131
+ "transition-all duration-200",
132
+ "hover:shadow-lg hover:-translate-y-0.5",
133
+ selected && "ring-2 ring-primary",
134
+ highlighted && !selected && "ring-2 ring-primary ring-opacity-50"
135
+ )}
136
+ onClick={handleClick}
137
+ >
138
+ {/* Thumbnail area */}
139
+ <div className="aspect-[4/3] relative overflow-hidden bg-surface-100 dark:bg-surface-800">
140
+ {usedImageProperty && usedImageValue ? (
141
+ <div className="w-full h-full">
142
+ <PropertyPreview
143
+ property={usedImageProperty}
144
+ propertyKey={imagePropertyKey as string}
145
+ size="medium"
146
+ value={usedImageValue}
147
+ fill={true}
148
+ />
149
+ </div>
150
+ ) : (
151
+ <div className="w-full h-full flex items-center justify-center">
152
+ <IconForView
153
+ collectionOrView={collection}
154
+ color="disabled"
155
+ size="large"
156
+ />
157
+ </div>
158
+ )}
159
+
160
+ {/* Hover overlay */}
161
+ <div className={cls(
162
+ "absolute inset-0 bg-black/0 group-hover:bg-black/10",
163
+ "transition-colors duration-200"
164
+ )} />
165
+
166
+ {/* Selection checkbox */}
167
+ {selectionEnabled && (
168
+ <div
169
+ className={cls(
170
+ "absolute",
171
+ size === "xs" || size === "s" ? "top-1 left-1" : "top-2 left-2"
172
+ )}
173
+ onClick={handleCheckboxClick}
174
+ >
175
+ <Checkbox
176
+ checked={selected ?? false}
177
+ onCheckedChange={handleSelectionChange}
178
+ size={size === "xs" ? "smallest" : "small"}
179
+ />
180
+ </div>
181
+ )}
182
+
183
+ </div>
184
+
185
+ {/* Content area */}
186
+ <div className="p-3">
187
+ {/* Entity ID */}
188
+ <Typography
189
+ variant="caption"
190
+ color="disabled"
191
+ className="font-mono truncate block"
192
+ >
193
+ {entity.id}
194
+ </Typography>
195
+
196
+ {/* Title */}
197
+ <div className="truncate my-1 text-sm font-medium min-h-[20px]">
198
+ {titleProperty && titleValue ? (
199
+ <PropertyPreview
200
+ propertyKey={titlePropertyKey as string}
201
+ value={titleValue}
202
+ property={titleProperty}
203
+ size="small"
204
+ />
205
+ ) : (
206
+ <Typography variant="body2" className="text-surface-500">
207
+ {entity.id}
208
+ </Typography>
209
+ )}
210
+ </div>
211
+
212
+ {/* Preview properties */}
213
+ {previewKeys.slice(0, 2).map((key) => {
214
+ const property = resolvedCollection.properties[key] as ResolvedProperty;
215
+ if (!property) return null;
216
+ const value = getValueInPath(entity.values, key);
217
+ return (
218
+ <div key={key} className="truncate text-xs text-surface-600 dark:text-surface-400">
219
+ <PropertyPreview
220
+ propertyKey={key}
221
+ value={value}
222
+ property={property}
223
+ size="small"
224
+ />
225
+ </div>
226
+ );
227
+ })}
228
+ </div>
229
+ </Card>
230
+ );
231
+ }