@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,87 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
4
+ import { AppLayout } from './AppLayout';
5
+ import { useDefaultTitle, useResourceDefinitions } from '@strato-admin/core';
6
+ import { TopNavigation } from './TopNavigation';
7
+
8
+ // Mock strato-core
9
+ vi.mock('@strato-admin/core', () => import('../__mocks__/strato-core'));
10
+
11
+ // Mock global-styles
12
+ vi.mock('@cloudscape-design/global-styles', () => ({
13
+ Mode: { Light: 'light', Dark: 'dark' },
14
+ applyMode: vi.fn(),
15
+ }));
16
+
17
+ // Mock react-router-dom
18
+ vi.mock('react-router-dom', () => ({
19
+ useNavigate: vi.fn(),
20
+ }));
21
+
22
+ // Mock TopNavigation
23
+ vi.mock('./TopNavigation', () => ({
24
+ TopNavigation: vi.fn(() => <div data-testid="top-navigation" />),
25
+ }));
26
+
27
+ // Mock Cloudscape components
28
+ vi.mock('@cloudscape-design/components/app-layout', () => ({
29
+ default: ({ navigation, content }: any) => (
30
+ <div>
31
+ <div data-testid="navigation">{navigation}</div>
32
+ <div data-testid="content">{content}</div>
33
+ </div>
34
+ ),
35
+ }));
36
+
37
+ vi.mock('@cloudscape-design/components/side-navigation', () => ({
38
+ default: () => <div data-testid="side-navigation" />,
39
+ }));
40
+
41
+ describe('AppLayout', () => {
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ });
45
+
46
+ it('should use title from useDefaultTitle hook if no title prop provided', () => {
47
+ (useDefaultTitle as any).mockReturnValue('Hook Title');
48
+
49
+ render(
50
+ <AppLayout>
51
+ <div>Content</div>
52
+ </AppLayout>,
53
+ );
54
+
55
+ expect(vi.mocked(TopNavigation).mock.calls[0][0]).toMatchObject({
56
+ identity: { title: 'Hook Title' },
57
+ });
58
+ });
59
+
60
+ it('should prioritize title prop over useDefaultTitle hook', () => {
61
+ (useDefaultTitle as any).mockReturnValue('Hook Title');
62
+
63
+ render(
64
+ <AppLayout title="Prop Title">
65
+ <div>Content</div>
66
+ </AppLayout>,
67
+ );
68
+
69
+ expect(vi.mocked(TopNavigation).mock.calls[0][0]).toMatchObject({
70
+ identity: { title: 'Prop Title' },
71
+ });
72
+ });
73
+
74
+ it('should handle undefined default title', () => {
75
+ (useDefaultTitle as any).mockReturnValue(undefined);
76
+
77
+ render(
78
+ <AppLayout>
79
+ <div>Content</div>
80
+ </AppLayout>,
81
+ );
82
+
83
+ expect(vi.mocked(TopNavigation).mock.calls[0][0]).toMatchObject({
84
+ identity: { title: '' },
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,60 @@
1
+ import React, { useState } from 'react';
2
+ import CloudscapeAppLayout from '@cloudscape-design/components/app-layout';
3
+ import SideNavigation from '@cloudscape-design/components/side-navigation';
4
+ import { useResourceDefinitions, useDefaultTitle, useGetResourceLabel } from '@strato-admin/core';
5
+ import { useNavigate } from 'react-router-dom';
6
+ import { TopNavigation } from './TopNavigation';
7
+ import ThemeManager from '../theme/ThemeManager';
8
+
9
+ export interface AppLayoutProps {
10
+ children: React.ReactNode;
11
+ header?: React.ReactNode;
12
+ title?: string;
13
+ }
14
+
15
+ export const AppLayout = ({ children, header, title }: AppLayoutProps) => {
16
+ const resources = useResourceDefinitions();
17
+ const getResourceLabel = useGetResourceLabel();
18
+ const navigate = useNavigate();
19
+ const defaultTitle = useDefaultTitle();
20
+ const [navigationOpen, setNavigationOpen] = useState(true);
21
+
22
+ const appTitle =
23
+ title ?? (typeof defaultTitle === 'string' ? defaultTitle : '');
24
+
25
+ const items = Object.values(resources).map((resource) => ({
26
+ type: 'link' as const,
27
+ text: getResourceLabel(resource.name),
28
+ href: `/${resource.name}`,
29
+ }));
30
+
31
+ return (
32
+ <>
33
+ <ThemeManager />
34
+ {header || <TopNavigation identity={{ title: appTitle, href: '/' }} />}
35
+ <CloudscapeAppLayout
36
+ headerSelector="#header"
37
+ navigationOpen={navigationOpen}
38
+ onNavigationChange={({ detail }) => setNavigationOpen(detail.open)}
39
+ navigation={
40
+ <SideNavigation
41
+ //header={{
42
+ // href: '/',
43
+ // text: 'Dashboard',
44
+ //}}
45
+ items={items}
46
+ onFollow={(event) => {
47
+ if (!event.detail.external) {
48
+ event.preventDefault();
49
+ navigate(event.detail.href);
50
+ }
51
+ }}
52
+ />
53
+ }
54
+ content={<div>{children}</div>}
55
+ />
56
+ </>
57
+ );
58
+ };
59
+
60
+ export default AppLayout;
@@ -0,0 +1,78 @@
1
+
2
+ import { render } from '@testing-library/react';
3
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
4
+ import { useAuthProvider } from '@strato-admin/core';
5
+ import CloudscapeTopNavigation from '@cloudscape-design/components/top-navigation';
6
+ import TopNavigation from './TopNavigation';
7
+
8
+ // Mock ra-core
9
+ vi.mock('@strato-admin/core', () => ({
10
+ useLocale: vi.fn(() => 'en'),
11
+ useSetLocale: vi.fn(),
12
+ useLocales: vi.fn(() => []),
13
+ useTranslate: vi.fn(() => (key: string, options: any) => options?._ || key),
14
+ useAuthProvider: vi.fn(),
15
+ useStore: vi.fn(() => ['light', vi.fn()]),
16
+ }));
17
+
18
+ // Mock Cloudscape components
19
+ vi.mock('@cloudscape-design/components/top-navigation', () => ({
20
+ default: vi.fn(() => <div data-testid="top-navigation" />),
21
+ }));
22
+
23
+ // Mock global-styles
24
+ vi.mock('@cloudscape-design/global-styles', () => ({
25
+ Mode: { Light: 'light', Dark: 'dark' },
26
+ applyMode: vi.fn(),
27
+ }));
28
+
29
+ describe('TopNavigation', () => {
30
+ beforeEach(() => {
31
+ vi.clearAllMocks();
32
+ });
33
+
34
+ it('should display theme toggle', () => {
35
+ render(<TopNavigation />);
36
+
37
+ const navigationProps = (CloudscapeTopNavigation as any).mock.calls[0][0];
38
+ const themeToggle = navigationProps.utilities.find((u: any) => u.iconSvg !== undefined);
39
+ expect(themeToggle).toBeDefined();
40
+ expect(themeToggle.type).toBe('button');
41
+ });
42
+
43
+ it('should not display user menu when authProvider is missing', () => {
44
+ (useAuthProvider as any).mockReturnValue(undefined);
45
+
46
+ render(<TopNavigation />);
47
+
48
+ const navigationProps = (CloudscapeTopNavigation as any).mock.calls[0][0];
49
+ const userMenu = navigationProps.utilities.find((u: any) => u.iconName === 'user-profile');
50
+ expect(userMenu).toBeUndefined();
51
+ });
52
+
53
+ it('should display user menu when authProvider is present', () => {
54
+ (useAuthProvider as any).mockReturnValue({});
55
+
56
+ render(<TopNavigation />);
57
+
58
+ const navigationProps = (CloudscapeTopNavigation as any).mock.calls[0][0];
59
+ const userMenu = navigationProps.utilities.find((u: any) => u.iconName === 'user-profile');
60
+ expect(userMenu).toBeDefined();
61
+ expect(userMenu.text).toBe('User'); // Based on mock translate returning default value
62
+ });
63
+
64
+ it('should use provided identity', () => {
65
+ render(<TopNavigation identity={{ title: 'Custom Title', href: '/custom' }} />);
66
+
67
+ const navigationProps = (CloudscapeTopNavigation as any).mock.calls[0][0];
68
+ expect(navigationProps.identity).toEqual({ title: 'Custom Title', href: '/custom' });
69
+ });
70
+
71
+ it('should use provided utilities', () => {
72
+ const customUtilities = [{ type: 'button' as const, text: 'Custom' }];
73
+ render(<TopNavigation utilities={customUtilities} />);
74
+
75
+ const navigationProps = (CloudscapeTopNavigation as any).mock.calls[0][0];
76
+ expect(navigationProps.utilities).toEqual(customUtilities);
77
+ });
78
+ });
@@ -0,0 +1,84 @@
1
+ import CloudscapeTopNavigation, { TopNavigationProps } from '@cloudscape-design/components/top-navigation';
2
+ import { useLocale, useSetLocale, useLocales, useTranslate, useAuthProvider, useStore } from '@strato-admin/core';
3
+
4
+ export interface MyTopNavigationProps extends Omit<TopNavigationProps, 'identity'> {
5
+ identity?: TopNavigationProps.Identity;
6
+ }
7
+
8
+ const LightModeIcon = (
9
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" focusable="false">
10
+ <path d="M8 1.5v13a6.5 6.5 0 0 0 0-13z" fill="currentColor" />
11
+ <circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.5" />
12
+ </svg>
13
+ );
14
+
15
+ const DarkModeIcon = (
16
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" focusable="false">
17
+ <path d="M8 1.5v13a6.5 6.5 0 0 1 0-13z" fill="currentColor" />
18
+ <circle cx="8" cy="8" r="7" fill="none" stroke="currentColor" stroke-width="1.5" />
19
+ </svg>
20
+ );
21
+
22
+ export const TopNavigation = ({ utilities: providedUtilities, identity, ...props }: MyTopNavigationProps) => {
23
+ const locale = useLocale();
24
+ const setLocale = useSetLocale();
25
+ const locales = useLocales();
26
+ const translate = useTranslate();
27
+ const authProvider = useAuthProvider();
28
+ const [theme, setTheme] = useStore('theme', 'light');
29
+
30
+ let utilities = providedUtilities;
31
+
32
+ if (!utilities) {
33
+ const autoUtilities: any[] = [];
34
+
35
+ autoUtilities.push({
36
+ type: 'button',
37
+ iconSvg: theme === 'light' ? LightModeIcon : DarkModeIcon,
38
+ onClick: () => {
39
+ setTheme(theme === 'dark' ? 'light' : 'dark');
40
+ },
41
+ ariaLabel: translate('strato.action.toggle_theme', { _: 'Toggle theme' }),
42
+ });
43
+
44
+ if (locales && locales.length > 1) {
45
+ autoUtilities.push({
46
+ type: 'menu-dropdown',
47
+ text: locales.find((l: any) => l.locale === locale)?.name || locale,
48
+ iconName: 'globe',
49
+ onItemClick: (event: any) => {
50
+ setLocale(event.detail.id);
51
+ },
52
+ items: locales.map((l: any) => ({
53
+ id: l.locale,
54
+ text: l.name,
55
+ })),
56
+ });
57
+ }
58
+
59
+ if (authProvider) {
60
+ autoUtilities.push({
61
+ type: 'menu-dropdown',
62
+ text: translate('ra.auth.user_menu', { _: 'User' }),
63
+ iconName: 'user-profile',
64
+ items: [
65
+ { id: 'profile', text: translate('ra.auth.profile', { _: 'Profile' }) },
66
+ { id: 'signout', text: translate('ra.auth.logout', { _: 'Sign out' }) },
67
+ ],
68
+ });
69
+ }
70
+ utilities = autoUtilities;
71
+ }
72
+
73
+ return (
74
+ <div id="header">
75
+ <CloudscapeTopNavigation
76
+ identity={identity || { title: 'Strato Admin', href: '/' }}
77
+ utilities={utilities}
78
+ {...props}
79
+ />
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default TopNavigation;
@@ -0,0 +1,2 @@
1
+ export * from './AppLayout';
2
+ export * from './TopNavigation';
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import CloudscapeCards, { CardsProps } from '@cloudscape-design/components/cards';
3
+ import Pagination from '@cloudscape-design/components/pagination';
4
+ import { RaRecord, RecordContextProvider, useFieldSchema } from '@strato-admin/core';
5
+ import { useCollection } from '../collection-hooks';
6
+ import KeyValuePairs from '../detail/KeyValuePairs';
7
+
8
+ export interface ListCardsProps<T extends RaRecord = any> extends Omit<CardsProps<T>, 'items' | 'cardDefinition'> {
9
+ renderItem?: (item: T) => React.ReactNode;
10
+ include?: string[];
11
+ exclude?: string[];
12
+ }
13
+
14
+ export const ListCards = <T extends RaRecord = any>({
15
+ renderItem,
16
+ include,
17
+ exclude,
18
+ ...props
19
+ }: ListCardsProps<T>) => {
20
+ const { items, paginationProps, collectionProps } = useCollection<T>({
21
+ filtering: {},
22
+ pagination: {},
23
+ sorting: {},
24
+ });
25
+
26
+ const schemaChildren = useFieldSchema();
27
+
28
+ const defaultRenderItem = (_item: T) => (
29
+ <KeyValuePairs include={include} exclude={exclude}>
30
+ {schemaChildren}
31
+ </KeyValuePairs>
32
+ );
33
+
34
+ const finalRenderItem = renderItem || defaultRenderItem;
35
+
36
+ const cardDefinition: CardsProps.CardDefinition<T> = {
37
+ sections: [
38
+ {
39
+ id: 'main',
40
+ content: (item: T) => <RecordContextProvider value={item}>{finalRenderItem(item) as any}</RecordContextProvider>,
41
+ },
42
+ ],
43
+ };
44
+
45
+ return (
46
+ <CloudscapeCards
47
+ {...collectionProps}
48
+ {...props}
49
+ items={items || []}
50
+ cardDefinition={cardDefinition}
51
+ pagination={<Pagination {...paginationProps} />}
52
+ />
53
+ );
54
+ };
55
+
56
+ export const Cards = ListCards;
57
+
58
+ export default ListCards;
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import { ListBase, type RaRecord, ResourceSchemaProvider } from '@strato-admin/core';
3
+ import Table from './Table';
4
+
5
+ export interface ListProps<_RecordType extends RaRecord = any> {
6
+ children?: React.ReactNode;
7
+ fieldSchema?: React.ReactNode;
8
+ include?: string[];
9
+ exclude?: string[];
10
+ title?: React.ReactNode;
11
+ actions?: React.ReactNode;
12
+ /**
13
+ * Whether to enable text filtering in the implicit Table.
14
+ * @default true
15
+ */
16
+ filtering?: boolean;
17
+ /**
18
+ * Whether to show the preferences button in the implicit Table.
19
+ * @default true
20
+ */
21
+ preferences?: boolean | React.ReactNode;
22
+ [key: string]: any;
23
+ }
24
+
25
+ /**
26
+ * A List component that provides a list context and a Cloudscape Table.
27
+ *
28
+ * @example
29
+ * <List>
30
+ * <Table>
31
+ * <Table.Column source="name" />
32
+ * </Table>
33
+ * </List>
34
+ *
35
+ * @example
36
+ * // Using FieldSchema from context
37
+ * <List include={['name', 'price']} />
38
+ *
39
+ * @example
40
+ * // Passing a custom field schema
41
+ * <List fieldSchema={<FieldSchema>...</FieldSchema>}>
42
+ * <Table />
43
+ * </List>
44
+ */
45
+ export const List = <RecordType extends RaRecord = any>({
46
+ children,
47
+ fieldSchema,
48
+ include,
49
+ exclude,
50
+ title,
51
+ actions,
52
+ filtering = true,
53
+ preferences = true,
54
+ ...props
55
+ }: ListProps<RecordType>) => {
56
+ const finalChildren = children || (
57
+ <Table
58
+ include={include}
59
+ exclude={exclude}
60
+ title={title}
61
+ actions={actions}
62
+ filtering={filtering}
63
+ preferences={preferences}
64
+ />
65
+ );
66
+
67
+ return (
68
+ <ListBase {...props}>
69
+ <ResourceSchemaProvider resource={props.resource} fieldSchema={fieldSchema}>
70
+ {finalChildren as any}
71
+ </ResourceSchemaProvider>
72
+ </ListBase>
73
+ );
74
+ };
75
+
76
+ export default List;
@@ -0,0 +1,11 @@
1
+ import { Table, Column, NumberColumn, DateColumn } from './Table';
2
+
3
+ export const ProductList = () => (
4
+ <Table title="Product Catalog">
5
+ <Column source="name" label="Product Name" />
6
+ <Column source="category" label="Category" />
7
+ <NumberColumn source="price" label="Price" />
8
+ <NumberColumn source="stock" label="Inventory" />
9
+ <DateColumn source="lastUpdated" label="Last Updated" />
10
+ </Table>
11
+ );
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Table, Column, NumberColumn, DateColumn } from './Table';
4
+ import { ResourceContext, CoreAdminContext, ListContextProvider } from '@strato-admin/core';
5
+ import { ProductList } from './Table.examples';
6
+
7
+ const meta: Meta<typeof Table> = {
8
+ title: 'Components/Table',
9
+ component: Table,
10
+ tags: ['autodocs'],
11
+ decorators: [
12
+ (Story: React.ComponentType) => (
13
+ <CoreAdminContext dataProvider={{} as any}>
14
+ <ResourceContext.Provider value="products">
15
+ <Story />
16
+ </ResourceContext.Provider>
17
+ </CoreAdminContext>
18
+ ),
19
+ ],
20
+ };
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof Table>;
24
+
25
+ const items = [
26
+ { id: 1, name: 'Wireless Mouse', category: 'Accessories', price: 25.99, stock: 150, lastUpdated: '2024-03-10' },
27
+ { id: 2, name: 'Mechanical Keyboard', category: 'Accessories', price: 89.99, stock: 45, lastUpdated: '2024-03-08' },
28
+ { id: 3, name: '27-inch Monitor', category: 'Hardware', price: 299.99, stock: 20, lastUpdated: '2024-03-05' },
29
+ { id: 4, name: 'USB-C Cable', category: 'Accessories', price: 12.5, stock: 500, lastUpdated: '2024-03-11' },
30
+ { id: 5, name: 'Laptop Pro 14', category: 'Hardware', price: 1499.0, stock: 10, lastUpdated: '2024-03-01' },
31
+ ];
32
+
33
+ const listContext = {
34
+ data: items,
35
+ total: items.length,
36
+ isPending: false,
37
+ isFetching: false,
38
+ isLoading: false,
39
+ page: 1,
40
+ perPage: 10,
41
+ setPage: () => {},
42
+ setPerPage: () => {},
43
+ sort: { field: 'name', order: 'ASC' },
44
+ setSort: () => {},
45
+ filterValues: {},
46
+ setFilters: () => {},
47
+ selectedIds: [],
48
+ onSelect: () => {},
49
+ onToggleItem: () => {},
50
+ onUnselectItems: () => {},
51
+ resource: 'products',
52
+ } as any;
53
+
54
+ export const Basic: Story = {
55
+ render: () => (
56
+ <ListContextProvider value={listContext}>
57
+ <ProductList />
58
+ </ListContextProvider>
59
+ ),
60
+ };
61
+
62
+ export const WithFiltering: Story = {
63
+ render: () => (
64
+ <ListContextProvider value={listContext}>
65
+ <Table title="Inventory Management" filtering preferences>
66
+ <Column source="name" label="Product Name" />
67
+ <Column source="category" label="Category" />
68
+ <NumberColumn source="price" label="Price" />
69
+ <NumberColumn source="stock" label="Inventory" />
70
+ </Table>
71
+ </ListContextProvider>
72
+ ),
73
+ };