@strato-admin/cloudscape 0.1.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 (255) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/Admin.d.ts +17 -0
  4. package/dist/Admin.js +69 -0
  5. package/dist/RecordLink.d.ts +9 -0
  6. package/dist/RecordLink.js +43 -0
  7. package/dist/__mocks__/strato-core.js +50 -0
  8. package/dist/__mocks__to__delete/strato-core.js +50 -0
  9. package/dist/button/BulkDeleteButton.d.ts +7 -0
  10. package/dist/button/BulkDeleteButton.js +17 -0
  11. package/dist/button/Button.d.ts +6 -0
  12. package/dist/button/Button.js +6 -0
  13. package/dist/button/CreateButton.d.ts +6 -0
  14. package/dist/button/CreateButton.js +24 -0
  15. package/dist/button/EditButton.d.ts +8 -0
  16. package/dist/button/EditButton.js +24 -0
  17. package/dist/button/SaveButton.d.ts +6 -0
  18. package/dist/button/SaveButton.js +8 -0
  19. package/dist/button/index.d.ts +5 -0
  20. package/dist/button/index.js +5 -0
  21. package/dist/collection-hooks/index.d.ts +2 -0
  22. package/dist/collection-hooks/index.js +2 -0
  23. package/dist/collection-hooks/interfaces.d.ts +93 -0
  24. package/dist/collection-hooks/interfaces.js +1 -0
  25. package/dist/collection-hooks/useCollection.d.ts +3 -0
  26. package/dist/collection-hooks/useCollection.js +102 -0
  27. package/dist/create/Create.d.ts +40 -0
  28. package/dist/create/Create.js +34 -0
  29. package/dist/create/CreateHeader.d.ts +7 -0
  30. package/dist/create/CreateHeader.js +18 -0
  31. package/dist/create/index.d.ts +2 -0
  32. package/dist/create/index.js +2 -0
  33. package/dist/detail/KeyValuePairs.d.ts +36 -0
  34. package/dist/detail/KeyValuePairs.js +58 -0
  35. package/dist/detail/Show.d.ts +39 -0
  36. package/dist/detail/Show.js +40 -0
  37. package/dist/detail/ShowHeader.d.ts +7 -0
  38. package/dist/detail/ShowHeader.js +19 -0
  39. package/dist/detail/index.d.ts +3 -0
  40. package/dist/detail/index.js +3 -0
  41. package/dist/edit/Edit.d.ts +42 -0
  42. package/dist/edit/Edit.js +38 -0
  43. package/dist/edit/EditHeader.d.ts +7 -0
  44. package/dist/edit/EditHeader.js +18 -0
  45. package/dist/edit/index.d.ts +2 -0
  46. package/dist/edit/index.js +2 -0
  47. package/dist/field/ArrayField.d.ts +29 -0
  48. package/dist/field/ArrayField.js +30 -0
  49. package/dist/field/BadgeField.d.ts +12 -0
  50. package/dist/field/BadgeField.js +15 -0
  51. package/dist/field/BooleanField.d.ts +18 -0
  52. package/dist/field/BooleanField.js +14 -0
  53. package/dist/field/CurrencyField.d.ts +19 -0
  54. package/dist/field/CurrencyField.js +23 -0
  55. package/dist/field/DateField.d.ts +14 -0
  56. package/dist/field/DateField.js +17 -0
  57. package/dist/field/IdField.d.ts +17 -0
  58. package/dist/field/IdField.js +21 -0
  59. package/dist/field/NumberField.d.ts +14 -0
  60. package/dist/field/NumberField.js +18 -0
  61. package/dist/field/ReferenceField.d.ts +16 -0
  62. package/dist/field/ReferenceField.js +23 -0
  63. package/dist/field/ReferenceManyField.d.ts +55 -0
  64. package/dist/field/ReferenceManyField.js +19 -0
  65. package/dist/field/StatusIndicatorField.d.ts +56 -0
  66. package/dist/field/StatusIndicatorField.js +48 -0
  67. package/dist/field/TextField.d.ts +5 -0
  68. package/dist/field/TextField.js +11 -0
  69. package/dist/field/index.d.ts +23 -0
  70. package/dist/field/index.js +23 -0
  71. package/dist/field/types.d.ts +56 -0
  72. package/dist/field/types.js +1 -0
  73. package/dist/form/Form.d.ts +13 -0
  74. package/dist/form/Form.js +33 -0
  75. package/dist/form/index.d.ts +2 -0
  76. package/dist/form/index.js +2 -0
  77. package/dist/index.d.ts +22 -0
  78. package/dist/index.js +22 -0
  79. package/dist/input/AttributeEditor.d.ts +25 -0
  80. package/dist/input/AttributeEditor.js +80 -0
  81. package/dist/input/AutocompleteInput.d.ts +10 -0
  82. package/dist/input/AutocompleteInput.js +67 -0
  83. package/dist/input/FieldTitle.d.ts +8 -0
  84. package/dist/input/FieldTitle.js +29 -0
  85. package/dist/input/FormField.d.ts +7 -0
  86. package/dist/input/FormField.js +35 -0
  87. package/dist/input/FormFieldContext.d.ts +6 -0
  88. package/dist/input/FormFieldContext.js +3 -0
  89. package/dist/input/NumberInput.d.ts +7 -0
  90. package/dist/input/NumberInput.js +27 -0
  91. package/dist/input/ReferenceInput.d.ts +3 -0
  92. package/dist/input/ReferenceInput.js +25 -0
  93. package/dist/input/SelectInput.d.ts +15 -0
  94. package/dist/input/SelectInput.js +47 -0
  95. package/dist/input/SliderInput.d.ts +6 -0
  96. package/dist/input/SliderInput.js +25 -0
  97. package/dist/input/TextAreaInput.d.ts +6 -0
  98. package/dist/input/TextAreaInput.js +23 -0
  99. package/dist/input/TextInput.d.ts +7 -0
  100. package/dist/input/TextInput.js +23 -0
  101. package/dist/input/index.d.ts +11 -0
  102. package/dist/input/index.js +11 -0
  103. package/dist/input/types.d.ts +6 -0
  104. package/dist/input/types.js +1 -0
  105. package/dist/layout/AppLayout.d.ts +8 -0
  106. package/dist/layout/AppLayout.js +38 -0
  107. package/dist/layout/TopNavigation.d.ts +6 -0
  108. package/dist/layout/TopNavigation.js +53 -0
  109. package/dist/layout/index.d.ts +2 -0
  110. package/dist/layout/index.js +2 -0
  111. package/dist/list/Cards.d.ts +11 -0
  112. package/dist/list/Cards.js +27 -0
  113. package/dist/list/List.d.ts +43 -0
  114. package/dist/list/List.js +28 -0
  115. package/dist/list/Table.d.ts +112 -0
  116. package/dist/list/Table.examples.d.ts +1 -0
  117. package/dist/list/Table.examples.js +3 -0
  118. package/dist/list/Table.js +218 -0
  119. package/dist/list/TableHeader.d.ts +7 -0
  120. package/dist/list/TableHeader.js +22 -0
  121. package/dist/list/index.d.ts +4 -0
  122. package/dist/list/index.js +4 -0
  123. package/dist/preferences/index.d.ts +0 -0
  124. package/dist/preferences/index.js +1 -0
  125. package/dist/theme/ThemeManager.d.ts +2 -0
  126. package/dist/theme/ThemeManager.js +11 -0
  127. package/dist/theme/index.d.ts +2 -0
  128. package/dist/theme/index.js +2 -0
  129. package/package.json +73 -0
  130. package/src/Admin.test.tsx +32 -0
  131. package/src/Admin.tsx +123 -0
  132. package/src/RecordLink.stories.tsx +56 -0
  133. package/src/RecordLink.tsx +67 -0
  134. package/src/__mocks__/strato-core.tsx +52 -0
  135. package/src/button/BulkDeleteButton.stories.tsx +59 -0
  136. package/src/button/BulkDeleteButton.test.tsx +64 -0
  137. package/src/button/BulkDeleteButton.tsx +41 -0
  138. package/src/button/Button.stories.tsx +31 -0
  139. package/src/button/Button.tsx +12 -0
  140. package/src/button/CreateButton.stories.tsx +42 -0
  141. package/src/button/CreateButton.tsx +38 -0
  142. package/src/button/EditButton.stories.tsx +29 -0
  143. package/src/button/EditButton.tsx +38 -0
  144. package/src/button/SaveButton.stories.tsx +35 -0
  145. package/src/button/SaveButton.tsx +19 -0
  146. package/src/button/index.ts +5 -0
  147. package/src/collection-hooks/index.ts +2 -0
  148. package/src/collection-hooks/interfaces.ts +80 -0
  149. package/src/collection-hooks/useCollection.test.ts +413 -0
  150. package/src/collection-hooks/useCollection.ts +125 -0
  151. package/src/create/Create.test.tsx +63 -0
  152. package/src/create/Create.tsx +93 -0
  153. package/src/create/CreateHeader.tsx +34 -0
  154. package/src/create/index.ts +2 -0
  155. package/src/detail/KeyValuePairs.test.tsx +98 -0
  156. package/src/detail/KeyValuePairs.tsx +107 -0
  157. package/src/detail/Show.test.tsx +96 -0
  158. package/src/detail/Show.tsx +104 -0
  159. package/src/detail/ShowHeader.test.tsx +80 -0
  160. package/src/detail/ShowHeader.tsx +35 -0
  161. package/src/detail/index.ts +3 -0
  162. package/src/edit/Edit.test.tsx +91 -0
  163. package/src/edit/Edit.tsx +102 -0
  164. package/src/edit/EditHeader.tsx +34 -0
  165. package/src/edit/index.ts +2 -0
  166. package/src/field/ArrayField.tsx +51 -0
  167. package/src/field/BadgeField.tsx +33 -0
  168. package/src/field/BooleanField.stories.tsx +56 -0
  169. package/src/field/BooleanField.test.tsx +63 -0
  170. package/src/field/BooleanField.tsx +42 -0
  171. package/src/field/CurrencyField.stories.tsx +67 -0
  172. package/src/field/CurrencyField.tsx +45 -0
  173. package/src/field/DateField.stories.tsx +67 -0
  174. package/src/field/DateField.tsx +33 -0
  175. package/src/field/IdField.test.tsx +88 -0
  176. package/src/field/IdField.tsx +40 -0
  177. package/src/field/NumberField.stories.tsx +75 -0
  178. package/src/field/NumberField.tsx +35 -0
  179. package/src/field/ReferenceField.test.tsx +88 -0
  180. package/src/field/ReferenceField.tsx +64 -0
  181. package/src/field/ReferenceManyField.test.tsx +41 -0
  182. package/src/field/ReferenceManyField.tsx +73 -0
  183. package/src/field/StatusIndicatorField.stories.tsx +93 -0
  184. package/src/field/StatusIndicatorField.test.tsx +143 -0
  185. package/src/field/StatusIndicatorField.tsx +119 -0
  186. package/src/field/TextField.stories.tsx +45 -0
  187. package/src/field/TextField.tsx +17 -0
  188. package/src/field/index.ts +23 -0
  189. package/src/field/types.ts +58 -0
  190. package/src/form/Form.test.tsx +55 -0
  191. package/src/form/Form.tsx +66 -0
  192. package/src/form/index.ts +2 -0
  193. package/src/index.ts +25 -0
  194. package/src/input/AttributeEditor.test.tsx +147 -0
  195. package/src/input/AttributeEditor.tsx +185 -0
  196. package/src/input/AutocompleteInput.test.tsx +178 -0
  197. package/src/input/AutocompleteInput.tsx +116 -0
  198. package/src/input/FieldTitle.tsx +53 -0
  199. package/src/input/FormField.tsx +87 -0
  200. package/src/input/FormFieldContext.ts +9 -0
  201. package/src/input/NumberInput.tsx +56 -0
  202. package/src/input/ReferenceInput.test.tsx +35 -0
  203. package/src/input/ReferenceInput.tsx +36 -0
  204. package/src/input/SelectInput.tsx +91 -0
  205. package/src/input/SliderInput.test.tsx +103 -0
  206. package/src/input/SliderInput.tsx +49 -0
  207. package/src/input/TextAreaInput.tsx +48 -0
  208. package/src/input/TextInput.test.tsx +91 -0
  209. package/src/input/TextInput.tsx +51 -0
  210. package/src/input/index.ts +11 -0
  211. package/src/input/types.ts +14 -0
  212. package/src/layout/AppLayout.test.tsx +87 -0
  213. package/src/layout/AppLayout.tsx +60 -0
  214. package/src/layout/TopNavigation.test.tsx +78 -0
  215. package/src/layout/TopNavigation.tsx +84 -0
  216. package/src/layout/index.ts +2 -0
  217. package/src/list/Cards.tsx +58 -0
  218. package/src/list/List.tsx +76 -0
  219. package/src/list/Table.examples.tsx +11 -0
  220. package/src/list/Table.stories.tsx +73 -0
  221. package/src/list/Table.test.tsx +255 -0
  222. package/src/list/Table.tsx +438 -0
  223. package/src/list/TableHeader.test.tsx +114 -0
  224. package/src/list/TableHeader.tsx +44 -0
  225. package/src/list/index.ts +4 -0
  226. package/src/preferences/index.ts +0 -0
  227. package/src/stories/Button.stories.ts +54 -0
  228. package/src/stories/Button.tsx +31 -0
  229. package/src/stories/Configure.mdx +369 -0
  230. package/src/stories/Header.stories.ts +34 -0
  231. package/src/stories/Header.tsx +47 -0
  232. package/src/stories/Page.stories.ts +33 -0
  233. package/src/stories/Page.tsx +71 -0
  234. package/src/stories/RaStoryDecorator.tsx +38 -0
  235. package/src/stories/assets/accessibility.png +0 -0
  236. package/src/stories/assets/accessibility.svg +1 -0
  237. package/src/stories/assets/addon-library.png +0 -0
  238. package/src/stories/assets/assets.png +0 -0
  239. package/src/stories/assets/avif-test-image.avif +0 -0
  240. package/src/stories/assets/context.png +0 -0
  241. package/src/stories/assets/discord.svg +1 -0
  242. package/src/stories/assets/docs.png +0 -0
  243. package/src/stories/assets/figma-plugin.png +0 -0
  244. package/src/stories/assets/github.svg +1 -0
  245. package/src/stories/assets/share.png +0 -0
  246. package/src/stories/assets/styling.png +0 -0
  247. package/src/stories/assets/testing.png +0 -0
  248. package/src/stories/assets/theming.png +0 -0
  249. package/src/stories/assets/tutorials.svg +1 -0
  250. package/src/stories/assets/youtube.svg +1 -0
  251. package/src/stories/button.css +30 -0
  252. package/src/stories/header.css +32 -0
  253. package/src/stories/page.css +68 -0
  254. package/src/theme/ThemeManager.tsx +15 -0
  255. package/src/theme/index.ts +2 -0
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { type RaRecord } from '@strato-admin/core';
3
+ export interface ListProps<_RecordType extends RaRecord = any> {
4
+ children?: React.ReactNode;
5
+ fieldSchema?: React.ReactNode;
6
+ include?: string[];
7
+ exclude?: string[];
8
+ title?: React.ReactNode;
9
+ actions?: React.ReactNode;
10
+ /**
11
+ * Whether to enable text filtering in the implicit Table.
12
+ * @default true
13
+ */
14
+ filtering?: boolean;
15
+ /**
16
+ * Whether to show the preferences button in the implicit Table.
17
+ * @default true
18
+ */
19
+ preferences?: boolean | React.ReactNode;
20
+ [key: string]: any;
21
+ }
22
+ /**
23
+ * A List component that provides a list context and a Cloudscape Table.
24
+ *
25
+ * @example
26
+ * <List>
27
+ * <Table>
28
+ * <Table.Column source="name" />
29
+ * </Table>
30
+ * </List>
31
+ *
32
+ * @example
33
+ * // Using FieldSchema from context
34
+ * <List include={['name', 'price']} />
35
+ *
36
+ * @example
37
+ * // Passing a custom field schema
38
+ * <List fieldSchema={<FieldSchema>...</FieldSchema>}>
39
+ * <Table />
40
+ * </List>
41
+ */
42
+ export declare const List: <RecordType extends RaRecord = any>({ children, fieldSchema, include, exclude, title, actions, filtering, preferences, ...props }: ListProps<RecordType>) => import("react/jsx-runtime").JSX.Element;
43
+ export default List;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ListBase, ResourceSchemaProvider } from '@strato-admin/core';
3
+ import Table from './Table';
4
+ /**
5
+ * A List component that provides a list context and a Cloudscape Table.
6
+ *
7
+ * @example
8
+ * <List>
9
+ * <Table>
10
+ * <Table.Column source="name" />
11
+ * </Table>
12
+ * </List>
13
+ *
14
+ * @example
15
+ * // Using FieldSchema from context
16
+ * <List include={['name', 'price']} />
17
+ *
18
+ * @example
19
+ * // Passing a custom field schema
20
+ * <List fieldSchema={<FieldSchema>...</FieldSchema>}>
21
+ * <Table />
22
+ * </List>
23
+ */
24
+ export const List = ({ children, fieldSchema, include, exclude, title, actions, filtering = true, preferences = true, ...props }) => {
25
+ const finalChildren = children || (_jsx(Table, { include: include, exclude: exclude, title: title, actions: actions, filtering: filtering, preferences: preferences }));
26
+ return (_jsx(ListBase, { ...props, children: _jsx(ResourceSchemaProvider, { resource: props.resource, fieldSchema: fieldSchema, children: finalChildren }) }));
27
+ };
28
+ export default List;
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import { TableProps as CloudscapeTableProps } from '@cloudscape-design/components/table';
3
+ import { RaRecord } from '@strato-admin/core';
4
+ import { type RecordLinkType } from '../RecordLink';
5
+ export type CloudscapeColumnDefinitionProps = Partial<Omit<CloudscapeTableProps.ColumnDefinition<any>, 'id' | 'header' | 'cell' | 'sortingField'>>;
6
+ export interface ColumnProps extends CloudscapeColumnDefinitionProps {
7
+ source?: string;
8
+ label?: string | React.ReactNode;
9
+ header?: React.ReactNode;
10
+ children?: React.ReactNode;
11
+ sortable?: boolean;
12
+ link?: RecordLinkType;
13
+ field?: React.ComponentType<any>;
14
+ }
15
+ export declare const Column: ({ children, source, link, field: FieldComponent }: ColumnProps) => import("react/jsx-runtime").JSX.Element;
16
+ export interface NumberColumnProps extends ColumnProps {
17
+ source?: string;
18
+ }
19
+ export declare const NumberColumn: ({ children, source, link, field: FieldComponent }: NumberColumnProps) => import("react/jsx-runtime").JSX.Element;
20
+ export interface DateColumnProps extends ColumnProps {
21
+ source?: string;
22
+ }
23
+ export declare const DateColumn: ({ children, source, link, field: FieldComponent }: DateColumnProps) => import("react/jsx-runtime").JSX.Element;
24
+ export interface BooleanColumnProps extends ColumnProps {
25
+ source?: string;
26
+ }
27
+ export declare const BooleanColumn: ({ children, source, field: FieldComponent }: BooleanColumnProps) => import("react/jsx-runtime").JSX.Element;
28
+ export interface ReferenceColumnProps extends ColumnProps {
29
+ source?: string;
30
+ reference: string;
31
+ }
32
+ export declare const ReferenceColumn: ({ children, source, reference, link, field: FieldComponent }: ReferenceColumnProps) => import("react/jsx-runtime").JSX.Element;
33
+ /**
34
+ * Properties for the Table component.
35
+ */
36
+ export interface TableProps<RecordType extends RaRecord = any> extends Partial<Omit<CloudscapeTableProps<RecordType>, 'items' | 'columnDefinitions' | 'preferences'>> {
37
+ /**
38
+ * The title content of the table. Can be a string or a React node.
39
+ */
40
+ title?: React.ReactNode;
41
+ /**
42
+ * Actions to display in the table header, typically a button group.
43
+ */
44
+ actions?: React.ReactNode;
45
+ /**
46
+ * The columns to display, usually using `Table.Column` and its variants.
47
+ */
48
+ children?: React.ReactNode;
49
+ /**
50
+ * Include only these fields from the schema.
51
+ */
52
+ include?: string[];
53
+ /**
54
+ * Exclude these fields from the schema.
55
+ */
56
+ exclude?: string[];
57
+ /**
58
+ * Whether to enable text filtering.
59
+ * @default true
60
+ */
61
+ filtering?: boolean;
62
+ /**
63
+ * Placeholder text for the filter input.
64
+ * @default "Search..."
65
+ */
66
+ filteringPlaceholder?: string;
67
+ /**
68
+ * Options for the page size selector.
69
+ */
70
+ pageSizeOptions?: ReadonlyArray<{
71
+ value: number;
72
+ label?: string;
73
+ }>;
74
+ /**
75
+ * Whether to show the preferences button or custom preferences content.
76
+ * @default true
77
+ */
78
+ preferences?: boolean | React.ReactNode;
79
+ /**
80
+ * Whether columns can be reordered by the user.
81
+ * @default true
82
+ */
83
+ reorderable?: boolean;
84
+ /**
85
+ * The fields to display by default.
86
+ * Can be an array of field sources/IDs.
87
+ * If not specified, the first 5 fields will be shown.
88
+ */
89
+ defaultVisibleFields?: string[];
90
+ }
91
+ /**
92
+ * The Table component provides a declarative way to build data tables with Cloudscape components
93
+ * while integrating with React Admin's data fetching and state management.
94
+ *
95
+ * @example
96
+ * ```tsx
97
+ * <Table title="Products">
98
+ * <Table.Column source="name" label="Name" />
99
+ * <Table.NumberColumn source="price" label="Price" />
100
+ * </Table>
101
+ * ```
102
+ */
103
+ export declare const Table: {
104
+ <RecordType extends RaRecord = any>({ title, actions, children, include, exclude, filtering, filteringPlaceholder, pageSizeOptions, preferences, reorderable, defaultVisibleFields, selectionType, ...props }: TableProps<RecordType>): import("react/jsx-runtime").JSX.Element;
105
+ Column: ({ children, source, link, field: FieldComponent }: ColumnProps) => import("react/jsx-runtime").JSX.Element;
106
+ NumberColumn: ({ children, source, link, field: FieldComponent }: NumberColumnProps) => import("react/jsx-runtime").JSX.Element;
107
+ DateColumn: ({ children, source, link, field: FieldComponent }: DateColumnProps) => import("react/jsx-runtime").JSX.Element;
108
+ BooleanColumn: ({ children, source, field: FieldComponent }: BooleanColumnProps) => import("react/jsx-runtime").JSX.Element;
109
+ ReferenceColumn: ({ children, source, reference, link, field: FieldComponent }: ReferenceColumnProps) => import("react/jsx-runtime").JSX.Element;
110
+ Header: ({ title, actions, ...props }: import("./TableHeader").TableHeaderProps) => import("react/jsx-runtime").JSX.Element;
111
+ };
112
+ export default Table;
@@ -0,0 +1 @@
1
+ export declare const ProductList: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Table, Column, NumberColumn, DateColumn } from './Table';
3
+ export const ProductList = () => (_jsxs(Table, { title: "Product Catalog", children: [_jsx(Column, { source: "name", label: "Product Name" }), _jsx(Column, { source: "category", label: "Category" }), _jsx(NumberColumn, { source: "price", label: "Price" }), _jsx(NumberColumn, { source: "stock", label: "Inventory" }), _jsx(DateColumn, { source: "lastUpdated", label: "Last Updated" })] }));
@@ -0,0 +1,218 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import CloudscapeTable from '@cloudscape-design/components/table';
4
+ import Pagination from '@cloudscape-design/components/pagination';
5
+ import Box from '@cloudscape-design/components/box';
6
+ import TextFilter from '@cloudscape-design/components/text-filter';
7
+ import CollectionPreferences from '@cloudscape-design/components/collection-preferences';
8
+ import { RecordContextProvider, useResourceContext, useFieldSchema, useResourceDefinition, useGetResourceLabel, useTranslateLabel, useTranslate } from '@strato-admin/core';
9
+ import { useCollection } from '../collection-hooks';
10
+ import TextField from '../field/TextField';
11
+ import NumberField from '../field/NumberField';
12
+ import DateField from '../field/DateField';
13
+ import BooleanField from '../field/BooleanField';
14
+ import ReferenceField from '../field/ReferenceField';
15
+ import { TableHeader } from './TableHeader';
16
+ export const Column = ({ children, source, link, field: FieldComponent }) => {
17
+ if (children) {
18
+ return (_jsx(_Fragment, { children: React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement(child, { source }) : child) }));
19
+ }
20
+ if (FieldComponent) {
21
+ return _jsx(FieldComponent, { link: link, source: source });
22
+ }
23
+ return _jsx(TextField, { link: link, source: source });
24
+ };
25
+ export const NumberColumn = ({ children, source, link, field: FieldComponent }) => {
26
+ if (children) {
27
+ return (_jsx(_Fragment, { children: React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement(child, { source }) : child) }));
28
+ }
29
+ if (FieldComponent) {
30
+ return _jsx(FieldComponent, { link: link, source: source });
31
+ }
32
+ return _jsx(NumberField, { link: link, source: source });
33
+ };
34
+ NumberColumn.isNumberColumn = true;
35
+ export const DateColumn = ({ children, source, link, field: FieldComponent }) => {
36
+ if (children) {
37
+ return (_jsx(_Fragment, { children: React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement(child, { source }) : child) }));
38
+ }
39
+ if (FieldComponent) {
40
+ return _jsx(FieldComponent, { link: link, source: source });
41
+ }
42
+ return _jsx(DateField, { link: link, source: source });
43
+ };
44
+ export const BooleanColumn = ({ children, source, field: FieldComponent }) => {
45
+ if (children) {
46
+ return (_jsx(_Fragment, { children: React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement(child, { source }) : child) }));
47
+ }
48
+ if (FieldComponent) {
49
+ return _jsx(FieldComponent, { source: source });
50
+ }
51
+ return _jsx(BooleanField, { source: source });
52
+ };
53
+ export const ReferenceColumn = ({ children, source, reference, link, field: FieldComponent }) => {
54
+ // ReferenceCol requires reference, so we pass it down
55
+ if (FieldComponent) {
56
+ return (_jsx(FieldComponent, { reference: reference, link: link, source: source, children: children }));
57
+ }
58
+ return (_jsx(ReferenceField, { reference: reference, link: link, source: source, children: children }));
59
+ };
60
+ const defaultPageSizeOptions = [
61
+ { value: 10, label: '10 items' },
62
+ { value: 25, label: '25 items' },
63
+ { value: 50, label: '50 items' },
64
+ { value: 100, label: '100 items' },
65
+ ];
66
+ /**
67
+ * The Table component provides a declarative way to build data tables with Cloudscape components
68
+ * while integrating with React Admin's data fetching and state management.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * <Table title="Products">
73
+ * <Table.Column source="name" label="Name" />
74
+ * <Table.NumberColumn source="price" label="Price" />
75
+ * </Table>
76
+ * ```
77
+ */
78
+ export const Table = ({ title, actions, children, include, exclude, filtering = true, filteringPlaceholder, pageSizeOptions = defaultPageSizeOptions, preferences = true, reorderable = true, defaultVisibleFields, selectionType, ...props }) => {
79
+ const resource = useResourceContext();
80
+ const translate = useTranslate();
81
+ const translateLabel = useTranslateLabel();
82
+ const schemaChildren = useFieldSchema();
83
+ const resourceDefinition = useResourceDefinition({ resource });
84
+ const finalSelectionType = selectionType ?? (resourceDefinition?.options?.canDelete ? 'multi' : undefined);
85
+ const finalChildren = React.useMemo(() => {
86
+ const baseChildren = children || schemaChildren;
87
+ let result = React.Children.toArray(baseChildren);
88
+ if (include) {
89
+ result = result.filter((child) => React.isValidElement(child) && include.includes(child.props.source));
90
+ }
91
+ else if (exclude) {
92
+ result = result.filter((child) => React.isValidElement(child) && !exclude.includes(child.props.source));
93
+ }
94
+ return result;
95
+ }, [children, schemaChildren, include, exclude]);
96
+ // 1. Extract columns and options before calling useCollection
97
+ const extractedColumns = React.useMemo(() => {
98
+ const columns = [];
99
+ const options = [];
100
+ finalChildren.forEach((child, index) => {
101
+ if (!React.isValidElement(child)) {
102
+ return;
103
+ }
104
+ const { source, label, header: childHeader, sortable, link, field, children: childChildren, ...restColumnProps } = child.props;
105
+ const isNumberColumn = child.type?.isNumberColumn;
106
+ const headerLabel = translateLabel({ label, resource, source });
107
+ const finalHeader = isNumberColumn ? _jsx(Box, { textAlign: "right", children: headerLabel }) : headerLabel;
108
+ const columnId = source || `col-${index}`;
109
+ const id = resource ? `${resource}___${columnId}` : columnId;
110
+ columns.push({
111
+ ...restColumnProps,
112
+ id,
113
+ header: finalHeader,
114
+ cell: (item) => {
115
+ const content = _jsx(RecordContextProvider, { value: item, children: child });
116
+ return isNumberColumn ? _jsx(Box, { textAlign: "right", children: content }) : content;
117
+ },
118
+ sortingField: sortable !== false ? source : undefined,
119
+ });
120
+ // If we have a meaningful label/header string, allow toggling
121
+ if (typeof headerLabel === 'string') {
122
+ options.push({
123
+ id,
124
+ label: headerLabel,
125
+ });
126
+ }
127
+ });
128
+ return { columns, options };
129
+ }, [finalChildren, resource, translateLabel]);
130
+ const defaultVisibleContent = React.useMemo(() => {
131
+ if (extractedColumns.options.length === 0)
132
+ return undefined;
133
+ if (defaultVisibleFields) {
134
+ // Map user-provided fields to their actual IDs
135
+ return extractedColumns.options
136
+ .filter((opt) => {
137
+ const column = extractedColumns.columns.find((c) => c.id === opt.id);
138
+ return (defaultVisibleFields.includes(opt.id) ||
139
+ (column?.sortingField && defaultVisibleFields.includes(column.sortingField)));
140
+ })
141
+ .map((opt) => opt.id);
142
+ }
143
+ // Default to first 5 toggleable columns
144
+ return extractedColumns.options.slice(0, 5).map((opt) => opt.id);
145
+ }, [extractedColumns, defaultVisibleFields]);
146
+ const defaultContentDisplay = React.useMemo(() => {
147
+ if (extractedColumns.options.length === 0)
148
+ return undefined;
149
+ const visibleIds = defaultVisibleContent || [];
150
+ return extractedColumns.options.map((opt) => ({
151
+ id: opt.id,
152
+ visible: visibleIds.includes(opt.id),
153
+ }));
154
+ }, [extractedColumns.options, defaultVisibleContent]);
155
+ const { items, paginationProps, collectionProps, filterProps, preferencesProps } = useCollection({
156
+ filtering: {},
157
+ pagination: {},
158
+ sorting: {},
159
+ preferences: {
160
+ pageSizeOptions,
161
+ visibleContentOptions: !reorderable && extractedColumns.options.length > 0 ? extractedColumns.options : undefined,
162
+ contentDisplayOptions: reorderable && extractedColumns.options.length > 0 ? extractedColumns.options : undefined,
163
+ visibleContent: defaultVisibleContent,
164
+ contentDisplay: defaultContentDisplay,
165
+ },
166
+ });
167
+ // 2. Filter columnDefinitions if reordering is disabled (Cloudscape Table handles it if columnDisplay is passed)
168
+ const columnDefinitions = React.useMemo(() => {
169
+ if (reorderable || !preferencesProps.preferences.visibleContent) {
170
+ return extractedColumns.columns;
171
+ }
172
+ return extractedColumns.columns.filter((col) => {
173
+ // Always show columns that are not in options (non-toggleable columns like Actions)
174
+ const isToggleable = extractedColumns.options.some((opt) => opt.id === col.id);
175
+ if (!isToggleable)
176
+ return true;
177
+ return preferencesProps.preferences.visibleContent?.includes(col.id);
178
+ });
179
+ }, [extractedColumns.columns, extractedColumns.options, preferencesProps.preferences.visibleContent, reorderable]);
180
+ const getResourceLabel = useGetResourceLabel();
181
+ const tableHeader = React.useMemo(() => {
182
+ if (title === null || title === false) {
183
+ return undefined;
184
+ }
185
+ if (React.isValidElement(title)) {
186
+ return title;
187
+ }
188
+ const finalTitle = title !== undefined ? title : getResourceLabel(resource, 2);
189
+ return _jsx(TableHeader, { title: finalTitle, actions: actions });
190
+ }, [title, actions, resource, getResourceLabel]);
191
+ return (_jsx(CloudscapeTable, { ...collectionProps, ...props, selectionType: finalSelectionType, stripedRows: preferencesProps.preferences.stripedRows, wrapLines: preferencesProps.preferences.wrapLines, columnDefinitions: columnDefinitions, columnDisplay: reorderable ? preferencesProps.preferences.contentDisplay : undefined, items: items || [], header: tableHeader, filter: filtering && (_jsx(TextFilter, { ...filterProps })), pagination: _jsx(Pagination, { ...paginationProps }), preferences: preferences === true || pageSizeOptions ? (_jsx(CollectionPreferences, { ...preferencesProps, pageSizePreference: pageSizeOptions
192
+ ? {
193
+ options: pageSizeOptions,
194
+ }
195
+ : undefined, visibleContentPreference: !reorderable && extractedColumns.options.length > 0
196
+ ? {
197
+ title: translate('ra.action.select_columns', { _: 'Select visible columns' }),
198
+ options: [
199
+ {
200
+ label: translate('ra.action.select_columns', { _: 'Select visible columns' }),
201
+ options: extractedColumns.options,
202
+ },
203
+ ],
204
+ }
205
+ : undefined, contentDisplayPreference: reorderable && extractedColumns.options.length > 0
206
+ ? {
207
+ title: translate('ra.action.select_columns', { _: 'Select visible columns' }),
208
+ options: extractedColumns.options,
209
+ }
210
+ : undefined })) : React.isValidElement(preferences) ? (preferences) : undefined }));
211
+ };
212
+ Table.Column = Column;
213
+ Table.NumberColumn = NumberColumn;
214
+ Table.DateColumn = DateColumn;
215
+ Table.BooleanColumn = BooleanColumn;
216
+ Table.ReferenceColumn = ReferenceColumn;
217
+ Table.Header = TableHeader;
218
+ export default Table;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { HeaderProps } from '@cloudscape-design/components/header';
3
+ export interface TableHeaderProps extends Omit<HeaderProps, 'children'> {
4
+ title?: React.ReactNode;
5
+ }
6
+ export declare const TableHeader: ({ title, actions, ...props }: TableHeaderProps) => import("react/jsx-runtime").JSX.Element;
7
+ export default TableHeader;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import Header from '@cloudscape-design/components/header';
4
+ import SpaceBetween from '@cloudscape-design/components/space-between';
5
+ import { useListContext, useTranslate, useLocale } from '@strato-admin/core';
6
+ import { BulkDeleteButton } from '../button/BulkDeleteButton';
7
+ import { CreateButton } from '../button/CreateButton';
8
+ export const TableHeader = ({ title, actions, ...props }) => {
9
+ const translate = useTranslate();
10
+ const locale = useLocale();
11
+ const { total, isPending, defaultTitle } = useListContext();
12
+ const headerTitle = React.useMemo(() => {
13
+ if (title !== undefined) {
14
+ return typeof title === 'string' ? translate(title, { _: title }) : title;
15
+ }
16
+ return defaultTitle;
17
+ }, [title, defaultTitle, translate, locale]);
18
+ const counter = props.counter !== undefined ? props.counter : !isPending && total !== undefined ? `(${total})` : undefined;
19
+ const headerActions = actions !== undefined ? (actions) : (_jsxs(SpaceBetween, { direction: "horizontal", size: "xs", children: [_jsx(BulkDeleteButton, {}), _jsx(CreateButton, {})] }));
20
+ return (_jsx(Header, { variant: "h2", ...props, actions: headerActions, counter: counter, children: headerTitle }));
21
+ };
22
+ export default TableHeader;
@@ -0,0 +1,4 @@
1
+ export * from './Table';
2
+ export * from './Cards';
3
+ export * from './TableHeader';
4
+ export * from './List';
@@ -0,0 +1,4 @@
1
+ export * from './Table';
2
+ export * from './Cards';
3
+ export * from './TableHeader';
4
+ export * from './List';
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,2 @@
1
+ export declare const ThemeManager: () => null;
2
+ export default ThemeManager;
@@ -0,0 +1,11 @@
1
+ import { useEffect } from 'react';
2
+ import { useStore } from '@strato-admin/core';
3
+ import { Mode, applyMode } from '@cloudscape-design/global-styles';
4
+ export const ThemeManager = () => {
5
+ const [theme] = useStore('theme', 'light');
6
+ useEffect(() => {
7
+ applyMode(theme === 'dark' ? Mode.Dark : Mode.Light);
8
+ }, [theme]);
9
+ return null;
10
+ };
11
+ export default ThemeManager;
@@ -0,0 +1,2 @@
1
+ export { default as ThemeManager } from './ThemeManager';
2
+ export * from './ThemeManager';
@@ -0,0 +1,2 @@
1
+ export { default as ThemeManager } from './ThemeManager';
2
+ export * from './ThemeManager';
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@strato-admin/cloudscape",
3
+ "version": "0.1.0",
4
+ "description": "Strato Admin Cloudscape implementation - UI component library and theme for React Admin",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "keywords": [
23
+ "strato",
24
+ "admin",
25
+ "react-admin",
26
+ "cloudscape",
27
+ "aws",
28
+ "ui"
29
+ ],
30
+ "author": "Vadim Gubergrits <vadim.gubergrits@gmail.com>",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/vgrits/strato-admin.git",
35
+ "directory": "packages/strato-cloudscape"
36
+ },
37
+ "dependencies": {
38
+ "@cloudscape-design/collection-hooks": "^1.0.85",
39
+ "@cloudscape-design/components": "^3.0.1217",
40
+ "@cloudscape-design/global-styles": "^1.0.0",
41
+ "inflection": "^3.0.2",
42
+ "react-hook-form": "^7.71.2",
43
+ "react-router-dom": "^6.22.3",
44
+ "@strato-admin/core": "0.1.0",
45
+ "@strato-admin/i18n": "0.1.0",
46
+ "@strato-admin/language-en": "0.1.0"
47
+ },
48
+ "devDependencies": {
49
+ "@chromatic-com/storybook": "^5.0.1",
50
+ "@playwright/test": "^1.58.2",
51
+ "@storybook/addon-a11y": "^10.2.17",
52
+ "@storybook/addon-docs": "^10.2.17",
53
+ "@storybook/addon-onboarding": "^10.2.17",
54
+ "@storybook/addon-vitest": "^10.2.17",
55
+ "@storybook/react": "^10.2.17",
56
+ "@storybook/react-vite": "^10.2.17",
57
+ "@types/react": "^19.0.0",
58
+ "@types/react-dom": "^19.0.0",
59
+ "@vitest/browser-playwright": "^4.0.18",
60
+ "@vitest/coverage-v8": "^4.0.18",
61
+ "eslint-plugin-storybook": "^10.2.17",
62
+ "playwright": "^1.58.2",
63
+ "react": "^19.0.0",
64
+ "react-dom": "^19.0.0",
65
+ "storybook": "^10.2.17"
66
+ },
67
+ "scripts": {
68
+ "build": "tsc -p tsconfig.build.json",
69
+ "test": "vitest run",
70
+ "storybook": "storybook dev -p 6006",
71
+ "build-storybook": "storybook build"
72
+ }
73
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import { Admin } from './Admin';
5
+ import { Resource } from '@strato-admin/core';
6
+
7
+ // Mock Cloudscape TopNavigation
8
+ vi.mock('./layout/TopNavigation', () => ({
9
+ TopNavigation: () => <div data-testid="top-nav" />,
10
+ }));
11
+
12
+ describe('Admin', () => {
13
+ it('should render without crashing when no i18nProvider is provided', () => {
14
+ const dataProvider = {
15
+ getList: () => Promise.resolve({ data: [], total: 0 }),
16
+ getOne: () => Promise.resolve({ data: {} }),
17
+ getMany: () => Promise.resolve({ data: [] }),
18
+ getManyReference: () => Promise.resolve({ data: [], total: 0 }),
19
+ update: () => Promise.resolve({ data: {} }),
20
+ updateMany: () => Promise.resolve({ data: [] }),
21
+ create: () => Promise.resolve({ data: {} }),
22
+ delete: () => Promise.resolve({ data: {} }),
23
+ deleteMany: () => Promise.resolve({ data: [] }),
24
+ } as any;
25
+
26
+ render(
27
+ <Admin dataProvider={dataProvider}>
28
+ <Resource name="posts" list={() => <div>Posts List</div>} />
29
+ </Admin>,
30
+ );
31
+ });
32
+ });