@strato-admin/cloudscape 0.1.0 → 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.
- package/dist/Admin.d.ts +6 -2
- package/dist/Admin.js +14 -8
- package/dist/RecordLink.js +5 -4
- package/dist/Settings.d.ts +17 -0
- package/dist/Settings.js +14 -0
- package/dist/button/BulkDeleteButton.d.ts +4 -1
- package/dist/button/BulkDeleteButton.js +37 -5
- package/dist/button/Button.d.ts +2 -1
- package/dist/button/CancelButton.d.ts +6 -0
- package/dist/button/CancelButton.js +10 -0
- package/dist/button/CreateButton.js +9 -8
- package/dist/button/DeleteButton.d.ts +13 -0
- package/dist/button/DeleteButton.js +36 -0
- package/dist/button/EditButton.d.ts +1 -1
- package/dist/button/EditButton.js +10 -10
- package/dist/button/SaveButton.js +2 -2
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.js +2 -0
- package/dist/collection-hooks/interfaces.d.ts +7 -3
- package/dist/collection-hooks/useCollection.d.ts +1 -1
- package/dist/collection-hooks/useCollection.js +15 -10
- package/dist/create/Create.d.ts +9 -17
- package/dist/create/Create.js +40 -12
- package/dist/create/CreateHeader.d.ts +2 -2
- package/dist/create/CreateHeader.js +4 -5
- package/dist/defaults.d.ts +6 -0
- package/dist/defaults.js +21 -0
- package/dist/detail/Detail.d.ts +33 -0
- package/dist/detail/Detail.js +22 -0
- package/dist/detail/DetailHeader.d.ts +11 -0
- package/dist/detail/{ShowHeader.js → DetailHeader.js} +7 -5
- package/dist/detail/DetailHub.d.ts +27 -0
- package/dist/detail/DetailHub.js +63 -0
- package/dist/detail/KeyValuePairs.d.ts +7 -1
- package/dist/detail/KeyValuePairs.js +14 -8
- package/dist/detail/index.d.ts +3 -2
- package/dist/detail/index.js +3 -2
- package/dist/edit/Edit.d.ts +8 -19
- package/dist/edit/Edit.js +48 -12
- package/dist/edit/EditHeader.d.ts +2 -2
- package/dist/edit/EditHeader.js +5 -4
- package/dist/field/ArrayField.d.ts +26 -10
- package/dist/field/ArrayField.js +38 -10
- package/dist/field/BadgeField.d.ts +1 -1
- package/dist/field/BadgeField.js +1 -1
- package/dist/field/BooleanField.d.ts +1 -1
- package/dist/field/BooleanField.js +2 -2
- package/dist/field/CurrencyField.d.ts +1 -1
- package/dist/field/CurrencyField.js +1 -1
- package/dist/field/DateField.d.ts +1 -1
- package/dist/field/DateField.js +1 -1
- package/dist/field/IdField.d.ts +1 -1
- package/dist/field/IdField.js +3 -3
- package/dist/field/NumberField.d.ts +1 -1
- package/dist/field/NumberField.js +1 -1
- package/dist/field/ReferenceField.d.ts +1 -1
- package/dist/field/ReferenceField.js +4 -2
- package/dist/field/ReferenceManyField.d.ts +35 -4
- package/dist/field/ReferenceManyField.js +17 -4
- package/dist/field/StatusIndicatorField.d.ts +1 -1
- package/dist/field/StatusIndicatorField.js +6 -5
- package/dist/field/TextField.d.ts +1 -1
- package/dist/field/TextField.js +1 -1
- package/dist/field/types.d.ts +9 -9
- package/dist/form/Form.d.ts +12 -2
- package/dist/form/Form.js +10 -16
- package/dist/form/index.d.ts +1 -1
- package/dist/form/index.js +1 -1
- package/dist/hooks/useSchemaFields.d.ts +22 -0
- package/dist/hooks/useSchemaFields.js +45 -0
- package/dist/i18n/Message.d.ts +15 -0
- package/dist/i18n/Message.js +19 -0
- package/dist/i18n/RecordMessage.d.ts +14 -0
- package/dist/i18n/RecordMessage.js +16 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/types.d.ts +19 -0
- package/dist/i18n/types.js +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/input/ArrayInput.d.ts +33 -0
- package/dist/input/{AttributeEditor.js → ArrayInput.js} +18 -11
- package/dist/input/AutocompleteInput.d.ts +1 -1
- package/dist/input/AutocompleteInput.js +3 -3
- package/dist/input/BooleanInput.d.ts +6 -0
- package/dist/input/BooleanInput.js +23 -0
- package/dist/input/CommonInputProps.d.ts +6 -0
- package/dist/input/CommonInputProps.js +6 -0
- package/dist/input/FieldTitle.js +4 -4
- package/dist/input/FormField.js +12 -3
- package/dist/input/FormFieldContext.d.ts +1 -1
- package/dist/input/NumberInput.d.ts +1 -1
- package/dist/input/NumberInput.js +3 -3
- package/dist/input/ReferenceInput.d.ts +1 -1
- package/dist/input/ReferenceInput.js +22 -12
- package/dist/input/SelectInput.d.ts +1 -1
- package/dist/input/SelectInput.js +3 -3
- package/dist/input/SliderInput.d.ts +1 -1
- package/dist/input/SliderInput.js +4 -4
- package/dist/input/TextAreaInput.d.ts +1 -1
- package/dist/input/TextAreaInput.js +3 -3
- package/dist/input/TextInput.d.ts +1 -1
- package/dist/input/TextInput.js +6 -12
- package/dist/input/index.d.ts +2 -1
- package/dist/input/index.js +2 -1
- package/dist/input/types.d.ts +33 -2
- package/dist/layout/AppLayout.js +6 -3
- package/dist/layout/Notifications.d.ts +1 -0
- package/dist/layout/Notifications.js +51 -0
- package/dist/layout/Ready.d.ts +6 -0
- package/dist/layout/Ready.js +24 -0
- package/dist/layout/TopNavigation.d.ts +4 -2
- package/dist/layout/TopNavigation.js +7 -7
- package/dist/layout/index.d.ts +2 -0
- package/dist/layout/index.js +2 -0
- package/dist/list/Cards.d.ts +31 -4
- package/dist/list/Cards.js +81 -10
- package/dist/list/List.d.ts +9 -12
- package/dist/list/List.js +41 -11
- package/dist/list/Table.d.ts +8 -4
- package/dist/list/Table.js +55 -55
- package/dist/list/TableHeader.d.ts +2 -2
- package/dist/list/TableHeader.js +4 -5
- package/dist/theme/ThemeManager.js +1 -1
- package/package.json +9 -6
- package/src/Admin.tsx +35 -18
- package/src/RecordLink.stories.tsx +1 -1
- package/src/RecordLink.tsx +5 -4
- package/src/Settings.tsx +16 -0
- package/src/__mocks__/ra-core.tsx +83 -0
- package/src/__mocks__/strato-core.tsx +36 -42
- package/src/button/BulkDeleteButton.test.tsx +45 -8
- package/src/button/BulkDeleteButton.tsx +75 -12
- package/src/button/Button.tsx +31 -2
- package/src/button/CancelButton.tsx +20 -0
- package/src/button/CreateButton.tsx +12 -10
- package/src/button/DeleteButton.tsx +96 -0
- package/src/button/EditButton.tsx +13 -12
- package/src/button/SaveButton.tsx +2 -3
- package/src/button/index.ts +2 -0
- package/src/collection-hooks/interfaces.ts +7 -3
- package/src/collection-hooks/useCollection.test.ts +115 -2
- package/src/collection-hooks/useCollection.ts +15 -10
- package/src/create/Create.test.tsx +3 -3
- package/src/create/Create.tsx +68 -37
- package/src/create/CreateHeader.tsx +6 -10
- package/src/defaults.tsx +28 -0
- package/src/detail/Detail-CollectionFields.test.tsx +84 -0
- package/src/detail/Detail.test.tsx +91 -0
- package/src/detail/Detail.tsx +48 -0
- package/src/detail/{ShowHeader.test.tsx → DetailHeader.test.tsx} +11 -9
- package/src/detail/DetailHeader.tsx +42 -0
- package/src/detail/DetailHub.tsx +88 -0
- package/src/detail/KeyValuePairs.test.tsx +2 -2
- package/src/detail/KeyValuePairs.tsx +25 -18
- package/src/detail/index.ts +3 -2
- package/src/edit/Edit.test.tsx +7 -5
- package/src/edit/Edit.tsx +92 -40
- package/src/edit/EditHeader.tsx +7 -5
- package/src/field/ArrayField.tsx +57 -11
- package/src/field/BadgeField.tsx +2 -3
- package/src/field/BooleanField.test.tsx +2 -3
- package/src/field/BooleanField.tsx +3 -3
- package/src/field/CurrencyField.tsx +1 -1
- package/src/field/DateField.tsx +1 -1
- package/src/field/IdField.test.tsx +8 -20
- package/src/field/IdField.tsx +5 -20
- package/src/field/NumberField.tsx +1 -1
- package/src/field/ReferenceField.test.tsx +15 -6
- package/src/field/ReferenceField.tsx +10 -7
- package/src/field/ReferenceManyField.test.tsx +55 -10
- package/src/field/ReferenceManyField.tsx +84 -13
- package/src/field/StatusIndicatorField.test.tsx +7 -21
- package/src/field/StatusIndicatorField.tsx +8 -20
- package/src/field/TextField.tsx +1 -1
- package/src/field/types.ts +12 -13
- package/src/form/Form.test.tsx +8 -4
- package/src/form/Form.tsx +24 -19
- package/src/form/index.ts +1 -1
- package/src/hooks/useSchemaFields.ts +89 -0
- package/src/i18n/Message.tsx +22 -0
- package/src/i18n/RecordMessage.tsx +22 -0
- package/src/i18n/index.ts +3 -0
- package/src/i18n/types.ts +19 -0
- package/src/index.ts +5 -1
- package/src/input/ArrayInput.test.tsx +81 -0
- package/src/input/{AttributeEditor.tsx → ArrayInput.tsx} +36 -18
- package/src/input/AutocompleteInput.test.tsx +2 -4
- package/src/input/AutocompleteInput.tsx +9 -11
- package/src/input/BooleanInput.tsx +42 -0
- package/src/input/CommonInputProps.tsx +8 -0
- package/src/input/FieldTitle.tsx +3 -15
- package/src/input/FormField.tsx +78 -67
- package/src/input/FormFieldContext.ts +1 -1
- package/src/input/NumberInput.tsx +10 -7
- package/src/input/ReferenceInput.test.tsx +12 -2
- package/src/input/ReferenceInput.tsx +32 -14
- package/src/input/SelectInput.tsx +14 -17
- package/src/input/SliderInput.test.tsx +2 -3
- package/src/input/SliderInput.tsx +48 -38
- package/src/input/TextAreaInput.tsx +10 -6
- package/src/input/TextInput.test.tsx +2 -4
- package/src/input/TextInput.tsx +35 -20
- package/src/input/index.ts +2 -1
- package/src/input/types.ts +40 -8
- package/src/layout/AppLayout.test.tsx +23 -3
- package/src/layout/AppLayout.tsx +11 -8
- package/src/layout/Notifications.test.tsx +102 -0
- package/src/layout/Notifications.tsx +61 -0
- package/src/layout/Ready.tsx +123 -0
- package/src/layout/TopNavigation.test.tsx +2 -3
- package/src/layout/TopNavigation.tsx +9 -8
- package/src/layout/index.ts +2 -0
- package/src/list/Cards.test.tsx +320 -0
- package/src/list/Cards.tsx +146 -16
- package/src/list/List.tsx +87 -26
- package/src/list/Table.test.tsx +40 -5
- package/src/list/Table.tsx +89 -98
- package/src/list/TableHeader.test.tsx +15 -11
- package/src/list/TableHeader.tsx +6 -8
- package/src/theme/ThemeManager.tsx +1 -1
- package/dist/__mocks__/strato-core.js +0 -50
- package/dist/__mocks__to__delete/strato-core.js +0 -50
- package/dist/detail/Show.d.ts +0 -39
- package/dist/detail/Show.js +0 -40
- package/dist/detail/ShowHeader.d.ts +0 -7
- package/dist/input/AttributeEditor.d.ts +0 -25
- package/src/detail/Show.test.tsx +0 -96
- package/src/detail/Show.tsx +0 -104
- package/src/detail/ShowHeader.tsx +0 -35
- package/src/input/AttributeEditor.test.tsx +0 -147
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import Flashbar, { FlashbarProps } from '@cloudscape-design/components/flashbar';
|
|
3
|
+
import { useNotificationContext, useTranslate, NotificationPayload } from '@strato-admin/ra-core';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_AUTO_HIDE_MS = null;
|
|
6
|
+
|
|
7
|
+
let idCounter = 0;
|
|
8
|
+
const stableIds = new WeakMap<object, string>();
|
|
9
|
+
const getStableId = (obj: object): string => {
|
|
10
|
+
if (!stableIds.has(obj)) stableIds.set(obj, String(++idCounter));
|
|
11
|
+
return stableIds.get(obj)!;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const Notifications = () => {
|
|
15
|
+
const { notifications, setNotifications } = useNotificationContext();
|
|
16
|
+
const translate = useTranslate();
|
|
17
|
+
const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
|
18
|
+
|
|
19
|
+
const dismiss = useCallback(
|
|
20
|
+
(notification: NotificationPayload) => {
|
|
21
|
+
const id = getStableId(notification);
|
|
22
|
+
clearTimeout(timers.current.get(id));
|
|
23
|
+
timers.current.delete(id);
|
|
24
|
+
setNotifications((prev: NotificationPayload[]) => prev.filter((n) => n !== notification));
|
|
25
|
+
},
|
|
26
|
+
[setNotifications],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
notifications.forEach((notification) => {
|
|
31
|
+
const id = getStableId(notification);
|
|
32
|
+
if (timers.current.has(id)) return;
|
|
33
|
+
const ms = notification.notificationOptions?.autoHideDuration ?? DEFAULT_AUTO_HIDE_MS;
|
|
34
|
+
if (ms === null) return;
|
|
35
|
+
timers.current.set(
|
|
36
|
+
id,
|
|
37
|
+
setTimeout(() => dismiss(notification), ms),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
timers.current.forEach((timer) => clearTimeout(timer));
|
|
43
|
+
timers.current.clear();
|
|
44
|
+
};
|
|
45
|
+
}, [notifications, dismiss]);
|
|
46
|
+
|
|
47
|
+
if (notifications.length === 0) return null;
|
|
48
|
+
|
|
49
|
+
const items: FlashbarProps.MessageDefinition[] = notifications.map((notification) => {
|
|
50
|
+
const { message, type, notificationOptions } = notification;
|
|
51
|
+
return {
|
|
52
|
+
id: getStableId(notification),
|
|
53
|
+
type,
|
|
54
|
+
content: typeof message === 'string' ? translate(message, notificationOptions?.messageArgs) : message,
|
|
55
|
+
dismissible: true,
|
|
56
|
+
onDismiss: () => dismiss(notification),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return <Flashbar items={items} />;
|
|
61
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
Container,
|
|
5
|
+
Header,
|
|
6
|
+
SpaceBetween,
|
|
7
|
+
Link,
|
|
8
|
+
Icon,
|
|
9
|
+
ContentLayout,
|
|
10
|
+
ColumnLayout,
|
|
11
|
+
} from '@cloudscape-design/components';
|
|
12
|
+
import { CodeView } from '@cloudscape-design/code-view';
|
|
13
|
+
import typescriptHighlight from '@cloudscape-design/code-view/highlight/typescript';
|
|
14
|
+
import { useDefaultTitle } from '@strato-admin/ra-core';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default component to display when the admin has no resources.
|
|
18
|
+
* This is a Cloudscape-themed replacement for the default react-admin Ready component.
|
|
19
|
+
*/
|
|
20
|
+
export const Ready = () => {
|
|
21
|
+
const defaultTitle = useDefaultTitle();
|
|
22
|
+
const appTitle = typeof defaultTitle === 'string' ? defaultTitle : 'Strato Admin';
|
|
23
|
+
|
|
24
|
+
const codeSnippet = `import { Admin, ResourceSchema, TextField } from '@strato-admin/admin';
|
|
25
|
+
|
|
26
|
+
export const App = () => (
|
|
27
|
+
<Admin dataProvider={...}>
|
|
28
|
+
<ResourceSchema name="posts" >
|
|
29
|
+
<TextField source="id" />
|
|
30
|
+
</ResourceSchema>
|
|
31
|
+
</Admin>
|
|
32
|
+
);`;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ContentLayout
|
|
36
|
+
maxContentWidth={1040}
|
|
37
|
+
headerVariant="high-contrast"
|
|
38
|
+
header={
|
|
39
|
+
<Box padding={{ vertical: 'xxxl' }} textAlign="center">
|
|
40
|
+
<SpaceBetween size="l">
|
|
41
|
+
<Header variant="h1" description="Your Strato Admin application is properly configured and ready to go.">
|
|
42
|
+
Welcome to {appTitle}
|
|
43
|
+
</Header>
|
|
44
|
+
<Box variant="p">
|
|
45
|
+
Strato Admin uses a <strong>Schema First</strong> approach to build admin interfaces efficiently. Start by
|
|
46
|
+
defining your resources and let us handle the rest.
|
|
47
|
+
</Box>
|
|
48
|
+
<Box margin={{ top: 'm' }}>
|
|
49
|
+
<SpaceBetween direction="horizontal" size="xs">
|
|
50
|
+
<Button
|
|
51
|
+
variant="primary"
|
|
52
|
+
href="https://strato-admin.dev/docs/getting-started/tutorial"
|
|
53
|
+
iconName="external"
|
|
54
|
+
iconAlign="right"
|
|
55
|
+
>
|
|
56
|
+
Get started with the tutorial
|
|
57
|
+
</Button>
|
|
58
|
+
<Button href="https://github.com/vadimgu/strato-admin" iconName="external" iconAlign="right">
|
|
59
|
+
View on GitHub
|
|
60
|
+
</Button>
|
|
61
|
+
</SpaceBetween>
|
|
62
|
+
</Box>
|
|
63
|
+
</SpaceBetween>
|
|
64
|
+
</Box>
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
<SpaceBetween size="xxl">
|
|
68
|
+
{/* Features / Details Section */}
|
|
69
|
+
<SpaceBetween size="l">
|
|
70
|
+
<Container header={<Header variant="h2">How to add your first resource</Header>}>
|
|
71
|
+
<SpaceBetween size="m">
|
|
72
|
+
<Box variant="p">
|
|
73
|
+
Add a <code><ResourceSchema></code> component as a child of your <code><Admin></code> to
|
|
74
|
+
define your data model.
|
|
75
|
+
</Box>
|
|
76
|
+
<CodeView content={codeSnippet} highlight={typescriptHighlight} />
|
|
77
|
+
</SpaceBetween>
|
|
78
|
+
</Container>
|
|
79
|
+
|
|
80
|
+
<Container header={<Header variant="h2">Next steps</Header>}>
|
|
81
|
+
<ColumnLayout columns={2} variant="text-grid">
|
|
82
|
+
<div>
|
|
83
|
+
<Box variant="h3" fontWeight="bold">
|
|
84
|
+
<Icon name="status-info" variant="link" /> Core Concepts
|
|
85
|
+
</Box>
|
|
86
|
+
<Box variant="p">
|
|
87
|
+
Learn how{' '}
|
|
88
|
+
<Link href="https://strato-admin.dev/docs/core-concepts/schema" external>
|
|
89
|
+
Schemas
|
|
90
|
+
</Link>{' '}
|
|
91
|
+
work to automate your UI.
|
|
92
|
+
</Box>
|
|
93
|
+
</div>
|
|
94
|
+
<div>
|
|
95
|
+
<Box variant="h3" fontWeight="bold">
|
|
96
|
+
<Icon name="star" variant="warning" /> Design System
|
|
97
|
+
</Box>
|
|
98
|
+
<Box variant="p">
|
|
99
|
+
Explore the{' '}
|
|
100
|
+
<Link href="https://cloudscape.design/" external>
|
|
101
|
+
Cloudscape design system
|
|
102
|
+
</Link>{' '}
|
|
103
|
+
components.
|
|
104
|
+
</Box>
|
|
105
|
+
</div>
|
|
106
|
+
</ColumnLayout>
|
|
107
|
+
</Container>
|
|
108
|
+
</SpaceBetween>
|
|
109
|
+
|
|
110
|
+
{/* Footer */}
|
|
111
|
+
<Box textAlign="center" color="text-label" margin={{ top: 'xs' }} padding={{ bottom: 'xxxl' }}>
|
|
112
|
+
Built with{' '}
|
|
113
|
+
<Link href="https://www.strato-admin.dev" external>
|
|
114
|
+
Strato Admin
|
|
115
|
+
</Link>
|
|
116
|
+
.
|
|
117
|
+
</Box>
|
|
118
|
+
</SpaceBetween>
|
|
119
|
+
</ContentLayout>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default Ready;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
2
1
|
import { render } from '@testing-library/react';
|
|
3
2
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
-
import { useAuthProvider } from '@strato-admin/core';
|
|
3
|
+
import { useAuthProvider } from '@strato-admin/ra-core';
|
|
5
4
|
import CloudscapeTopNavigation from '@cloudscape-design/components/top-navigation';
|
|
6
5
|
import TopNavigation from './TopNavigation';
|
|
7
6
|
|
|
8
7
|
// Mock ra-core
|
|
9
|
-
vi.mock('@strato-admin/core', () => ({
|
|
8
|
+
vi.mock('@strato-admin/ra-core', () => ({
|
|
10
9
|
useLocale: vi.fn(() => 'en'),
|
|
11
10
|
useSetLocale: vi.fn(),
|
|
12
11
|
useLocales: vi.fn(() => []),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import CloudscapeTopNavigation, { TopNavigationProps } from '@cloudscape-design/components/top-navigation';
|
|
2
|
-
import { useLocale, useSetLocale, useLocales, useTranslate, useAuthProvider, useStore } from '@strato-admin/core';
|
|
2
|
+
import { useLocale, useSetLocale, useLocales, useTranslate, useAuthProvider, useStore } from '@strato-admin/ra-core';
|
|
3
3
|
|
|
4
|
-
export interface MyTopNavigationProps
|
|
4
|
+
export interface MyTopNavigationProps {
|
|
5
5
|
identity?: TopNavigationProps.Identity;
|
|
6
|
+
utilities?: ReadonlyArray<TopNavigationProps.Utility>;
|
|
7
|
+
extraUtilities?: ReadonlyArray<TopNavigationProps.Utility>;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
const LightModeIcon = (
|
|
@@ -19,7 +21,7 @@ const DarkModeIcon = (
|
|
|
19
21
|
</svg>
|
|
20
22
|
);
|
|
21
23
|
|
|
22
|
-
export const TopNavigation = ({ utilities: providedUtilities, identity,
|
|
24
|
+
export const TopNavigation = ({ utilities: providedUtilities, identity, extraUtilities }: MyTopNavigationProps) => {
|
|
23
25
|
const locale = useLocale();
|
|
24
26
|
const setLocale = useSetLocale();
|
|
25
27
|
const locales = useLocales();
|
|
@@ -59,15 +61,15 @@ export const TopNavigation = ({ utilities: providedUtilities, identity, ...props
|
|
|
59
61
|
if (authProvider) {
|
|
60
62
|
autoUtilities.push({
|
|
61
63
|
type: 'menu-dropdown',
|
|
62
|
-
text: translate('
|
|
64
|
+
text: translate('strato.auth.user_menu', { _: 'User' }),
|
|
63
65
|
iconName: 'user-profile',
|
|
64
66
|
items: [
|
|
65
|
-
{ id: 'profile', text: translate('
|
|
66
|
-
{ id: 'signout', text: translate('
|
|
67
|
+
{ id: 'profile', text: translate('strato.auth.profile', { _: 'Profile' }) },
|
|
68
|
+
{ id: 'signout', text: translate('strato.auth.logout', { _: 'Sign out' }) },
|
|
67
69
|
],
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
|
-
utilities = autoUtilities;
|
|
72
|
+
utilities = extraUtilities ? [...autoUtilities, ...extraUtilities] : autoUtilities;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
return (
|
|
@@ -75,7 +77,6 @@ export const TopNavigation = ({ utilities: providedUtilities, identity, ...props
|
|
|
75
77
|
<CloudscapeTopNavigation
|
|
76
78
|
identity={identity || { title: 'Strato Admin', href: '/' }}
|
|
77
79
|
utilities={utilities}
|
|
78
|
-
{...props}
|
|
79
80
|
/>
|
|
80
81
|
</div>
|
|
81
82
|
);
|
package/src/layout/index.ts
CHANGED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, cleanup } from '@testing-library/react';
|
|
3
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { useTranslate, useTranslateLabel } from '@strato-admin/ra-core';
|
|
5
|
+
import { useResourceSchema } from '@strato-admin/core';
|
|
6
|
+
import { useCollection } from '../collection-hooks';
|
|
7
|
+
import Cards from './Cards';
|
|
8
|
+
import CloudscapeCards from '@cloudscape-design/components/cards';
|
|
9
|
+
import KeyValuePairs from '../detail/KeyValuePairs';
|
|
10
|
+
|
|
11
|
+
vi.mock('@strato-admin/ra-core', () => import('../__mocks__/ra-core'));
|
|
12
|
+
vi.mock('@strato-admin/core', () => import('../__mocks__/strato-core'));
|
|
13
|
+
|
|
14
|
+
// Mock useCollection
|
|
15
|
+
vi.mock('../collection-hooks', () => ({
|
|
16
|
+
useCollection: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Mock Cloudscape components
|
|
20
|
+
vi.mock('@cloudscape-design/components/cards', () => ({
|
|
21
|
+
default: vi.fn(({ items, cardDefinition, header, selectionType, filter, preferences }: any) => (
|
|
22
|
+
<div data-testid="cloudscape-cards" data-selection-type={selectionType}>
|
|
23
|
+
{header && <div data-testid="cards-header">{header}</div>}
|
|
24
|
+
{filter && <div data-testid="cards-filter">{filter}</div>}
|
|
25
|
+
{preferences && <div data-testid="cards-preferences">{preferences}</div>}
|
|
26
|
+
{items.map((item: any, index: number) => (
|
|
27
|
+
<div key={index} data-testid="card">
|
|
28
|
+
{cardDefinition.sections.map((section: any) => (
|
|
29
|
+
<div key={section.id} data-testid={`section-${section.id}`}>
|
|
30
|
+
{section.content(item)}
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
)),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
vi.mock('@cloudscape-design/components/pagination', () => ({
|
|
40
|
+
default: () => <div data-testid="pagination" />,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock('@cloudscape-design/components/text-filter', () => ({
|
|
44
|
+
default: vi.fn(({ filteringPlaceholder }: any) => <div data-testid="text-filter">{filteringPlaceholder}</div>),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
vi.mock('@cloudscape-design/components/collection-preferences', () => ({
|
|
48
|
+
default: vi.fn(() => <div data-testid="collection-preferences" />),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
vi.mock('../detail/KeyValuePairs', () => ({
|
|
52
|
+
default: vi.fn(({ children }: any) => <div data-testid="key-value-pairs">{children}</div>),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vi.mock('./TableHeader', () => ({
|
|
56
|
+
default: vi.fn(({ title, description, actions }: any) => (
|
|
57
|
+
<div data-testid="mock-table-header">
|
|
58
|
+
<div data-testid="header-title">{title}</div>
|
|
59
|
+
<div data-testid="header-description">{description}</div>
|
|
60
|
+
<div data-testid="header-actions">{actions}</div>
|
|
61
|
+
</div>
|
|
62
|
+
)),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
describe('Cards', () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks();
|
|
68
|
+
(useCollection as any).mockReturnValue({
|
|
69
|
+
items: [{ id: 1, name: 'Product 1', price: 100, description: 'Desc 1' }],
|
|
70
|
+
paginationProps: {},
|
|
71
|
+
collectionProps: {},
|
|
72
|
+
filterProps: { filteringPlaceholder: 'Search...' },
|
|
73
|
+
preferencesProps: {
|
|
74
|
+
preferences: {},
|
|
75
|
+
onConfirm: vi.fn(),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
cleanup();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should honor listInclude from resource schema', () => {
|
|
85
|
+
(useResourceSchema as any).mockReturnValue({
|
|
86
|
+
fieldSchema: [
|
|
87
|
+
<div key="name" source="name" data-testid="field-name" />,
|
|
88
|
+
<div key="price" source="price" data-testid="field-price" />,
|
|
89
|
+
<div key="description" source="description" data-testid="field-description" />,
|
|
90
|
+
],
|
|
91
|
+
listInclude: ['name', 'price'],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
render(<Cards />);
|
|
95
|
+
|
|
96
|
+
// Check that KeyValuePairs received only name and price fields
|
|
97
|
+
const keyValuePairsProps = (KeyValuePairs as any).mock.calls[0][0];
|
|
98
|
+
const children = React.Children.toArray(keyValuePairsProps.children);
|
|
99
|
+
|
|
100
|
+
expect(children).toHaveLength(2);
|
|
101
|
+
expect((children[0] as any).props.source).toBe('name');
|
|
102
|
+
expect((children[1] as any).props.source).toBe('price');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should honor listExclude from resource schema', () => {
|
|
106
|
+
(useResourceSchema as any).mockReturnValue({
|
|
107
|
+
fieldSchema: [
|
|
108
|
+
<div key="name" source="name" data-testid="field-name" />,
|
|
109
|
+
<div key="price" source="price" data-testid="field-price" />,
|
|
110
|
+
<div key="description" source="description" data-testid="field-description" />,
|
|
111
|
+
],
|
|
112
|
+
listExclude: ['description'],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
render(<Cards />);
|
|
116
|
+
|
|
117
|
+
const keyValuePairsProps = (KeyValuePairs as any).mock.calls[0][0];
|
|
118
|
+
const children = React.Children.toArray(keyValuePairsProps.children);
|
|
119
|
+
|
|
120
|
+
expect(children).toHaveLength(2);
|
|
121
|
+
expect((children[0] as any).props.source).toBe('name');
|
|
122
|
+
expect((children[1] as any).props.source).toBe('price');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should honor include prop over listInclude from schema', () => {
|
|
126
|
+
(useResourceSchema as any).mockReturnValue({
|
|
127
|
+
fieldSchema: [
|
|
128
|
+
<div key="name" source="name" data-testid="field-name" />,
|
|
129
|
+
<div key="price" source="price" data-testid="field-price" />,
|
|
130
|
+
<div key="description" source="description" data-testid="field-description" />,
|
|
131
|
+
],
|
|
132
|
+
listInclude: ['name'],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
render(<Cards include={['price', 'description']} />);
|
|
136
|
+
|
|
137
|
+
const keyValuePairsProps = (KeyValuePairs as any).mock.calls[0][0];
|
|
138
|
+
const children = React.Children.toArray(keyValuePairsProps.children);
|
|
139
|
+
|
|
140
|
+
expect(children).toHaveLength(2);
|
|
141
|
+
expect((children[0] as any).props.source).toBe('price');
|
|
142
|
+
expect((children[1] as any).props.source).toBe('description');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should support declarative children', () => {
|
|
146
|
+
(useResourceSchema as any).mockReturnValue({
|
|
147
|
+
fieldSchema: [<div key="name" source="name" data-testid="field-name" />],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
render(
|
|
151
|
+
<Cards>
|
|
152
|
+
<div source="custom" data-testid="field-custom" />
|
|
153
|
+
</Cards>,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const keyValuePairsProps = (KeyValuePairs as any).mock.calls[0][0];
|
|
157
|
+
const children = React.Children.toArray(keyValuePairsProps.children);
|
|
158
|
+
|
|
159
|
+
expect(children).toHaveLength(1);
|
|
160
|
+
expect((children[0] as any).props.source).toBe('custom');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should render TableHeader with correct title and description', () => {
|
|
164
|
+
(useResourceSchema as any).mockReturnValue({
|
|
165
|
+
fieldSchema: [],
|
|
166
|
+
label: 'Products',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const { getByTestId } = render(<Cards title="Custom Title" description="Custom Description" />);
|
|
170
|
+
|
|
171
|
+
expect(getByTestId('header-title').textContent).toBe('Custom Title');
|
|
172
|
+
expect(getByTestId('header-description').textContent).toBe('Custom Description');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should use schema label as default title', () => {
|
|
176
|
+
(useResourceSchema as any).mockReturnValue({
|
|
177
|
+
fieldSchema: [],
|
|
178
|
+
label: 'Schema Products',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const { getByTestId } = render(<Cards />);
|
|
182
|
+
|
|
183
|
+
expect(getByTestId('header-title').textContent).toBe('Schema Products');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should pass selectionType to CloudscapeCards', () => {
|
|
187
|
+
(useResourceSchema as any).mockReturnValue({
|
|
188
|
+
fieldSchema: [],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const { getByTestId } = render(<Cards selectionType="multi" />);
|
|
192
|
+
|
|
193
|
+
expect(getByTestId('cloudscape-cards').getAttribute('data-selection-type')).toBe('multi');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should default selectionType to multi if canDelete is true', () => {
|
|
197
|
+
(useResourceSchema as any).mockReturnValue({
|
|
198
|
+
fieldSchema: [],
|
|
199
|
+
definition: {
|
|
200
|
+
options: { canDelete: true },
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const { getByTestId } = render(<Cards />);
|
|
205
|
+
|
|
206
|
+
expect(getByTestId('cloudscape-cards').getAttribute('data-selection-type')).toBe('multi');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should render TextFilter by default', () => {
|
|
210
|
+
(useResourceSchema as any).mockReturnValue({
|
|
211
|
+
fieldSchema: [],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const { getByTestId } = render(<Cards />);
|
|
215
|
+
|
|
216
|
+
expect(getByTestId('text-filter')).toBeDefined();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should not render TextFilter if filtering is false', () => {
|
|
220
|
+
(useResourceSchema as any).mockReturnValue({
|
|
221
|
+
fieldSchema: [],
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const { queryByTestId } = render(<Cards filtering={false} />);
|
|
225
|
+
|
|
226
|
+
expect(queryByTestId('text-filter')).toBeNull();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should pass filteringPlaceholder to TextFilter', () => {
|
|
230
|
+
(useResourceSchema as any).mockReturnValue({
|
|
231
|
+
fieldSchema: [],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const { getByTestId } = render(<Cards filteringPlaceholder="Custom Search" />);
|
|
235
|
+
|
|
236
|
+
expect(getByTestId('text-filter').textContent).toBe('Custom Search');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should render CollectionPreferences by default', () => {
|
|
240
|
+
(useResourceSchema as any).mockReturnValue({
|
|
241
|
+
fieldSchema: [],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const { getByTestId } = render(<Cards />);
|
|
245
|
+
|
|
246
|
+
expect(getByTestId('collection-preferences')).toBeDefined();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should pass pageSizeOptions to useCollection', () => {
|
|
250
|
+
(useResourceSchema as any).mockReturnValue({
|
|
251
|
+
fieldSchema: [],
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const pageSizeOptions = [{ value: 5, label: '5 items' }];
|
|
255
|
+
render(<Cards pageSizeOptions={pageSizeOptions} />);
|
|
256
|
+
|
|
257
|
+
expect(useCollection).toHaveBeenCalledWith(
|
|
258
|
+
expect.objectContaining({
|
|
259
|
+
preferences: expect.objectContaining({
|
|
260
|
+
pageSizeOptions,
|
|
261
|
+
} as any),
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should filter children based on visibleContent preference', () => {
|
|
267
|
+
(useResourceSchema as any).mockReturnValue({
|
|
268
|
+
fieldSchema: [
|
|
269
|
+
<div key="name" source="name" data-testid="field-name" />,
|
|
270
|
+
<div key="price" source="price" data-testid="field-price" />,
|
|
271
|
+
],
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
(useCollection as any).mockReturnValue({
|
|
275
|
+
items: [{ id: 1, name: 'Product 1', price: 100 }],
|
|
276
|
+
paginationProps: {},
|
|
277
|
+
collectionProps: {},
|
|
278
|
+
filterProps: {},
|
|
279
|
+
preferencesProps: {
|
|
280
|
+
preferences: {
|
|
281
|
+
visibleContent: ['name'],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
render(<Cards />);
|
|
287
|
+
|
|
288
|
+
const keyValuePairsProps = (KeyValuePairs as any).mock.calls[0][0];
|
|
289
|
+
const children = React.Children.toArray(keyValuePairsProps.children);
|
|
290
|
+
|
|
291
|
+
expect(children).toHaveLength(1);
|
|
292
|
+
expect((children[0] as any).props.source).toBe('name');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should not filter children if visibleContent preference is not set', () => {
|
|
296
|
+
(useResourceSchema as any).mockReturnValue({
|
|
297
|
+
fieldSchema: [
|
|
298
|
+
<div key="name" source="name" data-testid="field-name" />,
|
|
299
|
+
<div key="price" source="price" data-testid="field-price" />,
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
(useCollection as any).mockReturnValue({
|
|
304
|
+
items: [{ id: 1, name: 'Product 1', price: 100 }],
|
|
305
|
+
paginationProps: {},
|
|
306
|
+
collectionProps: {},
|
|
307
|
+
filterProps: {},
|
|
308
|
+
preferencesProps: {
|
|
309
|
+
preferences: {},
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
render(<Cards />);
|
|
314
|
+
|
|
315
|
+
const keyValuePairsProps = (KeyValuePairs as any).mock.calls[0][0];
|
|
316
|
+
const children = React.Children.toArray(keyValuePairsProps.children);
|
|
317
|
+
|
|
318
|
+
expect(children).toHaveLength(2);
|
|
319
|
+
});
|
|
320
|
+
});
|