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