@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
@@ -49,7 +49,8 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
49
49
  size,
50
50
  height,
51
51
  width,
52
- interactive
52
+ interactive,
53
+ fill
53
54
  } = props;
54
55
 
55
56
  const property = resolveProperty({
@@ -60,7 +61,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
60
61
  });
61
62
 
62
63
  if (property === null) {
63
- content = <EmptyValue/>;
64
+ content = <EmptyValue />;
64
65
  } else if (property.Preview) {
65
66
  content = createElement(property.Preview as React.ComponentType<PropertyPreviewProps>,
66
67
  {
@@ -74,7 +75,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
74
75
  customProps: property.customProps
75
76
  });
76
77
  } else if (value === undefined || value === null) {
77
- content = <EmptyValue/>;
78
+ content = <EmptyValue />;
78
79
  } else if (property.dataType === "string") {
79
80
  const stringProperty = property as ResolvedStringProperty;
80
81
  if (typeof value === "string") {
@@ -84,20 +85,23 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
84
85
  interactive={interactive}
85
86
  storeUrl={property.storage?.storeUrl ?? false}
86
87
  size={props.size}
87
- storagePathOrDownloadUrl={filePath}/>;
88
+ fill={fill}
89
+ storagePathOrDownloadUrl={filePath} />;
88
90
  } else if (stringProperty.url) {
89
91
  if (typeof stringProperty.url === "boolean")
90
92
  content =
91
93
  <UrlComponentPreview size={props.size}
92
- url={value}/>;
94
+ url={value}
95
+ fill={fill} />;
93
96
  else if (typeof stringProperty.url === "string")
94
97
  content =
95
98
  <UrlComponentPreview size={props.size}
96
- url={value}
97
- interactive={interactive}
98
- previewType={stringProperty.url}/>;
99
+ url={value}
100
+ interactive={interactive}
101
+ fill={fill}
102
+ previewType={stringProperty.url} />;
99
103
  } else if (stringProperty.markdown) {
100
- content = <Markdown source={value} size={"small"}/>;
104
+ content = <Markdown source={value} size={"small"} />;
101
105
  } else if (stringProperty.userSelect) {
102
106
  content = <UserPreview
103
107
  value={value}
@@ -116,13 +120,13 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
116
120
  reference={new EntityReference(value, stringProperty.reference.path)}
117
121
  />;
118
122
  } else {
119
- content = <EmptyValue/>;
123
+ content = <EmptyValue />;
120
124
  }
121
125
 
122
126
  } else {
123
127
  content = <StringPropertyPreview {...props}
124
- property={stringProperty}
125
- value={value}/>;
128
+ property={stringProperty}
129
+ value={value} />;
126
130
  }
127
131
  } else {
128
132
  content = buildWrongValueType(propertyKey, property.dataType, value);
@@ -137,43 +141,43 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
137
141
  if (arrayProperty.of) {
138
142
  if (Array.isArray(arrayProperty.of)) {
139
143
  content = <ArrayPropertyPreview {...props}
140
- value={value}
141
- property={property as ResolvedArrayProperty}/>;
144
+ value={value}
145
+ property={property as ResolvedArrayProperty} />;
142
146
  } else if (arrayProperty.of.dataType === "reference") {
143
147
  content = <ArrayOfReferencesPreview {...props}
144
- value={value}
145
- property={property as ResolvedArrayProperty}/>;
148
+ value={value}
149
+ property={property as ResolvedArrayProperty} />;
146
150
  } else if (arrayProperty.of.dataType === "string") {
147
151
  if (arrayProperty.of.enumValues) {
148
152
  content = <ArrayPropertyEnumPreview
149
153
  {...props}
150
154
  value={value as string[]}
151
- property={property as ResolvedArrayProperty}/>;
155
+ property={property as ResolvedArrayProperty} />;
152
156
  } else if (arrayProperty.of.storage) {
153
157
  content = <ArrayOfStorageComponentsPreview
154
158
  {...props}
155
159
  value={value}
156
- property={property as ResolvedArrayProperty}/>;
160
+ property={property as ResolvedArrayProperty} />;
157
161
  } else {
158
162
  content = <ArrayOfStringsPreview
159
163
  {...props}
160
164
  value={value as string[]}
161
- property={property as ResolvedArrayProperty}/>;
165
+ property={property as ResolvedArrayProperty} />;
162
166
  }
163
167
  } else if (arrayProperty.of.dataType === "number" && arrayProperty.of.enumValues) {
164
168
  content = <ArrayPropertyEnumPreview
165
169
  {...props}
166
170
  value={value as string[]}
167
- property={property as ResolvedArrayProperty}/>;
171
+ property={property as ResolvedArrayProperty} />;
168
172
  } else {
169
173
  content = <ArrayPropertyPreview {...props}
170
- value={value}
171
- property={property as ResolvedArrayProperty}/>;
174
+ value={value}
175
+ property={property as ResolvedArrayProperty} />;
172
176
  }
173
177
  } else if (arrayProperty.oneOf) {
174
178
  content = <ArrayOneOfPreview {...props}
175
- value={value}
176
- property={property as ResolvedArrayProperty}/>;
179
+ value={value}
180
+ property={property as ResolvedArrayProperty} />;
177
181
  }
178
182
  } else {
179
183
  content = buildWrongValueType(propertyKey, property.dataType, value);
@@ -182,13 +186,17 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
182
186
  if (typeof value === "object") {
183
187
  content =
184
188
  <MapPropertyPreview {...props}
185
- property={property as ResolvedMapProperty}/>;
189
+ property={property as ResolvedMapProperty} />;
186
190
  } else {
187
191
  content = buildWrongValueType(propertyKey, property.dataType, value);
188
192
  }
189
193
  } else if (property.dataType === "date") {
190
194
  if (value instanceof Date) {
191
- content = <DatePreview date={value}/>;
195
+ content = <DatePreview
196
+ date={value}
197
+ mode={property.mode}
198
+ timezone={property.timezone}
199
+ />;
192
200
  } else {
193
201
  content = buildWrongValueType(propertyKey, property.dataType, value);
194
202
  }
@@ -207,20 +215,20 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
207
215
  content = buildWrongValueType(propertyKey, property.dataType, value);
208
216
  }
209
217
  } else {
210
- content = <EmptyValue/>;
218
+ content = <EmptyValue />;
211
219
  }
212
220
 
213
221
  } else if (property.dataType === "boolean") {
214
222
  if (typeof value === "boolean") {
215
- content = <BooleanPreview value={value} size={size} property={property}/>;
223
+ content = <BooleanPreview value={value} size={size} property={property} />;
216
224
  } else {
217
225
  content = buildWrongValueType(propertyKey, property.dataType, value);
218
226
  }
219
227
  } else if (property.dataType === "number") {
220
228
  if (typeof value === "number") {
221
229
  content = <NumberPropertyPreview {...props}
222
- value={value}
223
- property={property as ResolvedNumberProperty}/>;
230
+ value={value}
231
+ property={property as ResolvedNumberProperty} />;
224
232
  } else {
225
233
  content = buildWrongValueType(propertyKey, property.dataType, value);
226
234
  }
@@ -229,7 +237,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
229
237
  }
230
238
 
231
239
  return content === undefined || content === null || (Array.isArray(content) && content.length === 0)
232
- ? <EmptyValue/>
240
+ ? <EmptyValue />
233
241
  : content;
234
242
  }, equal);
235
243
 
@@ -237,6 +245,6 @@ function buildWrongValueType(name: string | undefined, dataType: string, value:
237
245
  console.warn(`Unexpected value for property ${name}, of type ${dataType}`, value);
238
246
  return (
239
247
  <ErrorView title={"Unexpected value"}
240
- error={`${JSON.stringify(value)}`}/>
248
+ error={`${JSON.stringify(value)}`} />
241
249
  );
242
250
  }
@@ -52,4 +52,10 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
52
52
  */
53
53
  interactive?: boolean;
54
54
 
55
+ /**
56
+ * If true, image previews will fill their container completely.
57
+ * Only applies to image type properties.
58
+ */
59
+ fill?: boolean;
60
+
55
61
  }
@@ -5,18 +5,86 @@ import * as locales from "date-fns/locale";
5
5
  import { useCustomizationController } from "../../hooks";
6
6
  import { defaultDateFormat } from "../../util";
7
7
 
8
+ export interface DatePreviewProps {
9
+ date: Date;
10
+ /**
11
+ * Display mode: "date" for date-only, "date_time" for date and time
12
+ */
13
+ mode?: "date" | "date_time";
14
+ /**
15
+ * IANA timezone identifier (e.g., "America/New_York")
16
+ * When specified, the date will be displayed in this timezone
17
+ */
18
+ timezone?: string;
19
+ }
20
+
8
21
  /**
9
22
  * @group Preview components
10
23
  */
11
24
  export function DatePreview({
12
- date
13
- }: { date: Date }): React.ReactElement {
25
+ date,
26
+ mode = "date_time",
27
+ timezone
28
+ }: DatePreviewProps): React.ReactElement {
14
29
 
15
30
  const customizationController = useCustomizationController();
16
31
  // @ts-ignore
17
32
  const dateUtilsLocale = customizationController?.locale ? locales[customizationController?.locale] : undefined;
18
- const dateFormat: string = customizationController?.dateTimeFormat ?? defaultDateFormat;
19
- const formattedDate = date ? format(date, dateFormat, { locale: dateUtilsLocale }) : "";
33
+
34
+ if (!date) {
35
+ return <></>;
36
+ }
37
+
38
+ // If timezone is specified, format in that timezone using Intl.DateTimeFormat
39
+ if (timezone) {
40
+ const options: Intl.DateTimeFormatOptions = {
41
+ year: "numeric",
42
+ month: "short",
43
+ day: "numeric",
44
+ timeZone: timezone
45
+ };
46
+
47
+ if (mode === "date_time") {
48
+ options.hour = "2-digit";
49
+ options.minute = "2-digit";
50
+ }
51
+
52
+ const formatter = new Intl.DateTimeFormat(customizationController?.locale ?? "en-US", options);
53
+ const formattedDate = formatter.format(date);
54
+
55
+ // Get timezone abbreviation
56
+ const tzFormatter = new Intl.DateTimeFormat("en-US", {
57
+ timeZone: timezone,
58
+ timeZoneName: "short"
59
+ });
60
+ const parts = tzFormatter.formatToParts(date);
61
+ const tzAbbrev = parts.find(p => p.type === "timeZoneName")?.value ?? "";
62
+
63
+ return (
64
+ <span className="flex items-center gap-1">
65
+ {formattedDate}
66
+ {tzAbbrev && (
67
+ <span className="text-xs text-slate-500 dark:text-slate-400">
68
+ ({tzAbbrev})
69
+ </span>
70
+ )}
71
+ </span>
72
+ );
73
+ }
74
+
75
+ // No timezone specified: use local formatting with date-fns
76
+ let dateFormat: string;
77
+ if (mode === "date") {
78
+ // Date-only format (no time)
79
+ dateFormat = customizationController?.dateTimeFormat
80
+ ? customizationController.dateTimeFormat.replace(/[, ]*[HhKk].*$/, "").trim()
81
+ : "PP"; // e.g., "Apr 29, 2024"
82
+ } else {
83
+ // Full date-time format
84
+ dateFormat = customizationController?.dateTimeFormat ?? defaultDateFormat;
85
+ }
86
+
87
+ const formattedDate = format(date, dateFormat, { locale: dateUtilsLocale });
20
88
 
21
89
  return (
22
90
  <>
@@ -6,5 +6,5 @@ import React from "react";
6
6
  export function EmptyValue() {
7
7
 
8
8
  return <div
9
- className="rounded-full bg-surface-200 bg-opacity-30 dark:bg-opacity-20 w-5 h-2 inline-block"/>;
9
+ className="rounded-full bg-surface-200 bg-opacity-30 bg-surface-200/30 dark:bg-opacity-20 dark:bg-surface-200/20 w-5 h-2 inline-block"/>;
10
10
  }
@@ -9,39 +9,55 @@ import { ContentCopyIcon, IconButton, OpenInNewIcon, Tooltip } from "@firecms/ui
9
9
  */
10
10
  export interface ImagePreviewProps {
11
11
  size: PreviewSize,
12
- url: string
12
+ url: string,
13
+ /**
14
+ * If true, image fills its container completely with object-fit cover
15
+ */
16
+ fill?: boolean
13
17
  }
14
18
 
15
19
  /**
16
20
  * @group Preview components
17
21
  */
18
22
  export function ImagePreview({
19
- size,
20
- url
21
- }: ImagePreviewProps) {
23
+ size,
24
+ url,
25
+ fill
26
+ }: ImagePreviewProps) {
22
27
 
23
28
  const imageSize = useMemo(() => getThumbnailMeasure(size), [size]);
24
29
 
30
+ // Fill mode - image fills its container completely
31
+ if (fill) {
32
+ return (
33
+ <img src={url}
34
+ className={"w-full h-full object-cover"}
35
+ key={"fill_image_preview_" + url}
36
+ loading="lazy"
37
+ />
38
+ );
39
+ }
40
+
25
41
  if (size === "small") {
26
42
  return (
27
43
  <img src={url}
28
- className={"rounded-md"}
29
- key={"tiny_image_preview_" + url}
30
- style={{
31
- position: "relative",
32
- objectFit: "cover",
33
- width: imageSize,
34
- height: imageSize,
35
- maxHeight: "100%"
36
- }}/>
44
+ className={"rounded-md"}
45
+ key={"tiny_image_preview_" + url}
46
+ style={{
47
+ position: "relative",
48
+ objectFit: "cover",
49
+ width: imageSize,
50
+ height: imageSize,
51
+ maxHeight: "100%"
52
+ }} />
37
53
  );
38
54
  }
39
55
 
40
56
  const imageStyle: CSSProperties =
41
- {
42
- maxWidth: "100%",
43
- maxHeight: "100%"
44
- };
57
+ {
58
+ maxWidth: "100%",
59
+ maxHeight: "100%"
60
+ };
45
61
 
46
62
  return (
47
63
  <div
@@ -53,8 +69,8 @@ export function ImagePreview({
53
69
  key={"image_preview_" + url}>
54
70
 
55
71
  <img src={url}
56
- className={"rounded-md"}
57
- style={imageStyle}/>
72
+ className={"rounded-md"}
73
+ style={imageStyle} />
58
74
 
59
75
  <div className={"flex flex-row gap-2 absolute bottom-[-4px] right-[-4px] invisible group-hover:visible"}>
60
76
  {navigator && <Tooltip
@@ -69,7 +85,7 @@ export function ImagePreview({
69
85
  return navigator.clipboard.writeText(url);
70
86
  }}>
71
87
  <ContentCopyIcon className={"text-surface-700 dark:text-surface-300"}
72
- size={"smallest"}/>
88
+ size={"smallest"} />
73
89
  </IconButton>
74
90
  </Tooltip>}
75
91
 
@@ -85,7 +101,7 @@ export function ImagePreview({
85
101
  onClick={(e: any) => e.stopPropagation()}
86
102
  >
87
103
  <OpenInNewIcon className={"text-surface-700 dark:text-surface-300"}
88
- size={"smallest"}/>
104
+ size={"smallest"} />
89
105
  </IconButton>
90
106
  </Tooltip>
91
107
  </div>
@@ -12,6 +12,7 @@ type StorageThumbnailProps = {
12
12
  storeUrl: boolean;
13
13
  size: PreviewSize;
14
14
  interactive?: boolean;
15
+ fill?: boolean;
15
16
  };
16
17
 
17
18
  /**
@@ -22,18 +23,20 @@ export const StorageThumbnail = React.memo<StorageThumbnailProps>(StorageThumbna
22
23
  function areEqual(prevProps: StorageThumbnailProps, nextProps: StorageThumbnailProps) {
23
24
  return prevProps.size === nextProps.size &&
24
25
  prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl &&
25
- prevProps.storeUrl === nextProps.storeUrl&&
26
- prevProps.interactive === nextProps.interactive;
26
+ prevProps.storeUrl === nextProps.storeUrl &&
27
+ prevProps.interactive === nextProps.interactive &&
28
+ prevProps.fill === nextProps.fill;
27
29
  }
28
30
 
29
31
  const URL_CACHE: Record<string, DownloadConfig> = {};
30
32
 
31
33
  export function StorageThumbnailInternal({
32
- storeUrl,
33
- interactive,
34
- storagePathOrDownloadUrl,
35
- size
36
- }: StorageThumbnailProps) {
34
+ storeUrl,
35
+ interactive,
36
+ storagePathOrDownloadUrl,
37
+ size,
38
+ fill
39
+ }: StorageThumbnailProps) {
37
40
 
38
41
  const [error, setError] = React.useState<Error | undefined>(undefined);
39
42
  const storage = useStorageSource();
@@ -70,11 +73,12 @@ export function StorageThumbnailInternal({
70
73
 
71
74
  return downloadConfig?.url
72
75
  ? <UrlComponentPreview previewType={previewType}
73
- url={downloadConfig.url}
74
- interactive={interactive}
75
- size={size}
76
- hint={storagePathOrDownloadUrl}/>
77
- : renderSkeletonImageThumbnail(size);
76
+ url={downloadConfig.url}
77
+ interactive={interactive}
78
+ size={size}
79
+ fill={fill}
80
+ hint={storagePathOrDownloadUrl} />
81
+ : renderSkeletonImageThumbnail(size, fill);
78
82
  }
79
83
 
80
84
  function getFiletype(input: string): FileType {
@@ -11,31 +11,33 @@ import { EmptyValue } from "./EmptyValue";
11
11
  * @group Preview components
12
12
  */
13
13
  export function UrlComponentPreview({
14
- url,
15
- previewType,
16
- size,
17
- hint,
18
- interactive = true
19
- }: {
14
+ url,
15
+ previewType,
16
+ size,
17
+ hint,
18
+ interactive = true,
19
+ fill
20
+ }: {
20
21
  url: string,
21
22
  previewType?: PreviewType,
22
23
  size: PreviewSize,
23
24
  hint?: string,
24
25
  // for video controls
25
- interactive?: boolean
26
+ interactive?: boolean,
27
+ fill?: boolean
26
28
  }): React.ReactElement {
27
29
 
28
30
  if (!previewType) {
29
- if (!url || !url.trim()) return <EmptyValue/>;
31
+ if (!url || !url.trim()) return <EmptyValue />;
30
32
  return (
31
33
  <a className="flex gap-4 break-words items-center font-medium text-primary visited:text-primary dark:visited:text-primary dark:text-primary"
32
- href={url}
33
- rel="noopener noreferrer"
34
- onMouseDown={(e: React.MouseEvent) => {
35
- e.preventDefault();
36
- }}
37
- target="_blank">
38
- <OpenInNewIcon size={"small"}/>
34
+ href={url}
35
+ rel="noopener noreferrer"
36
+ onMouseDown={(e: React.MouseEvent) => {
37
+ e.preventDefault();
38
+ }}
39
+ target="_blank">
40
+ <OpenInNewIcon size={"small"} />
39
41
  {url}
40
42
  </a>
41
43
  );
@@ -43,16 +45,17 @@ export function UrlComponentPreview({
43
45
 
44
46
  if (previewType === "image") {
45
47
  return <ImagePreview url={url}
46
- size={size}/>;
48
+ size={size}
49
+ fill={fill} />;
47
50
  } else if (previewType === "audio") {
48
51
  return <audio controls
49
- className={"max-w-100%"}
50
- src={url}>
52
+ className={"max-w-100%"}
53
+ src={url}>
51
54
  Your browser does not support the
52
55
  <code>audio</code> element.
53
56
  </audio>;
54
57
  } else if (previewType === "video") {
55
- return <VideoPreview size={size} src={url} interactive={interactive}/>;
58
+ return <VideoPreview size={size} src={url} interactive={interactive} />;
56
59
  } else {
57
60
  return (
58
61
  <Tooltip title={hint}>
@@ -66,7 +69,7 @@ export function UrlComponentPreview({
66
69
  width: getThumbnailMeasure(size),
67
70
  height: getThumbnailMeasure(size)
68
71
  }}>
69
- <DescriptionIcon className="text-surface-700 dark:text-surface-300"/>
72
+ <DescriptionIcon className="text-surface-700 dark:text-surface-300" />
70
73
  {hint && <Typography
71
74
  className="max-w-full truncate rtl text-left"
72
75
  variant={"caption"}>{hint}</Typography>}
@@ -77,10 +80,10 @@ export function UrlComponentPreview({
77
80
  }
78
81
 
79
82
  function VideoPreview({
80
- size,
81
- src,
82
- interactive
83
- }: { size: PreviewSize, src: string, interactive: boolean }) {
83
+ size,
84
+ src,
85
+ interactive
86
+ }: { size: PreviewSize, src: string, interactive: boolean }) {
84
87
 
85
88
  const imageSize = useMemo(() => {
86
89
  if (size === "small")
@@ -106,6 +109,6 @@ function VideoPreview({
106
109
  }}
107
110
  {...videoProps}
108
111
  className={cls("max-w-100% rounded", { "pointer-events-none": !interactive })}>
109
- <source src={src}/>
112
+ <source src={src} />
110
113
  </video>;
111
114
  }
@@ -12,12 +12,12 @@ import { ErrorBoundary } from "../../components";
12
12
  * @group Preview components
13
13
  */
14
14
  export function ArrayOfStorageComponentsPreview({
15
- propertyKey,
16
- // entity,
17
- value,
18
- property: inputProperty,
19
- size
20
- }: PropertyPreviewProps<any[]>) {
15
+ propertyKey,
16
+ // entity,
17
+ value,
18
+ property: inputProperty,
19
+ size
20
+ }: PropertyPreviewProps<any[]>) {
21
21
 
22
22
  const authController = useAuthController();
23
23
  const customizationController = useCustomizationController();
@@ -28,6 +28,8 @@ export function ArrayOfStorageComponentsPreview({
28
28
  authController
29
29
  });
30
30
 
31
+ if (!property) return null;
32
+
31
33
  if (Array.isArray(property.of)) {
32
34
  throw Error("Using array properties instead of single one in `of` in ArrayProperty");
33
35
  }
@@ -47,7 +49,7 @@ export function ArrayOfStorageComponentsPreview({
47
49
  value={v}
48
50
  // entity={entity}
49
51
  property={property.of as ResolvedProperty<string>}
50
- size={childSize}/>
52
+ size={childSize} />
51
53
  </ErrorBoundary>
52
54
  )}
53
55
  </div>
@@ -10,12 +10,12 @@ import { ErrorBoundary } from "../../components";
10
10
  * @group Preview components
11
11
  */
12
12
  export function ArrayOfStringsPreview({
13
- propertyKey,
14
- value,
15
- property: inputProperty,
16
- // entity,
17
- size
18
- }: PropertyPreviewProps<string[]>) {
13
+ propertyKey,
14
+ value,
15
+ property: inputProperty,
16
+ // entity,
17
+ size
18
+ }: PropertyPreviewProps<string[]>) {
19
19
  const authController = useAuthController();
20
20
  const customizationController = useCustomizationController();
21
21
  const property = resolveArrayProperty({
@@ -25,6 +25,8 @@ export function ArrayOfStringsPreview({
25
25
  authController
26
26
  });
27
27
 
28
+ if (!property) return null;
29
+
28
30
  if (Array.isArray(property.of)) {
29
31
  throw Error("Using array properties instead of single one in `of` in ArrayProperty");
30
32
  }
@@ -43,10 +45,10 @@ export function ArrayOfStringsPreview({
43
45
  <div key={`preview_array_strings_${propertyKey}_${index}`}>
44
46
  <ErrorBoundary>
45
47
  <StringPropertyPreview propertyKey={propertyKey}
46
- property={stringProperty}
47
- value={v}
48
+ property={stringProperty}
49
+ value={v}
48
50
  // entity={entity}
49
- size={size}/>
51
+ size={size} />
50
52
  </ErrorBoundary>
51
53
  </div>
52
54
  )}