@strato-admin/cloudscape 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/dist/Admin.d.ts +6 -2
  2. package/dist/Admin.js +14 -8
  3. package/dist/RecordLink.js +5 -4
  4. package/dist/Settings.d.ts +17 -0
  5. package/dist/Settings.js +14 -0
  6. package/dist/button/BulkDeleteButton.d.ts +2 -1
  7. package/dist/button/BulkDeleteButton.js +17 -11
  8. package/dist/button/Button.d.ts +2 -1
  9. package/dist/button/CancelButton.d.ts +6 -0
  10. package/dist/button/CancelButton.js +10 -0
  11. package/dist/button/CreateButton.js +9 -8
  12. package/dist/button/DeleteButton.d.ts +13 -0
  13. package/dist/button/DeleteButton.js +36 -0
  14. package/dist/button/EditButton.d.ts +1 -1
  15. package/dist/button/EditButton.js +10 -10
  16. package/dist/button/SaveButton.js +2 -2
  17. package/dist/button/index.d.ts +2 -0
  18. package/dist/button/index.js +2 -0
  19. package/dist/collection-hooks/interfaces.d.ts +7 -3
  20. package/dist/collection-hooks/useCollection.d.ts +1 -1
  21. package/dist/collection-hooks/useCollection.js +15 -10
  22. package/dist/create/Create.d.ts +9 -17
  23. package/dist/create/Create.js +40 -12
  24. package/dist/create/CreateHeader.d.ts +2 -2
  25. package/dist/create/CreateHeader.js +4 -5
  26. package/dist/defaults.d.ts +6 -0
  27. package/dist/defaults.js +21 -0
  28. package/dist/detail/Detail.d.ts +33 -0
  29. package/dist/detail/Detail.js +22 -0
  30. package/dist/detail/DetailHeader.d.ts +11 -0
  31. package/dist/detail/{ShowHeader.js → DetailHeader.js} +7 -5
  32. package/dist/detail/DetailHub.d.ts +27 -0
  33. package/dist/detail/DetailHub.js +63 -0
  34. package/dist/detail/KeyValuePairs.d.ts +7 -1
  35. package/dist/detail/KeyValuePairs.js +14 -8
  36. package/dist/detail/index.d.ts +3 -2
  37. package/dist/detail/index.js +3 -2
  38. package/dist/edit/Edit.d.ts +8 -19
  39. package/dist/edit/Edit.js +48 -12
  40. package/dist/edit/EditHeader.d.ts +2 -2
  41. package/dist/edit/EditHeader.js +5 -4
  42. package/dist/field/ArrayField.d.ts +26 -10
  43. package/dist/field/ArrayField.js +38 -10
  44. package/dist/field/BadgeField.d.ts +1 -1
  45. package/dist/field/BadgeField.js +1 -1
  46. package/dist/field/BooleanField.d.ts +1 -1
  47. package/dist/field/BooleanField.js +2 -2
  48. package/dist/field/CurrencyField.d.ts +1 -1
  49. package/dist/field/CurrencyField.js +1 -1
  50. package/dist/field/DateField.d.ts +1 -1
  51. package/dist/field/DateField.js +1 -1
  52. package/dist/field/IdField.d.ts +1 -1
  53. package/dist/field/IdField.js +3 -3
  54. package/dist/field/NumberField.d.ts +1 -1
  55. package/dist/field/NumberField.js +1 -1
  56. package/dist/field/ReferenceField.d.ts +1 -1
  57. package/dist/field/ReferenceField.js +4 -2
  58. package/dist/field/ReferenceManyField.d.ts +35 -4
  59. package/dist/field/ReferenceManyField.js +17 -4
  60. package/dist/field/StatusIndicatorField.d.ts +1 -1
  61. package/dist/field/StatusIndicatorField.js +6 -5
  62. package/dist/field/TextField.d.ts +1 -1
  63. package/dist/field/TextField.js +1 -1
  64. package/dist/field/types.d.ts +9 -9
  65. package/dist/form/Form.d.ts +12 -2
  66. package/dist/form/Form.js +10 -16
  67. package/dist/form/index.d.ts +1 -1
  68. package/dist/form/index.js +1 -1
  69. package/dist/hooks/useSchemaFields.d.ts +22 -0
  70. package/dist/hooks/useSchemaFields.js +45 -0
  71. package/dist/i18n/Message.d.ts +15 -0
  72. package/dist/i18n/Message.js +19 -0
  73. package/dist/i18n/RecordMessage.d.ts +14 -0
  74. package/dist/i18n/RecordMessage.js +16 -0
  75. package/dist/i18n/index.d.ts +3 -0
  76. package/dist/i18n/index.js +2 -0
  77. package/dist/i18n/types.d.ts +19 -0
  78. package/dist/i18n/types.js +1 -0
  79. package/dist/index.d.ts +5 -1
  80. package/dist/index.js +5 -1
  81. package/dist/input/ArrayInput.d.ts +33 -0
  82. package/dist/input/{AttributeEditor.js → ArrayInput.js} +18 -11
  83. package/dist/input/AutocompleteInput.d.ts +1 -1
  84. package/dist/input/AutocompleteInput.js +3 -3
  85. package/dist/input/BooleanInput.d.ts +6 -0
  86. package/dist/input/BooleanInput.js +23 -0
  87. package/dist/input/CommonInputProps.d.ts +6 -0
  88. package/dist/input/CommonInputProps.js +6 -0
  89. package/dist/input/FieldTitle.js +4 -4
  90. package/dist/input/FormField.js +12 -3
  91. package/dist/input/FormFieldContext.d.ts +1 -1
  92. package/dist/input/NumberInput.d.ts +1 -1
  93. package/dist/input/NumberInput.js +3 -3
  94. package/dist/input/ReferenceInput.d.ts +1 -1
  95. package/dist/input/ReferenceInput.js +22 -12
  96. package/dist/input/SelectInput.d.ts +1 -1
  97. package/dist/input/SelectInput.js +3 -3
  98. package/dist/input/SliderInput.d.ts +1 -1
  99. package/dist/input/SliderInput.js +4 -4
  100. package/dist/input/TextAreaInput.d.ts +1 -1
  101. package/dist/input/TextAreaInput.js +3 -3
  102. package/dist/input/TextInput.d.ts +1 -1
  103. package/dist/input/TextInput.js +6 -12
  104. package/dist/input/index.d.ts +2 -1
  105. package/dist/input/index.js +2 -1
  106. package/dist/input/types.d.ts +33 -2
  107. package/dist/layout/AppLayout.js +6 -3
  108. package/dist/layout/Notifications.d.ts +1 -0
  109. package/dist/layout/Notifications.js +51 -0
  110. package/dist/layout/Ready.d.ts +6 -0
  111. package/dist/layout/Ready.js +24 -0
  112. package/dist/layout/TopNavigation.d.ts +4 -2
  113. package/dist/layout/TopNavigation.js +7 -7
  114. package/dist/layout/index.d.ts +2 -0
  115. package/dist/layout/index.js +2 -0
  116. package/dist/list/Cards.d.ts +31 -4
  117. package/dist/list/Cards.js +81 -10
  118. package/dist/list/List.d.ts +9 -12
  119. package/dist/list/List.js +41 -11
  120. package/dist/list/Table.d.ts +8 -4
  121. package/dist/list/Table.js +55 -55
  122. package/dist/list/TableHeader.d.ts +2 -2
  123. package/dist/list/TableHeader.js +4 -5
  124. package/dist/theme/ThemeManager.js +1 -1
  125. package/package.json +8 -5
  126. package/src/Admin.tsx +35 -18
  127. package/src/RecordLink.stories.tsx +1 -1
  128. package/src/RecordLink.tsx +5 -4
  129. package/src/Settings.tsx +16 -0
  130. package/src/__mocks__/ra-core.tsx +83 -0
  131. package/src/__mocks__/strato-core.tsx +36 -42
  132. package/src/button/BulkDeleteButton.test.tsx +17 -4
  133. package/src/button/BulkDeleteButton.tsx +24 -29
  134. package/src/button/Button.tsx +31 -2
  135. package/src/button/CancelButton.tsx +20 -0
  136. package/src/button/CreateButton.tsx +12 -10
  137. package/src/button/DeleteButton.tsx +96 -0
  138. package/src/button/EditButton.tsx +13 -12
  139. package/src/button/SaveButton.tsx +2 -3
  140. package/src/button/index.ts +2 -0
  141. package/src/collection-hooks/interfaces.ts +7 -3
  142. package/src/collection-hooks/useCollection.test.ts +115 -2
  143. package/src/collection-hooks/useCollection.ts +15 -10
  144. package/src/create/Create.test.tsx +3 -3
  145. package/src/create/Create.tsx +68 -37
  146. package/src/create/CreateHeader.tsx +6 -10
  147. package/src/defaults.tsx +28 -0
  148. package/src/detail/Detail-CollectionFields.test.tsx +84 -0
  149. package/src/detail/Detail.test.tsx +91 -0
  150. package/src/detail/Detail.tsx +48 -0
  151. package/src/detail/{ShowHeader.test.tsx → DetailHeader.test.tsx} +11 -9
  152. package/src/detail/DetailHeader.tsx +42 -0
  153. package/src/detail/DetailHub.tsx +88 -0
  154. package/src/detail/KeyValuePairs.test.tsx +2 -2
  155. package/src/detail/KeyValuePairs.tsx +25 -18
  156. package/src/detail/index.ts +3 -2
  157. package/src/edit/Edit.test.tsx +7 -5
  158. package/src/edit/Edit.tsx +92 -40
  159. package/src/edit/EditHeader.tsx +7 -5
  160. package/src/field/ArrayField.tsx +57 -11
  161. package/src/field/BadgeField.tsx +2 -3
  162. package/src/field/BooleanField.test.tsx +2 -3
  163. package/src/field/BooleanField.tsx +3 -3
  164. package/src/field/CurrencyField.tsx +1 -1
  165. package/src/field/DateField.tsx +1 -1
  166. package/src/field/IdField.test.tsx +8 -20
  167. package/src/field/IdField.tsx +5 -20
  168. package/src/field/NumberField.tsx +1 -1
  169. package/src/field/ReferenceField.test.tsx +15 -6
  170. package/src/field/ReferenceField.tsx +10 -7
  171. package/src/field/ReferenceManyField.test.tsx +55 -10
  172. package/src/field/ReferenceManyField.tsx +84 -13
  173. package/src/field/StatusIndicatorField.test.tsx +7 -21
  174. package/src/field/StatusIndicatorField.tsx +8 -20
  175. package/src/field/TextField.tsx +1 -1
  176. package/src/field/types.ts +12 -13
  177. package/src/form/Form.test.tsx +8 -4
  178. package/src/form/Form.tsx +24 -19
  179. package/src/form/index.ts +1 -1
  180. package/src/hooks/useSchemaFields.ts +89 -0
  181. package/src/i18n/Message.tsx +22 -0
  182. package/src/i18n/RecordMessage.tsx +22 -0
  183. package/src/i18n/index.ts +3 -0
  184. package/src/i18n/types.ts +19 -0
  185. package/src/index.ts +5 -1
  186. package/src/input/ArrayInput.test.tsx +81 -0
  187. package/src/input/{AttributeEditor.tsx → ArrayInput.tsx} +36 -18
  188. package/src/input/AutocompleteInput.test.tsx +2 -4
  189. package/src/input/AutocompleteInput.tsx +9 -11
  190. package/src/input/BooleanInput.tsx +42 -0
  191. package/src/input/CommonInputProps.tsx +8 -0
  192. package/src/input/FieldTitle.tsx +3 -15
  193. package/src/input/FormField.tsx +78 -67
  194. package/src/input/FormFieldContext.ts +1 -1
  195. package/src/input/NumberInput.tsx +10 -7
  196. package/src/input/ReferenceInput.test.tsx +12 -2
  197. package/src/input/ReferenceInput.tsx +32 -14
  198. package/src/input/SelectInput.tsx +14 -17
  199. package/src/input/SliderInput.test.tsx +2 -3
  200. package/src/input/SliderInput.tsx +48 -38
  201. package/src/input/TextAreaInput.tsx +10 -6
  202. package/src/input/TextInput.test.tsx +2 -4
  203. package/src/input/TextInput.tsx +35 -20
  204. package/src/input/index.ts +2 -1
  205. package/src/input/types.ts +40 -8
  206. package/src/layout/AppLayout.test.tsx +23 -3
  207. package/src/layout/AppLayout.tsx +11 -8
  208. package/src/layout/Notifications.test.tsx +102 -0
  209. package/src/layout/Notifications.tsx +61 -0
  210. package/src/layout/Ready.tsx +123 -0
  211. package/src/layout/TopNavigation.test.tsx +2 -3
  212. package/src/layout/TopNavigation.tsx +9 -8
  213. package/src/layout/index.ts +2 -0
  214. package/src/list/Cards.test.tsx +320 -0
  215. package/src/list/Cards.tsx +146 -16
  216. package/src/list/List.tsx +87 -26
  217. package/src/list/Table.test.tsx +40 -5
  218. package/src/list/Table.tsx +89 -98
  219. package/src/list/TableHeader.test.tsx +15 -11
  220. package/src/list/TableHeader.tsx +6 -8
  221. package/src/theme/ThemeManager.tsx +1 -1
  222. package/dist/__mocks__/strato-core.js +0 -50
  223. package/dist/__mocks__to__delete/strato-core.js +0 -50
  224. package/dist/detail/Show.d.ts +0 -39
  225. package/dist/detail/Show.js +0 -40
  226. package/dist/detail/ShowHeader.d.ts +0 -7
  227. package/dist/input/AttributeEditor.d.ts +0 -25
  228. package/src/detail/Show.test.tsx +0 -96
  229. package/src/detail/Show.tsx +0 -104
  230. package/src/detail/ShowHeader.tsx +0 -35
  231. package/src/input/AttributeEditor.test.tsx +0 -147
@@ -4,8 +4,10 @@ import Pagination from '@cloudscape-design/components/pagination';
4
4
  import Box from '@cloudscape-design/components/box';
5
5
  import TextFilter from '@cloudscape-design/components/text-filter';
6
6
  import CollectionPreferences from '@cloudscape-design/components/collection-preferences';
7
- import { RecordContextProvider, RaRecord, useResourceContext, useFieldSchema, useResourceDefinition, useGetResourceLabel, useTranslateLabel, useTranslate } from '@strato-admin/core';
7
+ import { RecordContextProvider, RaRecord, useTranslateLabel, useTranslate } from '@strato-admin/ra-core';
8
+ import { useResourceSchema, useSettings } from '@strato-admin/core';
8
9
  import { useCollection } from '../collection-hooks';
10
+ import { useSchemaFields } from '../hooks/useSchemaFields';
9
11
  import TextField from '../field/TextField';
10
12
  import NumberField from '../field/NumberField';
11
13
  import DateField from '../field/DateField';
@@ -33,7 +35,7 @@ export const Column = ({ children, source, link, field: FieldComponent }: Column
33
35
  return (
34
36
  <>
35
37
  {React.Children.map(children, (child) =>
36
- React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child
38
+ React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child,
37
39
  )}
38
40
  </>
39
41
  );
@@ -53,7 +55,7 @@ export const NumberColumn = ({ children, source, link, field: FieldComponent }:
53
55
  return (
54
56
  <>
55
57
  {React.Children.map(children, (child) =>
56
- React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child
58
+ React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child,
57
59
  )}
58
60
  </>
59
61
  );
@@ -74,7 +76,7 @@ export const DateColumn = ({ children, source, link, field: FieldComponent }: Da
74
76
  return (
75
77
  <>
76
78
  {React.Children.map(children, (child) =>
77
- React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child
79
+ React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child,
78
80
  )}
79
81
  </>
80
82
  );
@@ -94,7 +96,7 @@ export const BooleanColumn = ({ children, source, field: FieldComponent }: Boole
94
96
  return (
95
97
  <>
96
98
  {React.Children.map(children, (child) =>
97
- React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child
99
+ React.isValidElement(child) ? React.cloneElement(child, { source } as any) : child,
98
100
  )}
99
101
  </>
100
102
  );
@@ -136,6 +138,10 @@ export interface TableProps<RecordType extends RaRecord = any> extends Partial<
136
138
  * The title content of the table. Can be a string or a React node.
137
139
  */
138
140
  title?: React.ReactNode;
141
+ /**
142
+ * The description content of the table.
143
+ */
144
+ description?: React.ReactNode;
139
145
  /**
140
146
  * Actions to display in the table header, typically a button group.
141
147
  */
@@ -181,16 +187,9 @@ export interface TableProps<RecordType extends RaRecord = any> extends Partial<
181
187
  * Can be an array of field sources/IDs.
182
188
  * If not specified, the first 5 fields will be shown.
183
189
  */
184
- defaultVisibleFields?: string[];
190
+ display?: string[];
185
191
  }
186
192
 
187
- const defaultPageSizeOptions = [
188
- { value: 10, label: '10 items' },
189
- { value: 25, label: '25 items' },
190
- { value: 50, label: '50 items' },
191
- { value: 100, label: '100 items' },
192
- ];
193
-
194
193
  /**
195
194
  * The Table component provides a declarative way to build data tables with Cloudscape components
196
195
  * while integrating with React Admin's data fetching and state management.
@@ -205,43 +204,44 @@ const defaultPageSizeOptions = [
205
204
  */
206
205
  export const Table = <RecordType extends RaRecord = any>({
207
206
  title,
207
+ description,
208
208
  actions,
209
209
  children,
210
210
  include,
211
211
  exclude,
212
212
  filtering = true,
213
213
  filteringPlaceholder,
214
- pageSizeOptions = defaultPageSizeOptions,
214
+ pageSizeOptions,
215
215
  preferences = true,
216
216
  reorderable = true,
217
- defaultVisibleFields,
217
+ display,
218
218
  selectionType,
219
+ pagination = true,
219
220
  ...props
220
221
  }: TableProps<RecordType>) => {
221
- const resource = useResourceContext();
222
222
  const translate = useTranslate();
223
+ const { listPageSizes, listPageSizeLabel } = useSettings();
224
+
225
+ const resolvedPageSizeOptions =
226
+ pageSizeOptions ??
227
+ (listPageSizes && listPageSizeLabel
228
+ ? listPageSizes.map((value) => ({
229
+ value,
230
+ label: listPageSizeLabel(value),
231
+ }))
232
+ : undefined);
223
233
  const translateLabel = useTranslateLabel();
224
- const schemaChildren = useFieldSchema();
225
- const resourceDefinition = useResourceDefinition({ resource });
226
-
227
- const finalSelectionType = selectionType ?? (resourceDefinition?.options?.canDelete ? 'multi' : undefined);
234
+ const { resource, definition, label: schemaLabel, listDisplay } = useResourceSchema();
228
235
 
229
- const finalChildren = React.useMemo(() => {
230
- const baseChildren = children || schemaChildren;
231
- let result = React.Children.toArray(baseChildren);
236
+ const { getListFields } = useSchemaFields();
232
237
 
233
- if (include) {
234
- result = result.filter(
235
- (child) => React.isValidElement(child) && include.includes((child.props as any).source)
236
- );
237
- } else if (exclude) {
238
- result = result.filter(
239
- (child) => React.isValidElement(child) && !exclude.includes((child.props as any).source)
240
- );
241
- }
238
+ const finalSelectionType = selectionType ?? (definition?.options?.canDelete ? 'multi' : undefined);
239
+ const finalDisplay = display || listDisplay;
242
240
 
243
- return result;
244
- }, [children, schemaChildren, include, exclude]);
241
+ const finalChildren = React.useMemo(
242
+ () => getListFields(children, { include, exclude }),
243
+ [getListFields, children, include, exclude],
244
+ );
245
245
 
246
246
  // 1. Extract columns and options before calling useCollection
247
247
  const extractedColumns = React.useMemo(() => {
@@ -298,22 +298,19 @@ export const Table = <RecordType extends RaRecord = any>({
298
298
  const defaultVisibleContent = React.useMemo(() => {
299
299
  if (extractedColumns.options.length === 0) return undefined;
300
300
 
301
- if (defaultVisibleFields) {
301
+ if (finalDisplay) {
302
302
  // Map user-provided fields to their actual IDs
303
303
  return extractedColumns.options
304
304
  .filter((opt) => {
305
305
  const column = extractedColumns.columns.find((c) => c.id === opt.id);
306
- return (
307
- defaultVisibleFields.includes(opt.id) ||
308
- (column?.sortingField && defaultVisibleFields.includes(column.sortingField))
309
- );
306
+ return finalDisplay.includes(opt.id) || (column?.sortingField && finalDisplay.includes(column.sortingField));
310
307
  })
311
308
  .map((opt) => opt.id);
312
309
  }
313
310
 
314
311
  // Default to first 5 toggleable columns
315
312
  return extractedColumns.options.slice(0, 5).map((opt) => opt.id);
316
- }, [extractedColumns, defaultVisibleFields]);
313
+ }, [extractedColumns, finalDisplay]);
317
314
 
318
315
  const defaultContentDisplay = React.useMemo(() => {
319
316
  if (extractedColumns.options.length === 0) return undefined;
@@ -327,15 +324,10 @@ export const Table = <RecordType extends RaRecord = any>({
327
324
  }, [extractedColumns.options, defaultVisibleContent]);
328
325
 
329
326
  const { items, paginationProps, collectionProps, filterProps, preferencesProps } = useCollection<RecordType>({
330
- filtering: {},
331
- pagination: {},
332
- sorting: {},
333
327
  preferences: {
334
- pageSizeOptions,
335
- visibleContentOptions:
336
- !reorderable && extractedColumns.options.length > 0 ? extractedColumns.options : undefined,
337
- contentDisplayOptions:
338
- reorderable && extractedColumns.options.length > 0 ? extractedColumns.options : undefined,
328
+ pageSizeOptions: resolvedPageSizeOptions as any,
329
+ visibleContentOptions: !reorderable && extractedColumns.options.length > 0 ? extractedColumns.options : undefined,
330
+ contentDisplayOptions: reorderable && extractedColumns.options.length > 0 ? extractedColumns.options : undefined,
339
331
  visibleContent: defaultVisibleContent,
340
332
  contentDisplay: defaultContentDisplay,
341
333
  },
@@ -355,8 +347,6 @@ export const Table = <RecordType extends RaRecord = any>({
355
347
  });
356
348
  }, [extractedColumns.columns, extractedColumns.options, preferencesProps.preferences.visibleContent, reorderable]);
357
349
 
358
- const getResourceLabel = useGetResourceLabel();
359
-
360
350
  const tableHeader = React.useMemo(() => {
361
351
  if (title === null || title === false) {
362
352
  return undefined;
@@ -364,9 +354,52 @@ export const Table = <RecordType extends RaRecord = any>({
364
354
  if (React.isValidElement(title)) {
365
355
  return title;
366
356
  }
367
- const finalTitle = title !== undefined ? title : getResourceLabel(resource as string, 2);
368
- return <TableHeader title={finalTitle} actions={actions} />;
369
- }, [title, actions, resource, getResourceLabel]);
357
+ const finalTitle = title !== undefined ? title : schemaLabel;
358
+ return <TableHeader title={finalTitle} description={description} actions={actions} />;
359
+ }, [title, description, actions, schemaLabel]);
360
+
361
+ const finalPreferences = React.useMemo(() => {
362
+ if (preferences === false) return undefined;
363
+ if (React.isValidElement(preferences)) return preferences;
364
+
365
+ // preferences is true or an object, or we have pageSizeOptions
366
+ if (preferences === true || resolvedPageSizeOptions) {
367
+ return (
368
+ <CollectionPreferences
369
+ {...preferencesProps}
370
+ pageSizePreference={
371
+ resolvedPageSizeOptions
372
+ ? {
373
+ options: resolvedPageSizeOptions as any,
374
+ }
375
+ : undefined
376
+ }
377
+ visibleContentPreference={
378
+ !reorderable && extractedColumns.options.length > 0
379
+ ? {
380
+ title: translate('strato.action.select_columns', { _: 'Select visible columns' }),
381
+ options: [
382
+ {
383
+ label: translate('strato.action.select_columns', { _: 'Select visible columns' }),
384
+ options: extractedColumns.options,
385
+ },
386
+ ],
387
+ }
388
+ : undefined
389
+ }
390
+ contentDisplayPreference={
391
+ reorderable && extractedColumns.options.length > 0
392
+ ? {
393
+ title: translate('strato.action.select_columns', { _: 'Select visible columns' }),
394
+ options: extractedColumns.options,
395
+ }
396
+ : undefined
397
+ }
398
+ />
399
+ );
400
+ }
401
+ return undefined;
402
+ }, [preferences, resolvedPageSizeOptions, preferencesProps, reorderable, extractedColumns.options, translate]);
370
403
 
371
404
  return (
372
405
  <CloudscapeTable
@@ -379,51 +412,9 @@ export const Table = <RecordType extends RaRecord = any>({
379
412
  columnDisplay={reorderable ? preferencesProps.preferences.contentDisplay : undefined}
380
413
  items={items || []}
381
414
  header={tableHeader}
382
- filter={
383
- filtering && (
384
- <TextFilter
385
- {...filterProps}
386
- />
387
- )
388
- }
389
- pagination={<Pagination {...paginationProps} />}
390
- preferences={
391
- preferences === true || pageSizeOptions ? (
392
- <CollectionPreferences
393
- {...preferencesProps}
394
- pageSizePreference={
395
- pageSizeOptions
396
- ? {
397
- options: pageSizeOptions,
398
- }
399
- : undefined
400
- }
401
- visibleContentPreference={
402
- !reorderable && extractedColumns.options.length > 0
403
- ? {
404
- title: translate('ra.action.select_columns', { _: 'Select visible columns' }),
405
- options: [
406
- {
407
- label: translate('ra.action.select_columns', { _: 'Select visible columns' }),
408
- options: extractedColumns.options,
409
- },
410
- ],
411
- }
412
- : undefined
413
- }
414
- contentDisplayPreference={
415
- reorderable && extractedColumns.options.length > 0
416
- ? {
417
- title: translate('ra.action.select_columns', { _: 'Select visible columns' }),
418
- options: extractedColumns.options,
419
- }
420
- : undefined
421
- }
422
- />
423
- ) : React.isValidElement(preferences) ? (
424
- preferences
425
- ) : undefined
426
- }
415
+ filter={filtering && <TextFilter {...filterProps} />}
416
+ pagination={pagination === true ? <Pagination {...paginationProps} /> : pagination || undefined}
417
+ preferences={finalPreferences}
427
418
  />
428
419
  );
429
420
  };
@@ -1,17 +1,16 @@
1
1
  import React from 'react';
2
2
  import { render, cleanup } from '@testing-library/react';
3
3
  import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
4
- import { useResourceContext, useListContext, useResourceDefinitions, useTranslate } from '@strato-admin/core';
4
+ import { useResourceContext, useListContext, useResourceDefinitions, useTranslate } from '@strato-admin/ra-core';
5
5
  import { TableHeader } from './TableHeader';
6
6
 
7
7
  // Mock ra-core
8
- vi.mock('@strato-admin/core', () => ({
8
+ vi.mock('@strato-admin/ra-core', () => ({
9
9
  useResourceContext: vi.fn(),
10
10
  useListContext: vi.fn(),
11
11
  useTranslate: vi.fn(() => (key: string, options: any) => options?._ || key),
12
12
  useResourceDefinitions: vi.fn(() => ({})),
13
13
  useLocale: vi.fn(() => 'en'),
14
- useCreatePath: vi.fn(() => (params: any) => `/${params.resource}/${params.type}`),
15
14
  useBulkDeleteController: vi.fn(() => ({
16
15
  handleDelete: vi.fn(),
17
16
  isPending: false,
@@ -19,6 +18,11 @@ vi.mock('@strato-admin/core', () => ({
19
18
  })),
20
19
  }));
21
20
 
21
+ // Mock strato-core
22
+ vi.mock('@strato-admin/core', () => ({
23
+ useCreatePath: vi.fn(() => (params: any) => `/${params.resource}/${params.type}`),
24
+ }));
25
+
22
26
  // Mock react-router-dom
23
27
  vi.mock('react-router-dom', () => ({
24
28
  useNavigate: vi.fn(),
@@ -64,20 +68,20 @@ describe('TableHeader', () => {
64
68
  cleanup();
65
69
  });
66
70
 
67
- it('should render title and counter', () => {
71
+ it('should render title', () => {
68
72
  (useResourceContext as any).mockReturnValue('products');
69
- (useListContext as any).mockReturnValue({
70
- total: 10,
71
- isPending: false,
72
- selectedIds: [],
73
- defaultTitle: 'Products'
73
+ (useListContext as any).mockReturnValue({
74
+ total: 10,
75
+ isPending: false,
76
+ selectedIds: [],
77
+ defaultTitle: 'Products',
74
78
  });
75
79
  (useResourceDefinitions as any).mockReturnValue({ products: { hasCreate: false } });
76
80
 
77
- const { getByTestId } = render(<TableHeader />);
81
+ const { getByTestId, queryByTestId } = render(<TableHeader />);
78
82
 
79
83
  expect(getByTestId('header-title').textContent).toBe('Products');
80
- expect(getByTestId('header-counter').textContent).toBe('(10)');
84
+ expect(queryByTestId('header-counter')).toBeNull();
81
85
  });
82
86
 
83
87
  it('should render actions by default', () => {
@@ -1,18 +1,19 @@
1
1
  import React from 'react';
2
2
  import Header, { HeaderProps } from '@cloudscape-design/components/header';
3
3
  import SpaceBetween from '@cloudscape-design/components/space-between';
4
- import { useListContext, useTranslate, useLocale } from '@strato-admin/core';
4
+ import { useListContext, useTranslate, useLocale } from '@strato-admin/ra-core';
5
5
  import { BulkDeleteButton } from '../button/BulkDeleteButton';
6
6
  import { CreateButton } from '../button/CreateButton';
7
7
 
8
- export interface TableHeaderProps extends Omit<HeaderProps, 'children'> {
8
+ export interface TableHeaderProps
9
+ extends Pick<HeaderProps, 'variant' | 'counter' | 'actions' | 'description' | 'info' | 'headingTagOverride'> {
9
10
  title?: React.ReactNode;
10
11
  }
11
12
 
12
- export const TableHeader = ({ title, actions, ...props }: TableHeaderProps) => {
13
+ export const TableHeader = ({ title, actions, description, counter, info, variant = 'h2', headingTagOverride }: TableHeaderProps) => {
13
14
  const translate = useTranslate();
14
15
  const locale = useLocale();
15
- const { total, isPending, defaultTitle } = useListContext();
16
+ const { defaultTitle } = useListContext();
16
17
 
17
18
  const headerTitle = React.useMemo(() => {
18
19
  if (title !== undefined) {
@@ -21,9 +22,6 @@ export const TableHeader = ({ title, actions, ...props }: TableHeaderProps) => {
21
22
  return defaultTitle;
22
23
  }, [title, defaultTitle, translate, locale]);
23
24
 
24
- const counter =
25
- props.counter !== undefined ? props.counter : !isPending && total !== undefined ? `(${total})` : undefined;
26
-
27
25
  const headerActions =
28
26
  actions !== undefined ? (
29
27
  actions
@@ -35,7 +33,7 @@ export const TableHeader = ({ title, actions, ...props }: TableHeaderProps) => {
35
33
  );
36
34
 
37
35
  return (
38
- <Header variant="h2" {...props} actions={headerActions} counter={counter}>
36
+ <Header variant={variant} actions={headerActions} counter={counter} description={description} info={info} headingTagOverride={headingTagOverride}>
39
37
  {headerTitle}
40
38
  </Header>
41
39
  );
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react';
2
- import { useStore } from '@strato-admin/core';
2
+ import { useStore } from '@strato-admin/ra-core';
3
3
  import { Mode, applyMode } from '@cloudscape-design/global-styles';
4
4
 
5
5
  export const ThemeManager = () => {
@@ -1,50 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { vi } from 'vitest';
3
- export const useTranslate = vi.fn(() => (key, options) => options?._ || key);
4
- export const useTranslateLabel = vi.fn(() => (label) => typeof label === 'string' ? label : label?.source || '');
5
- export const useGetResourceLabel = vi.fn(() => (resource) => {
6
- if (resource === 'products')
7
- return 'Products';
8
- if (resource === 'categories')
9
- return 'Categories';
10
- return resource;
11
- });
12
- export const useResourceDefinitions = vi.fn(() => ({}));
13
- export const useResourceDefinition = vi.fn(() => ({
14
- name: 'products',
15
- options: { label: 'Products' },
16
- }));
17
- export const useResourceContext = vi.fn();
18
- export const useRecordContext = vi.fn((record) => record || {});
19
- export const useDefaultTitle = vi.fn(() => '');
20
- export const useInput = vi.fn();
21
- export const useChoicesContext = vi.fn(() => ({ allChoices: [], isPending: false }));
22
- export const useGetRecordRepresentation = vi.fn(() => (record) => record?.name || record?.id || record);
23
- export const useFieldSchema = vi.fn(() => []);
24
- export const useInputSchema = vi.fn(() => []);
25
- export const ResourceSchemaProvider = vi.fn(({ children }) => children);
26
- export const ValidationError = vi.fn(({ error }) => _jsx("span", { children: error }));
27
- export const useStore = vi.fn(() => ['light', vi.fn()]);
28
- export const useSaveContext = vi.fn(() => ({ save: vi.fn(), saving: false }));
29
- export const useNotify = vi.fn(() => vi.fn());
30
- export const useRedirect = vi.fn(() => vi.fn());
31
- export const useRefresh = vi.fn(() => vi.fn());
32
- export const useFieldValue = vi.fn(({ source, record }) => record?.[source]);
33
- export const useCreatePath = vi.fn(() => (params) => `/${params.resource}/${params.id}/${params.type}`);
34
- export const useLocale = vi.fn(() => 'en');
35
- export const useBulkDeleteController = vi.fn(() => ({
36
- handleDelete: vi.fn(),
37
- isPending: false,
38
- isLoading: false,
39
- }));
40
- export const CreateBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "create-base", children: children }));
41
- export const EditBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "edit-base", children: children }));
42
- export const ShowBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "show-base", children: children }));
43
- export const ListBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "list-base", children: children }));
44
- export const useShowContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
45
- export const useEditContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
46
- export const useCreateContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
47
- export const useListContext = vi.fn(() => ({ total: 0, isPending: false, selectedIds: [], defaultTitle: 'Products' }));
48
- export const Form = vi.fn(({ children }) => _jsx("div", { "data-testid": "ra-form", children: children }));
49
- export const FieldTitle = vi.fn(({ label, source }) => _jsx("span", { "data-testid": "field-title", children: label || source }));
50
- export const RecordContextProvider = vi.fn(({ children }) => _jsx(_Fragment, { children: children }));
@@ -1,50 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { vi } from 'vitest';
3
- export const useTranslate = vi.fn(() => (key, options) => options?._ || key);
4
- export const useTranslateLabel = vi.fn(() => (label) => typeof label === 'string' ? label : label?.source || '');
5
- export const useGetResourceLabel = vi.fn(() => (resource) => {
6
- if (resource === 'products')
7
- return 'Products';
8
- if (resource === 'categories')
9
- return 'Categories';
10
- return resource;
11
- });
12
- export const useResourceDefinitions = vi.fn(() => ({}));
13
- export const useResourceDefinition = vi.fn(() => ({
14
- name: 'products',
15
- options: { label: 'Products' },
16
- }));
17
- export const useResourceContext = vi.fn();
18
- export const useRecordContext = vi.fn((record) => record || {});
19
- export const useDefaultTitle = vi.fn(() => '');
20
- export const useInput = vi.fn();
21
- export const useChoicesContext = vi.fn(() => ({ allChoices: [], isPending: false }));
22
- export const useGetRecordRepresentation = vi.fn(() => (record) => record?.name || record?.id || record);
23
- export const useFieldSchema = vi.fn(() => []);
24
- export const useInputSchema = vi.fn(() => []);
25
- export const ResourceSchemaProvider = vi.fn(({ children }) => children);
26
- export const ValidationError = vi.fn(({ error }) => _jsx("span", { children: error }));
27
- export const useStore = vi.fn(() => ['light', vi.fn()]);
28
- export const useSaveContext = vi.fn(() => ({ save: vi.fn(), saving: false }));
29
- export const useNotify = vi.fn(() => vi.fn());
30
- export const useRedirect = vi.fn(() => vi.fn());
31
- export const useRefresh = vi.fn(() => vi.fn());
32
- export const useFieldValue = vi.fn(({ source, record }) => record?.[source]);
33
- export const useCreatePath = vi.fn(() => (params) => `/${params.resource}/${params.id}/${params.type}`);
34
- export const useLocale = vi.fn(() => 'en');
35
- export const useBulkDeleteController = vi.fn(() => ({
36
- handleDelete: vi.fn(),
37
- isPending: false,
38
- isLoading: false,
39
- }));
40
- export const CreateBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "create-base", children: children }));
41
- export const EditBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "edit-base", children: children }));
42
- export const ShowBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "show-base", children: children }));
43
- export const ListBase = vi.fn(({ children }) => _jsx("div", { "data-testid": "list-base", children: children }));
44
- export const useShowContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
45
- export const useEditContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
46
- export const useCreateContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
47
- export const useListContext = vi.fn(() => ({ total: 0, isPending: false, selectedIds: [], defaultTitle: 'Products' }));
48
- export const Form = vi.fn(({ children }) => _jsx("div", { "data-testid": "ra-form", children: children }));
49
- export const FieldTitle = vi.fn(({ label, source }) => _jsx("span", { "data-testid": "field-title", children: label || source }));
50
- export const RecordContextProvider = vi.fn(({ children }) => _jsx(_Fragment, { children: children }));
@@ -1,39 +0,0 @@
1
- import React from 'react';
2
- import { type RaRecord } from '@strato-admin/core';
3
- export interface ShowProps<_RecordType extends RaRecord = RaRecord> {
4
- children?: React.ReactNode;
5
- fieldSchema?: React.ReactNode;
6
- title?: React.ReactNode;
7
- actions?: React.ReactNode;
8
- resource?: string;
9
- id?: any;
10
- queryOptions?: any;
11
- include?: string[];
12
- exclude?: string[];
13
- }
14
- /**
15
- * A Show component that provides record context and a Cloudscape Container.
16
- *
17
- * @example
18
- * <Show>
19
- * <KeyValuePairs>
20
- * <TextField source="name" />
21
- * <NumberField source="price" />
22
- * </KeyValuePairs>
23
- * </Show>
24
- *
25
- * @example
26
- * // Using FieldSchema from context
27
- * <Show include={['name', 'price']} />
28
- *
29
- * @example
30
- * // Passing a custom field schema
31
- * <Show fieldSchema={<FieldSchema>...</FieldSchema>}>
32
- * <KeyValuePairs />
33
- * </Show>
34
- */
35
- export declare const Show: {
36
- <RecordType extends RaRecord = RaRecord<import("@strato-admin/ra-core").Identifier>>({ children, fieldSchema, title, actions, include, exclude, ...props }: ShowProps<RecordType>): import("react/jsx-runtime").JSX.Element;
37
- Header: ({ title, actions, ...props }: import("./ShowHeader").ShowHeaderProps) => import("react/jsx-runtime").JSX.Element;
38
- };
39
- export default Show;
@@ -1,40 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { ShowBase, useShowContext, ResourceSchemaProvider } from '@strato-admin/core';
3
- import Container from '@cloudscape-design/components/container';
4
- import SpaceBetween from '@cloudscape-design/components/space-between';
5
- import { ShowHeader } from './ShowHeader';
6
- import KeyValuePairs from './KeyValuePairs';
7
- const ShowUI = ({ children, resource, fieldSchema, title, actions, include, exclude, }) => {
8
- const { record, isLoading } = useShowContext();
9
- if (isLoading || !record) {
10
- return null;
11
- }
12
- const finalChildren = children || _jsx(KeyValuePairs, { include: include, exclude: exclude });
13
- return (_jsx(ResourceSchemaProvider, { resource: resource, fieldSchema: fieldSchema, children: _jsx(Container, { header: _jsx(ShowHeader, { title: title, actions: actions }), children: _jsx(SpaceBetween, { direction: "vertical", size: "l", children: finalChildren }) }) }));
14
- };
15
- /**
16
- * A Show component that provides record context and a Cloudscape Container.
17
- *
18
- * @example
19
- * <Show>
20
- * <KeyValuePairs>
21
- * <TextField source="name" />
22
- * <NumberField source="price" />
23
- * </KeyValuePairs>
24
- * </Show>
25
- *
26
- * @example
27
- * // Using FieldSchema from context
28
- * <Show include={['name', 'price']} />
29
- *
30
- * @example
31
- * // Passing a custom field schema
32
- * <Show fieldSchema={<FieldSchema>...</FieldSchema>}>
33
- * <KeyValuePairs />
34
- * </Show>
35
- */
36
- export const Show = ({ children, fieldSchema, title, actions, include, exclude, ...props }) => {
37
- return (_jsx(ShowBase, { ...props, children: _jsx(ShowUI, { resource: props.resource, title: title, actions: actions, include: include, exclude: exclude, fieldSchema: fieldSchema, children: children }) }));
38
- };
39
- Show.Header = ShowHeader;
40
- export default Show;
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import { HeaderProps } from '@cloudscape-design/components/header';
3
- export interface ShowHeaderProps extends Omit<HeaderProps, 'children'> {
4
- title?: React.ReactNode;
5
- }
6
- export declare const ShowHeader: ({ title, actions, ...props }: ShowHeaderProps) => import("react/jsx-runtime").JSX.Element;
7
- export default ShowHeader;
@@ -1,25 +0,0 @@
1
- import React from 'react';
2
- import { InputProps } from './types';
3
- export interface AttributeEditorItemProps {
4
- source: string;
5
- label?: string | false;
6
- field?: React.ComponentType<any>;
7
- validate?: any;
8
- defaultValue?: any;
9
- children?: React.ReactNode;
10
- }
11
- export declare const Item: (_props: AttributeEditorItemProps) => null;
12
- export interface AttributeEditorProps extends Omit<InputProps, 'source'> {
13
- source?: string;
14
- children: React.ReactNode;
15
- addButtonText?: string;
16
- removeButtonText?: string;
17
- empty?: React.ReactNode;
18
- disableAddButton?: boolean;
19
- hideAddButton?: boolean;
20
- }
21
- export declare const AttributeEditor: {
22
- (props: AttributeEditorProps): import("react/jsx-runtime").JSX.Element;
23
- Item: (_props: AttributeEditorItemProps) => null;
24
- };
25
- export default AttributeEditor;