@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/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@strato-admin/cloudscape",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Strato Admin Cloudscape implementation - UI component library and theme for React Admin",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
- "default": "dist/index.js"
10
+ "development": "./src/index.ts",
11
+ "default": "./dist/index.js"
11
12
  }
12
13
  },
13
14
  "files": [
@@ -35,15 +36,17 @@
35
36
  "directory": "packages/strato-cloudscape"
36
37
  },
37
38
  "dependencies": {
39
+ "@cloudscape-design/code-view": "^3.0.108",
38
40
  "@cloudscape-design/collection-hooks": "^1.0.85",
39
41
  "@cloudscape-design/components": "^3.0.1217",
40
42
  "@cloudscape-design/global-styles": "^1.0.0",
41
43
  "inflection": "^3.0.2",
42
44
  "react-hook-form": "^7.71.2",
43
45
  "react-router-dom": "^6.22.3",
44
- "@strato-admin/language-en": "0.1.1",
45
- "@strato-admin/core": "0.1.1",
46
- "@strato-admin/i18n": "0.1.1"
46
+ "@strato-admin/core": "0.3.0",
47
+ "@strato-admin/i18n": "0.3.0",
48
+ "@strato-admin/ra-core": "0.3.0",
49
+ "@strato-admin/language-en": "0.3.0"
47
50
  },
48
51
  "devDependencies": {
49
52
  "@chromatic-com/storybook": "^5.0.1",
package/src/Admin.tsx CHANGED
@@ -3,32 +3,38 @@ import {
3
3
  CoreAdmin,
4
4
  type CoreAdminProps,
5
5
  type AdminChildren,
6
- SchemaRegistryProvider,
7
6
  localStorageStore,
8
7
  useLocaleState,
8
+ } from '@strato-admin/ra-core';
9
+ import {
10
+ SchemaRegistryProvider,
9
11
  registerDefaultResourceComponents,
10
12
  registerFieldInputMapping,
13
+ SettingsContext,
14
+ type AdminSettings,
11
15
  } from '@strato-admin/core';
12
16
  import { icuI18nProvider } from '@strato-admin/i18n';
13
17
  import englishMessages from '@strato-admin/language-en';
14
18
  import AppLayout from './layout/AppLayout';
19
+ import Ready from './layout/Ready';
15
20
  import { List } from './list';
16
21
  import { Create } from './create';
17
22
  import { Edit } from './edit';
18
- import { Show } from './detail';
23
+ import { Detail } from './detail';
24
+ import { FRAMEWORK_DEFAULTS } from './defaults';
19
25
 
20
- import { TextField, NumberField, CurrencyField, ReferenceField } from './field';
21
- import { TextInput, NumberInput, ReferenceInput } from './input';
26
+ import { TextField, NumberField, CurrencyField, ReferenceField, ArrayField, BooleanField } from './field';
27
+ import { TextInput, NumberInput, ReferenceInput, ArrayInput, BooleanInput } from './input';
22
28
 
23
29
  import { I18nProvider, importMessages, I18nProviderProps } from '@cloudscape-design/components/i18n';
24
30
 
25
- // Register Cloudscape themed components as defaults for ResourceSchema.
31
+ // Register Cloudscape themed routing components as defaults for ResourceSchema.
26
32
  // This is done once, here, to avoid circular dependencies at the module level.
27
33
  registerDefaultResourceComponents({
28
34
  list: List,
29
35
  create: Create,
30
36
  edit: Edit,
31
- show: Show,
37
+ show: Detail,
32
38
  });
33
39
 
34
40
  registerFieldInputMapping(
@@ -36,13 +42,17 @@ registerFieldInputMapping(
36
42
  [TextField, TextInput],
37
43
  [NumberField, NumberInput],
38
44
  [CurrencyField, NumberInput],
45
+ [BooleanField, BooleanInput],
39
46
  [ReferenceField, ReferenceInput],
40
- ])
47
+ [ArrayField, ArrayInput],
48
+ ]),
41
49
  );
42
50
 
43
51
  export interface AdminProps extends CoreAdminProps {
44
52
  children?: AdminChildren;
45
53
  title?: string;
54
+ /** Declarative Admin-level defaults. Pass a <Settings> element. */
55
+ settings?: React.ReactElement<AdminSettings>;
46
56
  }
47
57
 
48
58
  const defaultI18nProvider = icuI18nProvider(() => englishMessages as any);
@@ -101,22 +111,29 @@ export const Admin = ({
101
111
  children,
102
112
  title,
103
113
  layout: Layout = AppLayout,
114
+ ready = Ready,
104
115
  i18nProvider = defaultI18nProvider,
105
116
  store = defaultStore,
117
+ settings,
106
118
  ...props
107
119
  }: AdminProps) => {
120
+ const mergedSettings: AdminSettings = { ...FRAMEWORK_DEFAULTS, ...settings?.props };
121
+
108
122
  return (
109
- <SchemaRegistryProvider>
110
- <CoreAdmin
111
- {...props}
112
- layout={(layoutProps: any) => <CloudscapeLayout {...layoutProps} layout={Layout} />}
113
- title={title}
114
- i18nProvider={i18nProvider}
115
- store={store}
116
- >
117
- {children}
118
- </CoreAdmin>
119
- </SchemaRegistryProvider>
123
+ <SettingsContext.Provider value={mergedSettings}>
124
+ <SchemaRegistryProvider>
125
+ <CoreAdmin
126
+ {...props}
127
+ layout={(layoutProps: any) => <CloudscapeLayout {...layoutProps} layout={Layout} />}
128
+ title={title}
129
+ ready={ready}
130
+ i18nProvider={i18nProvider}
131
+ store={store}
132
+ >
133
+ {children}
134
+ </CoreAdmin>
135
+ </SchemaRegistryProvider>
136
+ </SettingsContext.Provider>
120
137
  );
121
138
  };
122
139
 
@@ -19,7 +19,7 @@ export const Default: Story = {
19
19
  },
20
20
  };
21
21
 
22
- export const Show: Story = {
22
+ export const Detail: Story = {
23
23
  args: {
24
24
  link: 'show',
25
25
  children: 'Show details',
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import Link from '@cloudscape-design/components/link';
3
- import { useCreatePath, useResourceContext, useRecordContext } from '@strato-admin/core';
3
+ import { useResourceContext, useRecordContext } from '@strato-admin/ra-core';
4
+ import { useCreatePath } from '@strato-admin/core';
4
5
  import { useNavigate } from 'react-router-dom';
5
6
 
6
7
  export type RecordLinkType = string | boolean | ((record: any, reference?: string) => string);
@@ -27,9 +28,9 @@ const RecordLinkImpl = ({ link, resource, children }: RecordLinkProps) => {
27
28
  if (typeof link === 'function') {
28
29
  href = link(record, finalResource);
29
30
  } else if (link === true) {
30
- href = createPath({ resource: finalResource, id: record.id, type: 'edit' });
31
- } else if (link === 'edit' || link === 'show') {
32
- href = createPath({ resource: finalResource, id: record.id, type: link });
31
+ href = createPath({ resource: finalResource, id: record.id, type: 'detail' });
32
+ } else if (link === 'edit' || link === 'detail' || link === 'show') {
33
+ href = createPath({ resource: finalResource, id: record.id, type: link === 'show' ? 'detail' : link });
33
34
  } else if (typeof link === 'string') {
34
35
  href = link;
35
36
  }
@@ -0,0 +1,16 @@
1
+ import type { AdminSettings } from '@strato-admin/core';
2
+
3
+ /**
4
+ * Declarative configuration for Admin-level defaults.
5
+ * Pass as the `settings` prop on <Admin>.
6
+ *
7
+ * @example
8
+ * <Admin
9
+ * settings={<Settings listComponent={MyTable} deleteSuccessMessage="Deleted!" />}
10
+ * dataProvider={myDataProvider}
11
+ * >
12
+ * <Resource name="products" />
13
+ * </Admin>
14
+ */
15
+ export const Settings = (_: AdminSettings): null => null;
16
+ Settings.displayName = 'Settings';
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { vi } from 'vitest';
3
+
4
+ export const useTranslate = vi.fn(() => (key: string, options: any) => {
5
+ return options?._ || key;
6
+ });
7
+
8
+ export const useTranslateLabel = vi.fn(() => (label: any) => (typeof label === 'string' ? label : label?.source || ''));
9
+ export const useGetResourceLabel = vi.fn(() => (resource: string) => {
10
+ if (resource === 'products') return 'Products';
11
+ if (resource === 'categories') return 'Categories';
12
+ return resource;
13
+ });
14
+ export const useResourceDefinitions = vi.fn(() => ({}));
15
+ export const useResourceDefinition = vi.fn(() => ({
16
+ name: 'products',
17
+ options: { label: 'Products' },
18
+ }));
19
+ export const useResourceContext = vi.fn();
20
+ export const useRecordContext = vi.fn((record) => record || {});
21
+ export const useDefaultTitle = vi.fn(() => '');
22
+ export const useInput = vi.fn();
23
+ export const useChoicesContext = vi.fn(() => ({ allChoices: [], isPending: false }));
24
+ export const useGetRecordRepresentation = vi.fn(() => (record: any) => record?.name || record?.id || record);
25
+ export const ValidationError = vi.fn(({ error }: any) => <span>{error}</span>);
26
+ export const useStore = vi.fn(() => ['light', vi.fn()]);
27
+ export const useSaveContext = vi.fn(() => ({ save: vi.fn(), saving: false }));
28
+ export const useNotify = vi.fn(() => vi.fn());
29
+ export const useNotificationContext = vi.fn(() => ({
30
+ notifications: [],
31
+ setNotifications: vi.fn(),
32
+ }));
33
+ export const useRedirect = vi.fn(() => vi.fn());
34
+ export const useRefresh = vi.fn(() => vi.fn());
35
+ export const useFieldValue = vi.fn(({ source, record }: any) => record?.[source]);
36
+ export const useLocale = vi.fn(() => 'en');
37
+ export const useLocales = vi.fn(() => [{ locale: 'en', name: 'English' }]);
38
+ export const useSetLocale = vi.fn(() => vi.fn());
39
+ export const useAuthProvider = vi.fn(() => null);
40
+ export const useBulkDeleteController = vi.fn(() => ({
41
+ handleDelete: vi.fn(),
42
+ isPending: false,
43
+ isLoading: false,
44
+ }));
45
+ export const useDeleteController = vi.fn(() => ({
46
+ handleDelete: vi.fn(),
47
+ isPending: false,
48
+ isLoading: false,
49
+ }));
50
+
51
+ export const CreateBase = vi.fn(({ children }: any) => <div data-testid="create-base">{children}</div>);
52
+ export const EditBase = vi.fn(({ children }: any) => <div data-testid="edit-base">{children}</div>);
53
+ export const ShowBase = vi.fn(({ children }: any) => <div data-testid="show-base">{children}</div>);
54
+ export const ListBase = vi.fn(({ children }: any) => <div data-testid="list-base">{children}</div>);
55
+
56
+ export const useShowContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
57
+ export const useEditContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
58
+ export const useCreateContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
59
+ export const useListContext = vi.fn(() => ({ total: 0, isPending: false, selectedIds: [], defaultTitle: 'Products' }));
60
+
61
+ export const useShowController = vi.fn(() => ({ isLoading: false, record: {}, resource: 'products' }));
62
+ export const useEditController = vi.fn(() => ({ isLoading: false, record: {}, resource: 'products' }));
63
+ export const useCreateController = vi.fn(() => ({ isLoading: false, record: {}, resource: 'products' }));
64
+
65
+ export const ShowContextProvider = vi.fn(({ children }: any) => <>{children}</>);
66
+ export const EditContextProvider = vi.fn(({ children }: any) => <>{children}</>);
67
+ export const CreateContextProvider = vi.fn(({ children }: any) => <>{children}</>);
68
+ export const ListContextProvider = vi.fn(({ children }: any) => <>{children}</>);
69
+ export const RecordContextProvider = vi.fn(({ children }: any) => <>{children}</>);
70
+ export const ResourceContextProvider = vi.fn(({ children }: any) => <>{children}</>);
71
+
72
+ export const Form = vi.fn(({ children }: any) => <div data-testid="ra-form">{children}</div>);
73
+ export const FieldTitle = vi.fn(({ label, source }: any) => <span data-testid="field-title">{label || source}</span>);
74
+
75
+ export const ReferenceFieldBase = vi.fn(({ children }: any) => <>{children}</>);
76
+ export const ReferenceInputBase = vi.fn(({ children }: any) => <>{children}</>);
77
+ export const ReferenceManyFieldBase = vi.fn(({ children }: any) => <>{children}</>);
78
+
79
+ export const useList = vi.fn(() => ({ data: [], total: 0, isPending: false }));
80
+ export const TestContext = vi.fn(({ children }: any) => <>{children}</>);
81
+ export const CoreAdmin = vi.fn(({ children }: any) => <>{children}</>);
82
+ export const localStorageStore = vi.fn(() => ({}));
83
+ export const useLocaleState = vi.fn(() => ['en', vi.fn()]);
@@ -1,52 +1,46 @@
1
1
  import React from 'react';
2
2
  import { vi } from 'vitest';
3
+ import { useResourceContext } from './ra-core';
3
4
 
4
- export const useTranslate = vi.fn(() => (key: string, options: any) => options?._ || key);
5
- export const useTranslateLabel = vi.fn(() => (label: any) => typeof label === 'string' ? label : label?.source || '');
6
- export const useGetResourceLabel = vi.fn(() => (resource: string) => {
7
- if (resource === 'products') return 'Products';
8
- if (resource === 'categories') return 'Categories';
9
- return resource;
10
- });
11
- export const useResourceDefinitions = vi.fn(() => ({}));
12
- export const useResourceDefinition = vi.fn(() => ({
13
- name: 'products',
14
- options: { label: 'Products' },
15
- }));
16
- export const useResourceContext = vi.fn();
17
- export const useRecordContext = vi.fn((record) => record || {});
18
- export const useDefaultTitle = vi.fn(() => '');
19
- export const useInput = vi.fn();
20
- export const useChoicesContext = vi.fn(() => ({ allChoices: [], isPending: false }));
21
- export const useGetRecordRepresentation = vi.fn(() => (record: any) => record?.name || record?.id || record);
22
5
  export const useFieldSchema = vi.fn(() => []);
23
6
  export const useInputSchema = vi.fn(() => []);
24
7
  export const ResourceSchemaProvider = vi.fn(({ children }: any) => children);
25
- export const ValidationError = vi.fn(({ error }: any) => <span>{error}</span>);
26
- export const useStore = vi.fn(() => ['light', vi.fn()]);
27
- export const useSaveContext = vi.fn(() => ({ save: vi.fn(), saving: false }));
28
- export const useNotify = vi.fn(() => vi.fn());
29
- export const useRedirect = vi.fn(() => vi.fn());
30
- export const useRefresh = vi.fn(() => vi.fn());
31
- export const useFieldValue = vi.fn(({ source, record }: any) => record?.[source]);
32
- export const useCreatePath = vi.fn(() => (params: any) => `/${params.resource}/${params.id}/${params.type}`);
33
- export const useLocale = vi.fn(() => 'en');
34
- export const useBulkDeleteController = vi.fn(() => ({
35
- handleDelete: vi.fn(),
36
- isPending: false,
37
- isLoading: false,
8
+
9
+ export const useResourceSchema = vi.fn((resourceProp?: string) => {
10
+ const resource = resourceProp || useResourceContext();
11
+ const label = resource ? resource.charAt(0).toUpperCase() + resource.slice(1) : '';
12
+ return {
13
+ resource,
14
+ fieldSchema: [],
15
+ inputSchema: [],
16
+ definition: {
17
+ name: resource,
18
+ options: { label },
19
+ },
20
+ label,
21
+ getField: vi.fn(),
22
+ getInput: vi.fn(),
23
+ };
24
+ });
25
+
26
+ export const useSchemaRegistry = vi.fn(() => ({
27
+ defaultComponents: {},
28
+ registerSchemas: vi.fn(),
29
+ getSchemas: vi.fn(),
38
30
  }));
39
31
 
40
- export const CreateBase = vi.fn(({ children }: any) => <div data-testid="create-base">{children}</div>);
41
- export const EditBase = vi.fn(({ children }: any) => <div data-testid="edit-base">{children}</div>);
42
- export const ShowBase = vi.fn(({ children }: any) => <div data-testid="show-base">{children}</div>);
43
- export const ListBase = vi.fn(({ children }: any) => <div data-testid="list-base">{children}</div>);
32
+ export const useSettings = vi.fn(() => ({}));
33
+
34
+ export const useSettingValue = vi.fn(() => (propValue: any, _settingKey: any, schemaValue?: any) => {
35
+ if (propValue !== undefined) return propValue;
36
+ if (schemaValue !== undefined) return schemaValue;
37
+ return undefined;
38
+ });
39
+
40
+ export const useConstructedPageTitle = vi.fn((type, label) => `${type} ${label}`);
41
+
42
+ export const useCreatePath = vi.fn(() => (params: any) => `/${params.resource}/${params.id}/${params.type}`);
44
43
 
45
- export const useShowContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
46
- export const useEditContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
47
- export const useCreateContext = vi.fn(() => ({ isLoading: false, record: {}, defaultTitle: 'Products' }));
48
- export const useListContext = vi.fn(() => ({ total: 0, isPending: false, selectedIds: [], defaultTitle: 'Products' }));
44
+ export const SchemaRegistryProvider = vi.fn(({ children }: any) => <>{children}</>);
49
45
 
50
- export const Form = vi.fn(({ children }: any) => <div data-testid="ra-form">{children}</div>);
51
- export const FieldTitle = vi.fn(({ label, source }: any) => <span data-testid="field-title">{label || source}</span>);
52
- export const RecordContextProvider = vi.fn(({ children }: any) => <>{children}</>);
46
+ export const Resource = vi.fn(({ children }: any) => <>{children}</>);
@@ -1,4 +1,3 @@
1
-
2
1
  import { render, screen, fireEvent, cleanup } from '@testing-library/react';
3
2
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
4
3
  import { BulkDeleteButton } from './BulkDeleteButton';
@@ -9,9 +8,9 @@ import {
9
8
  useResourceDefinition,
10
9
  useResourceContext,
11
10
  useGetResourceLabel,
12
- } from '@strato-admin/core';
11
+ } from '@strato-admin/ra-core';
13
12
 
14
- vi.mock('@strato-admin/core', () => ({
13
+ vi.mock('@strato-admin/ra-core', () => ({
15
14
  useListContext: vi.fn(),
16
15
  useBulkDeleteController: vi.fn(),
17
16
  useTranslate: vi.fn(),
@@ -20,10 +19,24 @@ vi.mock('@strato-admin/core', () => ({
20
19
  useGetResourceLabel: vi.fn(),
21
20
  }));
22
21
 
22
+ vi.mock('@strato-admin/core', () => ({
23
+ useSchemaRegistry: vi.fn(() => ({ defaultComponents: {} })),
24
+ useSettings: vi.fn(() => ({})),
25
+ useSettingValue: vi.fn(() => (propValue: any, _settingKey: any, schemaValue?: any) => {
26
+ if (propValue !== undefined) return propValue;
27
+ if (schemaValue !== undefined) return schemaValue;
28
+ return undefined;
29
+ }),
30
+ }));
31
+
23
32
  describe('BulkDeleteButton', () => {
24
33
  beforeEach(() => {
25
34
  vi.clearAllMocks();
26
- (useTranslate as any).mockReturnValue((key: string, options: any) => options?._ || key);
35
+ (useTranslate as any).mockReturnValue((key: string, options: any) => {
36
+ if (key === 'strato.message.bulk_delete_title') return 'Delete 2 items';
37
+ if (key === 'strato.message.bulk_delete_content') return 'Are you sure you want to delete these 2 items?';
38
+ return options?._ || key;
39
+ });
27
40
  (useBulkDeleteController as any).mockReturnValue({
28
41
  handleDelete: vi.fn(),
29
42
  isPending: false,
@@ -1,13 +1,6 @@
1
-
2
- import React, { useState } from 'react';
3
- import {
4
- useBulkDeleteController,
5
- useTranslate,
6
- useListContext,
7
- useResourceDefinition,
8
- useResourceContext,
9
- useGetResourceLabel,
10
- } from '@strato-admin/core';
1
+ import { useState } from 'react';
2
+ import { useBulkDeleteController, useTranslate, useListContext, useResourceDefinition } from '@strato-admin/ra-core';
3
+ import { useSettingValue } from '@strato-admin/core';
11
4
  import Modal from '@cloudscape-design/components/modal';
12
5
  import Box from '@cloudscape-design/components/box';
13
6
  import SpaceBetween from '@cloudscape-design/components/space-between';
@@ -17,6 +10,7 @@ export interface BulkDeleteButtonProps {
17
10
  label?: string;
18
11
  variant?: 'primary' | 'normal' | 'link';
19
12
  mutationMode?: 'undoable' | 'optimistic' | 'pessimistic';
13
+ successMessage?: string;
20
14
  dialogTitle?: string;
21
15
  dialogDescription?: string;
22
16
  }
@@ -24,17 +18,20 @@ export interface BulkDeleteButtonProps {
24
18
  export const BulkDeleteButton = ({
25
19
  label,
26
20
  variant = 'normal',
27
- mutationMode = 'pessimistic',
21
+ mutationMode,
22
+ successMessage,
28
23
  dialogTitle,
29
24
  dialogDescription,
30
25
  }: BulkDeleteButtonProps) => {
31
26
  const translate = useTranslate();
32
27
  const { selectedIds } = useListContext();
33
- const resource = useResourceContext();
34
- const getResourceLabel = useGetResourceLabel();
35
28
  const { options } = useResourceDefinition();
29
+ const resolve = useSettingValue();
30
+ const rawDefault = resolve(undefined, 'bulkDeleteSuccessMessage');
31
+ const resolvedDefault = typeof rawDefault === 'function' ? rawDefault(selectedIds?.length ?? 0) : rawDefault;
36
32
  const { handleDelete, isPending, isLoading } = useBulkDeleteController({
37
- mutationMode,
33
+ mutationMode: resolve(mutationMode, 'mutationMode'),
34
+ successMessage: successMessage ?? options?.bulkDeleteSuccessMessage ?? resolvedDefault,
38
35
  });
39
36
 
40
37
  const [isOpen, setIsOpen] = useState(false);
@@ -57,15 +54,18 @@ export const BulkDeleteButton = ({
57
54
  const handleClose = () => {
58
55
  setIsOpen(false);
59
56
  };
60
- console.log('selectedIds', selectedIds);
61
- const defaultTitle = translate('ra.message.bulk_delete_title', {
57
+ const defaultTitle = translate('strato.message.bulk_delete_title', {
62
58
  smart_count: selectedIds?.length || 0,
63
- _: `Delete ${selectedIds?.length || 0} items`,
59
+ _: `{smart_count, plural, one {Delete this item} other {Delete these # items}}`,
64
60
  });
65
61
 
66
- const defaultDescription = translate('ra.message.bulk_delete_content', {
62
+ const defaultDescription = translate('strato.message.bulk_delete_content', {
67
63
  smart_count: selectedIds?.length || 0,
68
- _: `Are you sure you want to delete these ${selectedIds?.length || 0} items?`,
64
+ _: `{
65
+ smart_count, plural,
66
+ one {Are you sure you want to delete this item?}
67
+ other {Are you sure you want to delete these # items?}
68
+ }`,
69
69
  });
70
70
  return (
71
71
  <>
@@ -75,25 +75,20 @@ export const BulkDeleteButton = ({
75
75
  loading={isBusy}
76
76
  disabled={isBusy || !selectedIds || selectedIds.length === 0}
77
77
  >
78
- {label || translate('ra.action.delete', { _: 'Delete' })}
78
+ {label || translate('strato.action.delete', { _: 'Delete' })}
79
79
  </Button>
80
80
  <Modal
81
81
  onDismiss={handleClose}
82
82
  visible={isOpen}
83
- closeAriaLabel={translate('ra.action.close', { _: 'Close' })}
83
+ closeAriaLabel={translate('strato.action.close', { _: 'Close' })}
84
84
  footer={
85
85
  <Box float="right">
86
86
  <SpaceBetween direction="horizontal" size="xs">
87
87
  <Button variant="link" onClick={handleClose}>
88
- {translate('ra.action.cancel', { _: 'Cancel' })}
88
+ {translate('strato.action.cancel', { _: 'Cancel' })}
89
89
  </Button>
90
- <Button
91
- variant="primary"
92
- onClick={handleConfirm}
93
- loading={isBusy}
94
- data-testid="confirm-bulk-delete"
95
- >
96
- {translate('ra.action.confirm', { _: 'Confirm' })}
90
+ <Button variant="primary" onClick={handleConfirm} loading={isBusy} data-testid="confirm-bulk-delete">
91
+ {translate('strato.action.confirm', { _: 'Confirm' })}
97
92
  </Button>
98
93
  </SpaceBetween>
99
94
  </Box>
@@ -1,7 +1,36 @@
1
-
1
+ import React from 'react';
2
2
  import CloudscapeButton, { ButtonProps as CloudscapeButtonProps } from '@cloudscape-design/components/button';
3
3
 
4
- export interface ButtonProps extends CloudscapeButtonProps {
4
+ export interface ButtonProps
5
+ extends Pick<
6
+ CloudscapeButtonProps,
7
+ | 'variant'
8
+ | 'iconName'
9
+ | 'iconSvg'
10
+ | 'iconAlign'
11
+ | 'iconAlt'
12
+ | 'href'
13
+ | 'target'
14
+ | 'rel'
15
+ | 'download'
16
+ | 'onClick'
17
+ | 'onFollow'
18
+ | 'loading'
19
+ | 'loadingText'
20
+ | 'disabled'
21
+ | 'disabledReason'
22
+ | 'ariaLabel'
23
+ | 'ariaDescribedby'
24
+ | 'ariaExpanded'
25
+ | 'ariaControls'
26
+ | 'formAction'
27
+ | 'form'
28
+ | 'nativeButtonAttributes'
29
+ | 'nativeAnchorAttributes'
30
+ | 'wrapText'
31
+ | 'fullWidth'
32
+ | 'external'
33
+ > {
5
34
  children: React.ReactNode;
6
35
  }
7
36
 
@@ -0,0 +1,20 @@
1
+ import { useTranslate } from '@strato-admin/ra-core';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Button, ButtonProps } from './Button';
4
+
5
+ export interface CancelButtonProps extends Omit<ButtonProps, 'children'> {
6
+ label?: string;
7
+ }
8
+
9
+ export const CancelButton = ({ label, variant = 'link', ...props }: CancelButtonProps) => {
10
+ const translate = useTranslate();
11
+ const navigate = useNavigate();
12
+
13
+ return (
14
+ <Button variant={variant} formAction="none" onClick={() => navigate(-1)} {...props}>
15
+ {label || translate('strato.action.cancel', { _: 'Cancel' })}
16
+ </Button>
17
+ );
18
+ };
19
+
20
+ export default CancelButton;
@@ -1,7 +1,8 @@
1
-
2
- import { useResourceContext, useTranslate, useCreatePath, useResourceDefinitions } from '@strato-admin/core';
1
+ import { useResourceContext, useTranslate, useResourceDefinitions } from '@strato-admin/ra-core';
2
+ import { useCreatePath } from '@strato-admin/core';
3
3
  import { useNavigate } from 'react-router-dom';
4
4
  import { Button, ButtonProps } from './Button';
5
+ import type { ButtonProps as CloudscapeButtonProps } from '@cloudscape-design/components/button';
5
6
 
6
7
  export interface CreateButtonProps extends Omit<ButtonProps, 'children'> {
7
8
  label?: string;
@@ -20,17 +21,18 @@ export const CreateButton = ({ label, variant = 'primary', ...props }: CreateBut
20
21
  return null;
21
22
  }
22
23
 
23
- const handleClick = () => {
24
- const path = createPath({
25
- resource,
26
- type: 'create',
27
- });
28
- navigate(path);
24
+ const path = createPath({ resource, type: 'create' });
25
+
26
+ const handleClick: NonNullable<CloudscapeButtonProps['onClick']> = (e) => {
27
+ if (!e.detail.metaKey && !e.detail.ctrlKey && !e.detail.shiftKey && e.detail.button === 0) {
28
+ e.preventDefault();
29
+ navigate(path);
30
+ }
29
31
  };
30
32
 
31
33
  return (
32
- <Button variant={variant} onClick={handleClick} iconName="add-plus" {...props}>
33
- {label || translate('ra.action.create', { _: 'Create' })}
34
+ <Button variant={variant} href={path} onClick={handleClick} iconName="add-plus" {...props}>
35
+ {label || translate('strato.action.create', { _: 'Create' })}
34
36
  </Button>
35
37
  );
36
38
  };