@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
package/src/edit/Edit.tsx CHANGED
@@ -1,54 +1,90 @@
1
1
  import React from 'react';
2
- import { EditBase, useEditContext, type RaRecord, ResourceSchemaProvider } from '@strato-admin/core';
2
+ import { EditBase, useEditContext, type RaRecord, type EditBaseProps, useNotify, useRedirect } from '@strato-admin/ra-core';
3
+ import {
4
+ ResourceSchemaProvider,
5
+ useResourceSchema,
6
+ useConstructedPageTitle,
7
+ useSettingValue,
8
+ } from '@strato-admin/core';
3
9
  import Container from '@cloudscape-design/components/container';
4
10
  import { EditHeader } from './EditHeader';
5
11
  import Form from '../form/Form';
6
12
 
7
- export interface EditProps<_RecordType extends RaRecord = RaRecord> {
13
+ export interface EditProps<RecordType extends RaRecord = RaRecord, ErrorType = Error> extends EditBaseProps<
14
+ RecordType,
15
+ ErrorType
16
+ > {
8
17
  children?: React.ReactNode;
9
- inputSchema?: React.ReactNode;
10
- title?: React.ReactNode;
18
+ title?: React.ReactNode | ((record: RecordType) => React.ReactNode);
19
+ description?: React.ReactNode | ((record: RecordType) => React.ReactNode);
11
20
  actions?: React.ReactNode;
12
- resource?: string;
13
- id?: any;
14
- mutationMode?: 'pessimistic' | 'optimistic' | 'undoable';
15
- mutationOptions?: any;
16
- queryOptions?: any;
17
- redirect?: any;
18
- transform?: any;
19
21
  include?: string[];
20
22
  exclude?: string[];
23
+ saveButtonLabel?: string;
24
+ redirect?: false | 'list' | 'detail';
21
25
  }
22
26
 
23
27
  const EditUI = ({
24
28
  children,
25
- resource,
26
- inputSchema,
27
29
  title,
28
30
  actions,
31
+ description,
29
32
  include,
30
33
  exclude,
34
+ saveButtonLabel,
31
35
  }: {
32
36
  children?: React.ReactNode;
33
- resource?: string;
34
- inputSchema?: React.ReactNode;
35
- title?: React.ReactNode;
37
+ title?: React.ReactNode | ((record: any) => React.ReactNode);
36
38
  actions?: React.ReactNode;
39
+ description?: React.ReactNode | ((record: any) => React.ReactNode);
37
40
  include?: string[];
38
41
  exclude?: string[];
42
+ saveButtonLabel?: string;
39
43
  }) => {
40
44
  const { record, isLoading } = useEditContext();
45
+ const { label, editTitle, editDescription } = useResourceSchema();
46
+ const constructedTitle = useConstructedPageTitle('edit', label);
47
+
48
+ const finalTitle = React.useMemo(() => {
49
+ if (isLoading || !record) {
50
+ return '';
51
+ }
52
+ const resolvedTitle = title ?? editTitle ?? constructedTitle;
53
+ if (typeof resolvedTitle === 'function') {
54
+ return resolvedTitle(record);
55
+ }
56
+ return resolvedTitle;
57
+ }, [isLoading, record, title, editTitle, constructedTitle]);
58
+
59
+ const finalDescription = React.useMemo(() => {
60
+ if (isLoading || !record) {
61
+ return '';
62
+ }
63
+ const resolvedDescription = description ?? editDescription;
64
+ if (typeof resolvedDescription === 'function') {
65
+ return resolvedDescription(record);
66
+ }
67
+ return resolvedDescription;
68
+ }, [isLoading, record, description, editDescription]);
41
69
 
42
70
  if (isLoading || !record) {
43
71
  return null;
44
72
  }
45
-
46
- const finalChildren = children || <Form include={include} exclude={exclude} />;
73
+ const finalSaveButtonLabel = saveButtonLabel // || <Message>Save</Message>
74
+ const finalChildren = children || <Form include={include} exclude={exclude} saveButtonLabel={finalSaveButtonLabel} />;
47
75
 
48
76
  return (
49
- <ResourceSchemaProvider resource={resource} inputSchema={inputSchema}>
50
- <Container header={<EditHeader title={title} actions={actions} />}>{finalChildren}</Container>
51
- </ResourceSchemaProvider>
77
+ <Container
78
+ header={
79
+ <EditHeader
80
+ title={finalTitle}
81
+ description={finalDescription}
82
+ actions={actions}
83
+ />
84
+ }
85
+ >
86
+ {finalChildren}
87
+ </Container>
52
88
  );
53
89
  };
54
90
 
@@ -61,38 +97,54 @@ const EditUI = ({
61
97
  * <TextInput source="name" />
62
98
  * </Form>
63
99
  * </Edit>
64
- *
100
+ *
65
101
  * @example
66
102
  * // Using InputSchema from context
67
103
  * <Edit include={['name', 'price']} />
68
- *
69
- * @example
70
- * // Passing a custom input schema
71
- * <Edit inputSchema={<InputSchema>...</InputSchema>}>
72
- * <Form />
73
- * </Edit>
74
104
  */
75
105
  export const Edit = <RecordType extends RaRecord = any>({
76
106
  children,
77
- inputSchema,
78
107
  title,
79
108
  actions,
109
+ description,
80
110
  include,
81
111
  exclude,
112
+ redirect,
113
+ saveButtonLabel,
82
114
  ...props
83
115
  }: EditProps<RecordType>) => {
116
+ const { queryOptions, editTitle } = useResourceSchema(props.resource);
117
+ const resolve = useSettingValue();
118
+ const resolvedRedirect = resolve(redirect, 'editRedirect');
119
+ const editSuccessMessage = resolve(undefined, 'editSuccessMessage');
120
+ const notify = useNotify();
121
+ const redirectFn = useRedirect();
122
+
123
+ const mutationOptions = React.useMemo(() => {
124
+ if (!editSuccessMessage || props.mutationOptions?.onSuccess) return props.mutationOptions;
125
+ return {
126
+ ...props.mutationOptions,
127
+ onSuccess: (data: RecordType) => {
128
+ notify(editSuccessMessage, { type: 'info' });
129
+ redirectFn(resolvedRedirect ?? 'detail', props.resource ?? '', data.id, data);
130
+ },
131
+ };
132
+ }, [editSuccessMessage, props.mutationOptions, props.resource, notify, redirectFn, resolvedRedirect]);
133
+
84
134
  return (
85
- <EditBase {...props}>
86
- <EditUI
87
- resource={props.resource}
88
- title={title}
89
- actions={actions}
90
- include={include}
91
- exclude={exclude}
92
- inputSchema={inputSchema}
93
- >
94
- {children}
95
- </EditUI>
135
+ <EditBase redirect={resolvedRedirect} queryOptions={queryOptions} {...props} mutationOptions={mutationOptions}>
136
+ <ResourceSchemaProvider resource={props.resource}>
137
+ <EditUI
138
+ title={title ?? editTitle ?? undefined}
139
+ actions={actions}
140
+ description={description}
141
+ include={include}
142
+ exclude={exclude}
143
+ saveButtonLabel={saveButtonLabel}
144
+ >
145
+ {children}
146
+ </EditUI>
147
+ </ResourceSchemaProvider>
96
148
  </EditBase>
97
149
  );
98
150
  };
@@ -1,13 +1,15 @@
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 { useResourceContext, useEditContext, useTranslate, useResourceDefinitions } from '@strato-admin/core';
4
+ import { useEditContext, useTranslate } from '@strato-admin/ra-core';
5
+ import { DeleteButton } from '../button/DeleteButton';
5
6
 
6
- export interface EditHeaderProps extends Omit<HeaderProps, 'children'> {
7
+ export interface EditHeaderProps
8
+ extends Pick<HeaderProps, 'variant' | 'counter' | 'actions' | 'description' | 'info' | 'headingTagOverride'> {
7
9
  title?: React.ReactNode;
8
10
  }
9
11
 
10
- export const EditHeader = ({ title, actions, ...props }: EditHeaderProps) => {
12
+ export const EditHeader = ({ title, actions, description, counter, info, variant = 'h2', headingTagOverride }: EditHeaderProps) => {
11
13
  const translate = useTranslate();
12
14
  const { defaultTitle } = useEditContext();
13
15
 
@@ -20,12 +22,12 @@ export const EditHeader = ({ title, actions, ...props }: EditHeaderProps) => {
20
22
 
21
23
  const headerActions = actions || (
22
24
  <SpaceBetween direction="horizontal" size="xs">
23
- {/* Add default edit actions here if needed */}
25
+ <DeleteButton />
24
26
  </SpaceBetween>
25
27
  );
26
28
 
27
29
  return (
28
- <Header variant="h2" {...props} actions={headerActions}>
30
+ <Header variant={variant} actions={headerActions} description={description} counter={counter} info={info} headingTagOverride={headingTagOverride}>
29
31
  {headerTitle}
30
32
  </Header>
31
33
  );
@@ -1,11 +1,19 @@
1
- import { useRecordContext, useList, ListContextProvider, ResourceContextProvider, type RaRecord } from '@strato-admin/core';
1
+ import React, { ReactNode } from 'react';
2
+ import {
3
+ useRecordContext,
4
+ useList,
5
+ ListContextProvider,
6
+ ResourceContextProvider,
7
+ type RaRecord,
8
+ } from '@strato-admin/ra-core';
2
9
  import { type FieldProps } from './types';
10
+ import Table from '../list/Table';
3
11
 
4
12
  export interface ArrayFieldProps<RecordType extends RaRecord = RaRecord> extends FieldProps<RecordType> {
5
13
  /**
6
- * Thecomponents to render for each item in the array.
14
+ * The components to render for each item in the array.
7
15
  */
8
- children: React.ReactNode;
16
+ children?: React.ReactNode;
9
17
  /**
10
18
  * Number of items per page if pagination is used within the field.
11
19
  */
@@ -17,18 +25,30 @@ export interface ArrayFieldProps<RecordType extends RaRecord = RaRecord> extends
17
25
  * and ResourceContextProvider initialized with the array data and resource name.
18
26
  *
19
27
  * This allows using components that expect a ListContext or ResourceContext
20
- * (like DataTable) to display nested arrays within a record.
28
+ * (like Table) to display nested arrays within a record.
21
29
  *
22
30
  * @example
23
- * <ArrayField source="products" resource="products">
24
- * <DataTable variant="embedded">
25
- * <DataTable.Col source="title" label="Title" />
26
- * <DataTable.NumberCol source="price" label="Price" />
27
- * </DataTable>
31
+ * <ArrayField source="items" label="Items">
32
+ * <ReferenceField source="product_id" reference="products" label="Product" />
33
+ * <NumberField source="quantity" label="Quantity" />
34
+ * <NumberField source="unit_price" label="Price" options={{ style: 'currency', currency: 'USD' }} />
28
35
  * </ArrayField>
36
+ *
37
+ * NOTE ON FILTERING:
38
+ * Client-side filtering via useList (used by the implicit Table) only searches
39
+ * properties directly present in the array items.
40
+ *
41
+ * If you have a <ReferenceField> child, filtering by the referenced record's
42
+ * name (e.g. Product Name) will NOT work by default because that data is fetched
43
+ * asynchronously and is not in the local array.
44
+ *
45
+ * To enable "Deep Filtering", you can:
46
+ * 1. Denormalize your data at the API level to include the labels in the nested items.
47
+ * 2. Augment the 'data' array before passing it to useList by pre-fetching labels
48
+ * for all referenced IDs and injecting them into the items.
29
49
  */
30
50
  export const ArrayField = <RecordType extends RaRecord = any>(props: ArrayFieldProps<RecordType>) => {
31
- const { source, resource, children, perPage = 10 } = props;
51
+ const { source, resource, children, perPage = 10, label = props.source } = props;
32
52
 
33
53
  const record = useRecordContext<RecordType>();
34
54
  const data = (source && record?.[source]) || [];
@@ -43,9 +63,35 @@ export const ArrayField = <RecordType extends RaRecord = any>(props: ArrayFieldP
43
63
 
44
64
  return (
45
65
  <ResourceContextProvider value={targetResource}>
46
- <ListContextProvider value={listContext}>{children as any}</ListContextProvider>
66
+ <ListContextProvider value={listContext as any}>
67
+ <ArrayFieldUI title={label}>{children}</ArrayFieldUI>
68
+ </ListContextProvider>
47
69
  </ResourceContextProvider>
48
70
  );
49
71
  };
50
72
 
73
+ const ArrayFieldUI = ({ children, title }: { children?: ReactNode; title?: ReactNode }) => {
74
+ if (!children) return null;
75
+
76
+ const childrenArray = React.Children.toArray(children);
77
+
78
+ // Check if children already contain a Table
79
+ const hasTable = childrenArray.some(
80
+ (child) => React.isValidElement(child) && (child.type === Table || (child.type as any).displayName === 'Table'),
81
+ );
82
+
83
+ if (hasTable) {
84
+ return <>{children}</>;
85
+ }
86
+
87
+ // If children are provided but no Table, wrap them in a Table.
88
+ // This supports the shorthand <ArrayField><TextField source="foo" /></ArrayField>
89
+ return (
90
+ <Table variant="embedded" title={title} selectionType={undefined} actions={null}>
91
+ {children}
92
+ </Table>
93
+ );
94
+ };
95
+
51
96
  export default ArrayField;
97
+ ArrayField.isCollectionField = true;
@@ -1,10 +1,9 @@
1
1
  import Badge, { type BadgeProps } from '@cloudscape-design/components/badge';
2
- import { type RaRecord, useFieldValue, useRecordContext } from '@strato-admin/core';
2
+ import { type RaRecord, useFieldValue, useRecordContext } from '@strato-admin/ra-core';
3
3
  import RecordLink from '../RecordLink';
4
4
  import { type FieldProps } from './types';
5
5
 
6
- export interface BadgeFieldProps<RecordType extends RaRecord = RaRecord>
7
- extends FieldProps<RecordType> {
6
+ export interface BadgeFieldProps<RecordType extends RaRecord = RaRecord> extends FieldProps<RecordType> {
8
7
  /**
9
8
  * The color of the badge.
10
9
  * @default "grey"
@@ -1,10 +1,9 @@
1
-
2
1
  import { render } from '@testing-library/react';
3
2
  import { vi, describe, it, expect } from 'vitest';
4
- import { useFieldValue, useRecordContext } from '@strato-admin/core';
3
+ import { useFieldValue, useRecordContext } from '@strato-admin/ra-core';
5
4
  import BooleanField from './BooleanField';
6
5
 
7
- // Mock ra-core
6
+ vi.mock('@strato-admin/ra-core', () => import('../__mocks__/ra-core'));
8
7
  vi.mock('@strato-admin/core', () => import('../__mocks__/strato-core'));
9
8
 
10
9
  // Mock Cloudscape components
@@ -1,5 +1,5 @@
1
1
  import StatusIndicator from '@cloudscape-design/components/status-indicator';
2
- import { RaRecord, useFieldValue, useRecordContext, useTranslate } from '@strato-admin/core';
2
+ import { RaRecord, useFieldValue, useRecordContext, useTranslate } from '@strato-admin/ra-core';
3
3
  import RecordLink from '../RecordLink';
4
4
  import { type FieldProps } from './types';
5
5
 
@@ -28,11 +28,11 @@ const BooleanField = <RecordType extends RaRecord = RaRecord>(props: BooleanFiel
28
28
 
29
29
  const content = isTrue ? (
30
30
  <StatusIndicator type="success" colorOverride="green">
31
- {showLabel ? (trueLabel ?? translate('ra.boolean.true', { _: 'Yes' })) : null}
31
+ {showLabel ? (trueLabel ?? translate('strato.boolean.true', { _: 'Yes' })) : null}
32
32
  </StatusIndicator>
33
33
  ) : (
34
34
  <StatusIndicator type="not-started">
35
- {showLabel ? (falseLabel ?? translate('ra.boolean.false', { _: 'No' })) : null}
35
+ {showLabel ? (falseLabel ?? translate('strato.boolean.false', { _: 'No' })) : null}
36
36
  </StatusIndicator>
37
37
  );
38
38
 
@@ -1,4 +1,4 @@
1
- import { type RaRecord, useFieldValue, useRecordContext, useLocale } from '@strato-admin/core';
1
+ import { type RaRecord, useFieldValue, useRecordContext, useLocale } from '@strato-admin/ra-core';
2
2
  import RecordLink from '../RecordLink';
3
3
  import { type FieldProps } from './types';
4
4
 
@@ -1,4 +1,4 @@
1
- import { type RaRecord, useFieldValue, useRecordContext, useLocale } from '@strato-admin/core';
1
+ import { type RaRecord, useFieldValue, useRecordContext, useLocale } from '@strato-admin/ra-core';
2
2
  import RecordLink from '../RecordLink';
3
3
  import { type FieldProps } from './types';
4
4
 
@@ -1,14 +1,10 @@
1
1
  import { render, cleanup } from '@testing-library/react';
2
2
  import { vi, describe, it, expect, afterEach } from 'vitest';
3
- import {
4
- useFieldValue,
5
- useRecordContext,
6
- useResourceDefinition,
7
- } from '@strato-admin/core';
3
+ import { useFieldValue, useRecordContext, useResourceDefinition } from '@strato-admin/ra-core';
8
4
  import IdField from './IdField';
9
5
 
10
- // Mock strato-core
11
- vi.mock('@strato-admin/core', () => ({
6
+ // Mock ra-core
7
+ vi.mock('@strato-admin/ra-core', () => ({
12
8
  useRecordContext: vi.fn(),
13
9
  useFieldValue: vi.fn(),
14
10
  useResourceDefinition: vi.fn(),
@@ -32,24 +28,20 @@ describe('IdField', () => {
32
28
  it('should render the ID and link to show by default if hasShow is true', () => {
33
29
  const record = { id: '123' };
34
30
  (useRecordContext as any).mockReturnValue(record);
35
- (useFieldValue as any).mockImplementation(
36
- ({ source }: any) => (record as any)[source]
37
- );
31
+ (useFieldValue as any).mockImplementation(({ source }: any) => (record as any)[source]);
38
32
  (useResourceDefinition as any).mockReturnValue({ hasShow: true });
39
33
 
40
34
  const { getByTestId, getByText } = render(<IdField />);
41
35
 
42
36
  expect(getByText('123')).toBeDefined();
43
37
  const link = getByTestId('record-link');
44
- expect(link.getAttribute('data-link')).toBe('show');
38
+ expect(link.getAttribute('data-link')).toBe('detail');
45
39
  });
46
40
 
47
41
  it('should not link by default if hasShow is false', () => {
48
42
  const record = { id: '123' };
49
43
  (useRecordContext as any).mockReturnValue(record);
50
- (useFieldValue as any).mockImplementation(
51
- ({ source }: any) => (record as any)[source]
52
- );
44
+ (useFieldValue as any).mockImplementation(({ source }: any) => (record as any)[source]);
53
45
  (useResourceDefinition as any).mockReturnValue({ hasShow: false });
54
46
 
55
47
  const { getByTestId, getByText } = render(<IdField />);
@@ -62,9 +54,7 @@ describe('IdField', () => {
62
54
  it('should use custom source if provided', () => {
63
55
  const record = { identifier: 'abc' };
64
56
  (useRecordContext as any).mockReturnValue(record);
65
- (useFieldValue as any).mockImplementation(
66
- ({ source }: any) => (record as any)[source]
67
- );
57
+ (useFieldValue as any).mockImplementation(({ source }: any) => (record as any)[source]);
68
58
  (useResourceDefinition as any).mockReturnValue({ hasShow: true });
69
59
 
70
60
  const { getByText } = render(<IdField source="identifier" />);
@@ -75,9 +65,7 @@ describe('IdField', () => {
75
65
  it('should allow overriding link', () => {
76
66
  const record = { id: '123' };
77
67
  (useRecordContext as any).mockReturnValue(record);
78
- (useFieldValue as any).mockImplementation(
79
- ({ source }: any) => (record as any)[source]
80
- );
68
+ (useFieldValue as any).mockImplementation(({ source }: any) => (record as any)[source]);
81
69
  (useResourceDefinition as any).mockReturnValue({ hasShow: true });
82
70
 
83
71
  const { getByTestId } = render(<IdField link="edit" />);
@@ -1,8 +1,7 @@
1
- import { type RaRecord, useResourceDefinition } from '@strato-admin/core';
1
+ import { type RaRecord, useResourceDefinition } from '@strato-admin/ra-core';
2
2
  import TextField, { type TextFieldProps } from './TextField';
3
3
 
4
- export type IdFieldProps<RecordType extends RaRecord = RaRecord> =
5
- TextFieldProps<RecordType>;
4
+ export type IdFieldProps<RecordType extends RaRecord = RaRecord> = TextFieldProps<RecordType>;
6
5
 
7
6
  /**
8
7
  * A field that displays the record's ID.
@@ -16,25 +15,11 @@ export type IdFieldProps<RecordType extends RaRecord = RaRecord> =
16
15
  * <IdField />
17
16
  * <IdField source="identifier" />
18
17
  */
19
- const IdField = <RecordType extends RaRecord = RaRecord>(
20
- props: IdFieldProps<RecordType>
21
- ) => {
18
+ const IdField = <RecordType extends RaRecord = RaRecord>(props: IdFieldProps<RecordType>) => {
22
19
  const { hasShow } = useResourceDefinition(props);
23
- const {
24
- source = 'id',
25
- link = hasShow ? 'show' : undefined,
26
- input = false,
27
- ...rest
28
- } = props;
20
+ const { source = 'id', link = hasShow ? 'detail' : undefined, input = false, ...rest } = props;
29
21
 
30
- return (
31
- <TextField<RecordType>
32
- source={source}
33
- link={link}
34
- input={input}
35
- {...rest}
36
- />
37
- );
22
+ return <TextField<RecordType> source={source as any} link={link} input={input} {...rest} />;
38
23
  };
39
24
 
40
25
  export default IdField;
@@ -1,4 +1,4 @@
1
- import { type RaRecord, useFieldValue, useRecordContext, useLocale } from '@strato-admin/core';
1
+ import { type RaRecord, useFieldValue, useRecordContext, useLocale } from '@strato-admin/ra-core';
2
2
  import RecordLink from '../RecordLink';
3
3
  import { type FieldProps } from './types';
4
4
 
@@ -1,20 +1,29 @@
1
-
2
1
  import { render, screen } from '@testing-library/react';
3
2
  import { vi, describe, it, expect, beforeEach } from 'vitest';
4
3
  import ReferenceField from './ReferenceField';
5
- import { useRecordContext, useGetRecordRepresentation } from '@strato-admin/core';
4
+ import { useRecordContext, useGetRecordRepresentation } from '@strato-admin/ra-core';
6
5
 
7
6
  // Mock ra-core
8
- vi.mock('@strato-admin/core', () => ({
7
+ vi.mock('@strato-admin/ra-core', () => ({
9
8
  ReferenceFieldBase: vi.fn(({ children }: any) => <div data-testid="ra-reference-field-base">{children}</div>),
10
9
  useRecordContext: vi.fn(),
11
10
  useGetRecordRepresentation: vi.fn(),
12
11
  useResourceDefinition: vi.fn(),
13
12
  useResourceContext: vi.fn(() => 'categories'),
14
- useCreatePath: vi.fn(() => (params: any) => `/${params.resource}/${params.id}/${params.type}`),
15
13
  ResourceContextProvider: ({ children }: any) => <div data-testid="resource-context-provider">{children}</div>,
16
14
  }));
17
15
 
16
+ // Mock strato-core
17
+ vi.mock('@strato-admin/core', () => ({
18
+ useCreatePath: vi.fn(() => (params: any) => {
19
+ if (params.type === 'detail') return `/${params.resource}/${params.id}`;
20
+ if (params.type === 'edit') return `/${params.resource}/${params.id}/edit`;
21
+ if (params.type === 'create') return `/${params.resource}/create`;
22
+ return `/${params.resource}`;
23
+ }),
24
+ useResourceSchema: vi.fn(() => ({ queryOptions: undefined })),
25
+ }));
26
+
18
27
  // Mock react-router-dom
19
28
  vi.mock('react-router-dom', () => ({
20
29
  useNavigate: vi.fn(),
@@ -78,11 +87,11 @@ describe('ReferenceField', () => {
78
87
  (useRecordContext as any).mockReturnValue(record);
79
88
  (useGetRecordRepresentation as any).mockReturnValue(() => 'Category 1');
80
89
 
81
- render(<ReferenceField source="categoryId" reference="categories" link="show" />);
90
+ render(<ReferenceField source="categoryId" reference="categories" link="detail" />);
82
91
 
83
92
  const link = screen.getByTestId('cloudscape-link');
84
93
  expect(link).toBeDefined();
85
- expect(link.getAttribute('href')).toBe('/categories/1/show');
94
+ expect(link.getAttribute('href')).toBe('/categories/1');
86
95
  expect(link.textContent).toBe('Category 1');
87
96
  });
88
97
  });
@@ -1,10 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
- import {
3
- ReferenceFieldBase,
4
- type RaRecord,
5
- useRecordContext,
6
- useGetRecordRepresentation,
7
- } from '@strato-admin/core';
2
+ import { ReferenceFieldBase, type RaRecord, useRecordContext, useGetRecordRepresentation } from '@strato-admin/ra-core';
3
+ import { useResourceSchema } from '@strato-admin/core';
8
4
  import RecordLink from '../RecordLink';
9
5
  import { type FieldProps } from './types';
10
6
 
@@ -22,13 +18,20 @@ export type ReferenceFieldProps<RecordType extends RaRecord = RaRecord> = FieldP
22
18
 
23
19
  const ReferenceField = <RecordType extends RaRecord = RaRecord>(props: ReferenceFieldProps<RecordType>) => {
24
20
  const { source, reference, children, emptyText, record, link } = props;
21
+ const { queryOptions } = useResourceSchema(reference);
25
22
 
26
23
  if (!source) {
27
24
  return null; // Or some fallback
28
25
  }
29
26
 
30
27
  return (
31
- <ReferenceFieldBase source={source} reference={reference} record={record} empty={<>{emptyText ?? null}</>}>
28
+ <ReferenceFieldBase
29
+ source={source}
30
+ reference={reference}
31
+ record={record}
32
+ empty={<>{emptyText ?? null}</>}
33
+ queryOptions={queryOptions}
34
+ >
32
35
  <ReferenceFieldValue emptyText={emptyText} link={link} reference={reference}>
33
36
  {children}
34
37
  </ReferenceFieldValue>