@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,19 @@
|
|
|
1
|
+
export interface MessageProps {
|
|
2
|
+
children: string;
|
|
3
|
+
/**
|
|
4
|
+
* Explicit stable message ID. Used directly as the lookup key — bypasses
|
|
5
|
+
* hashing entirely. Takes priority over `context`. Written as `#. id: <id>`
|
|
6
|
+
* in PO files so translators see the stable identifier alongside the source text.
|
|
7
|
+
*/
|
|
8
|
+
id?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Disambiguation context. Prepended to the message before hashing so that
|
|
11
|
+
* the same string in different contexts produces different translation entries.
|
|
12
|
+
* Ignored when `id` is provided. Stored as `msgctxt` in PO files.
|
|
13
|
+
*/
|
|
14
|
+
context?: string;
|
|
15
|
+
/** Translator note written into the PO file as a `#` comment. Ignored at runtime. */
|
|
16
|
+
comment?: string;
|
|
17
|
+
/** ICU variables substituted into the message at runtime. */
|
|
18
|
+
vars?: Record<string, any>;
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,9 @@ export * from './form';
|
|
|
11
11
|
export * from './layout';
|
|
12
12
|
export * from './theme';
|
|
13
13
|
export * from './button';
|
|
14
|
+
export * from './i18n';
|
|
14
15
|
export * from './Admin';
|
|
16
|
+
export * from './Settings';
|
|
15
17
|
export { default as RecordLink } from './RecordLink';
|
|
16
18
|
export * from './RecordLink';
|
|
17
19
|
|
|
@@ -21,5 +23,7 @@ export { Form, type FormProps } from './form';
|
|
|
21
23
|
export { List, type ListProps } from './list';
|
|
22
24
|
export { Create, type CreateProps } from './create';
|
|
23
25
|
export { Edit, type EditProps } from './edit';
|
|
24
|
-
export { Show, type ShowProps } from './detail';
|
|
26
|
+
export { Detail, type DetailProps, Detail as Show, type DetailProps as ShowProps } from './detail';
|
|
25
27
|
export { type InputProps } from './input';
|
|
28
|
+
export { Ready, AppLayout, TopNavigation } from './layout';
|
|
29
|
+
export { Button } from './button';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { useForm, FormProvider } from 'react-hook-form';
|
|
5
|
+
import { ArrayInput } from './ArrayInput';
|
|
6
|
+
import TextInput from './TextInput';
|
|
7
|
+
|
|
8
|
+
const TestWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
9
|
+
const methods = useForm({
|
|
10
|
+
defaultValues: {
|
|
11
|
+
items: [{ name: 'Item 1' }, { name: 'Item 2' }],
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
return <FormProvider {...methods}>{children}</FormProvider>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('ArrayInput', () => {
|
|
18
|
+
it('should render items from default values', () => {
|
|
19
|
+
render(
|
|
20
|
+
<TestWrapper>
|
|
21
|
+
<ArrayInput source="items">
|
|
22
|
+
<TextInput source="name" label="Name" />
|
|
23
|
+
</ArrayInput>
|
|
24
|
+
</TestWrapper>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(screen.getByDisplayValue('Item 1')).toBeDefined();
|
|
28
|
+
expect(screen.getByDisplayValue('Item 2')).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should add a new item when clicking the add button', async () => {
|
|
32
|
+
render(
|
|
33
|
+
<TestWrapper>
|
|
34
|
+
<ArrayInput source="items" addButtonText="Add new one">
|
|
35
|
+
<TextInput source="name" label="Name" />
|
|
36
|
+
</ArrayInput>
|
|
37
|
+
</TestWrapper>,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const addButton = screen.getByText('Add new one');
|
|
41
|
+
fireEvent.click(addButton);
|
|
42
|
+
|
|
43
|
+
// AttributeEditor might have some internal delay or we need to wait for react-hook-form
|
|
44
|
+
await waitFor(() => {
|
|
45
|
+
const inputs = screen.getAllByRole('textbox');
|
|
46
|
+
expect(inputs).toHaveLength(3);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should remove an item when clicking the remove button', async () => {
|
|
51
|
+
render(
|
|
52
|
+
<TestWrapper>
|
|
53
|
+
<ArrayInput source="items" removeButtonText="Remove this">
|
|
54
|
+
<TextInput source="name" label="Name" />
|
|
55
|
+
</ArrayInput>
|
|
56
|
+
</TestWrapper>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const removeButtons = screen.getAllByText('Remove this');
|
|
60
|
+
fireEvent.click(removeButtons[0]);
|
|
61
|
+
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
const inputs = screen.getAllByRole('textbox');
|
|
64
|
+
expect(inputs).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should support gridLayout prop', () => {
|
|
69
|
+
// This is mostly a smoke test to ensure the prop doesn't crash
|
|
70
|
+
const gridLayout = [{ rows: [[1]] }];
|
|
71
|
+
render(
|
|
72
|
+
<TestWrapper>
|
|
73
|
+
<ArrayInput source="items" gridLayout={gridLayout}>
|
|
74
|
+
<TextInput source="name" label="Name" />
|
|
75
|
+
</ArrayInput>
|
|
76
|
+
</TestWrapper>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(screen.getByDisplayValue('Item 1')).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
-
import { useInput, RecordContextProvider, useResourceContext } from '@strato-admin/core';
|
|
2
|
+
import { useInput, RecordContextProvider, useResourceContext } from '@strato-admin/ra-core';
|
|
3
3
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
4
|
-
import CloudscapeAttributeEditor
|
|
4
|
+
import CloudscapeAttributeEditor, {
|
|
5
|
+
AttributeEditorProps as CloudscapeAttributeEditorProps,
|
|
6
|
+
} from '@cloudscape-design/components/attribute-editor';
|
|
5
7
|
import Box from '@cloudscape-design/components/box';
|
|
6
8
|
import { FieldTitle } from './FieldTitle';
|
|
7
9
|
import TextInput from './TextInput';
|
|
@@ -9,7 +11,7 @@ import FormField from './FormField';
|
|
|
9
11
|
import { FormFieldContext, useFormFieldContext } from './FormFieldContext';
|
|
10
12
|
import { InputProps } from './types';
|
|
11
13
|
|
|
12
|
-
export interface
|
|
14
|
+
export interface ArrayInputItemProps {
|
|
13
15
|
source: string;
|
|
14
16
|
label?: string | false;
|
|
15
17
|
field?: React.ComponentType<any>;
|
|
@@ -18,23 +20,30 @@ export interface AttributeEditorItemProps {
|
|
|
18
20
|
children?: React.ReactNode;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export const Item = (_props:
|
|
23
|
+
export const Item = (_props: ArrayInputItemProps) => {
|
|
22
24
|
// This is a placeholder component used to collect props.
|
|
23
|
-
// The actual rendering is handled by the
|
|
25
|
+
// The actual rendering is handled by the ArrayInput.
|
|
24
26
|
return null;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
export interface
|
|
29
|
+
export interface ArrayInputProps extends Omit<InputProps, 'source'> {
|
|
28
30
|
source?: string;
|
|
29
|
-
children
|
|
31
|
+
children?: React.ReactNode;
|
|
30
32
|
addButtonText?: string;
|
|
31
33
|
removeButtonText?: string;
|
|
32
34
|
empty?: React.ReactNode;
|
|
33
35
|
disableAddButton?: boolean;
|
|
34
36
|
hideAddButton?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Optionally specifies the layout of the attributes.
|
|
39
|
+
*/
|
|
40
|
+
gridLayout?: ReadonlyArray<CloudscapeAttributeEditorProps.GridLayout>;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
/**
|
|
44
|
+
* ArrayInput component that uses Cloudscape's AttributeEditor to edit arrays of objects.
|
|
45
|
+
*/
|
|
46
|
+
export const ArrayInput = (props: ArrayInputProps) => {
|
|
38
47
|
const {
|
|
39
48
|
children,
|
|
40
49
|
label,
|
|
@@ -46,6 +55,7 @@ export const AttributeEditor = (props: AttributeEditorProps) => {
|
|
|
46
55
|
empty,
|
|
47
56
|
disableAddButton,
|
|
48
57
|
hideAddButton,
|
|
58
|
+
gridLayout,
|
|
49
59
|
...rest
|
|
50
60
|
} = props;
|
|
51
61
|
|
|
@@ -86,14 +96,25 @@ export const AttributeEditor = (props: AttributeEditorProps) => {
|
|
|
86
96
|
const childSource = childProps.source;
|
|
87
97
|
const childLabel = childProps.label;
|
|
88
98
|
const childValidate = childProps.validate;
|
|
99
|
+
const childDescription = childProps.description;
|
|
100
|
+
const childInfo = childProps.info;
|
|
89
101
|
|
|
90
102
|
// Determine if the field is required by checking validators
|
|
91
103
|
const isRequired = Array.isArray(childValidate)
|
|
92
|
-
? childValidate.some((v: any) => v.isRequired)
|
|
93
|
-
: childValidate?.isRequired || childProps.isRequired;
|
|
104
|
+
? childValidate.some((v: any) => v.isRequired || v.name === 'required')
|
|
105
|
+
: childValidate?.isRequired || childValidate?.name === 'required' || childProps.isRequired;
|
|
94
106
|
|
|
95
107
|
return {
|
|
96
|
-
label:
|
|
108
|
+
label: (
|
|
109
|
+
<FieldTitle
|
|
110
|
+
label={childLabel}
|
|
111
|
+
source={childSource}
|
|
112
|
+
resource={childProps.resource || resource}
|
|
113
|
+
isRequired={isRequired}
|
|
114
|
+
/>
|
|
115
|
+
),
|
|
116
|
+
description: childDescription,
|
|
117
|
+
info: childInfo,
|
|
97
118
|
control: (item: any, index: number) => {
|
|
98
119
|
const prefixedSource = `${source}.${index}.${childSource}`;
|
|
99
120
|
|
|
@@ -135,11 +156,7 @@ export const AttributeEditor = (props: AttributeEditorProps) => {
|
|
|
135
156
|
);
|
|
136
157
|
}
|
|
137
158
|
|
|
138
|
-
return
|
|
139
|
-
<RecordContextProvider value={item}>
|
|
140
|
-
<Box padding={{ top: 's' }}>{content}</Box>
|
|
141
|
-
</RecordContextProvider>
|
|
142
|
-
);
|
|
159
|
+
return <RecordContextProvider value={item}>{content}</RecordContextProvider>;
|
|
143
160
|
},
|
|
144
161
|
};
|
|
145
162
|
})?.filter(Boolean) as any[];
|
|
@@ -163,6 +180,7 @@ export const AttributeEditor = (props: AttributeEditorProps) => {
|
|
|
163
180
|
removeButtonText={removeButtonText}
|
|
164
181
|
disableAddButton={disableAddButton}
|
|
165
182
|
hideAddButton={hideAddButton}
|
|
183
|
+
gridLayout={gridLayout}
|
|
166
184
|
/>
|
|
167
185
|
</FormFieldContext.Provider>
|
|
168
186
|
);
|
|
@@ -180,6 +198,6 @@ export const AttributeEditor = (props: AttributeEditorProps) => {
|
|
|
180
198
|
);
|
|
181
199
|
};
|
|
182
200
|
|
|
183
|
-
|
|
201
|
+
ArrayInput.Item = Item;
|
|
184
202
|
|
|
185
|
-
export default
|
|
203
|
+
export default ArrayInput;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
1
|
import { render, fireEvent } from '@testing-library/react';
|
|
3
2
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
-
import { useInput, useResourceContext, useChoicesContext } from '@strato-admin/core';
|
|
3
|
+
import { useInput, useResourceContext, useChoicesContext } from '@strato-admin/ra-core';
|
|
5
4
|
import { AutocompleteInput } from './AutocompleteInput';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
vi.mock('@strato-admin/core', () => ({
|
|
6
|
+
vi.mock('@strato-admin/ra-core', () => ({
|
|
9
7
|
useInput: vi.fn(),
|
|
10
8
|
useResourceContext: vi.fn(),
|
|
11
9
|
useChoicesContext: vi.fn().mockReturnValue({ allChoices: [], isPending: false }),
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
useInput,
|
|
4
|
-
useResourceContext,
|
|
5
|
-
useChoicesContext,
|
|
6
|
-
useGetRecordRepresentation,
|
|
7
|
-
} from '@strato-admin/core';
|
|
2
|
+
import { useInput, useResourceContext, useChoicesContext, useGetRecordRepresentation } from '@strato-admin/ra-core';
|
|
8
3
|
import CloudscapeAutosuggest, {
|
|
9
4
|
AutosuggestProps as CloudscapeAutosuggestProps,
|
|
10
5
|
} from '@cloudscape-design/components/autosuggest';
|
|
@@ -13,13 +8,13 @@ import { FormFieldContext, useFormFieldContext } from './FormFieldContext';
|
|
|
13
8
|
import { InputProps } from './types';
|
|
14
9
|
|
|
15
10
|
export interface AutocompleteInputProps
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
extends InputProps,
|
|
12
|
+
Pick<CloudscapeAutosuggestProps, 'placeholder' | 'disabled' | 'readOnly' | 'enteredTextLabel'> {
|
|
13
|
+
choices?: Array<{ id: string | number; [key: string]: any }>;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
export const AutocompleteInput = (props: AutocompleteInputProps) => {
|
|
22
|
-
|
|
17
|
+
const { label, source, defaultValue, validate, choices: choicesProp, placeholder, disabled, readOnly, enteredTextLabel, ...rest } = props;
|
|
23
18
|
const resource = useResourceContext();
|
|
24
19
|
const { allChoices, isPending, setFilters } = useChoicesContext(props);
|
|
25
20
|
const getRecordRepresentation = useGetRecordRepresentation(resource);
|
|
@@ -90,8 +85,11 @@ export const AutocompleteInput = (props: AutocompleteInputProps) => {
|
|
|
90
85
|
|
|
91
86
|
const inner = (
|
|
92
87
|
<CloudscapeAutosuggest
|
|
93
|
-
{...rest}
|
|
94
88
|
id={id}
|
|
89
|
+
placeholder={placeholder}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
readOnly={readOnly}
|
|
92
|
+
enteredTextLabel={enteredTextLabel}
|
|
95
93
|
options={options}
|
|
96
94
|
value={filterValue}
|
|
97
95
|
statusType={isPending ? 'loading' : 'finished'}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useInput } from '@strato-admin/ra-core';
|
|
2
|
+
import Toggle, { ToggleProps } from '@cloudscape-design/components/toggle';
|
|
3
|
+
import { FormField } from './FormField';
|
|
4
|
+
import { FormFieldContext, useFormFieldContext } from './FormFieldContext';
|
|
5
|
+
import { InputProps } from './types';
|
|
6
|
+
|
|
7
|
+
export interface BooleanInputProps
|
|
8
|
+
extends InputProps,
|
|
9
|
+
Pick<ToggleProps, 'disabled' | 'readOnly' | 'children' | 'ariaLabel'> {}
|
|
10
|
+
|
|
11
|
+
export const BooleanInput = (props: BooleanInputProps) => {
|
|
12
|
+
const { label, source, defaultValue, validate, disabled, readOnly, children, ariaLabel, ...rest } = props;
|
|
13
|
+
const context = useFormFieldContext();
|
|
14
|
+
const inputState =
|
|
15
|
+
context ??
|
|
16
|
+
useInput({
|
|
17
|
+
source,
|
|
18
|
+
defaultValue: defaultValue ?? false,
|
|
19
|
+
validate,
|
|
20
|
+
...rest,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const { field } = inputState;
|
|
24
|
+
|
|
25
|
+
const inner = (
|
|
26
|
+
<Toggle disabled={disabled} readOnly={readOnly} ariaLabel={ariaLabel} checked={!!field.value} onChange={(event) => field.onChange(event.detail.checked)}>
|
|
27
|
+
{children}
|
|
28
|
+
</Toggle>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (context) {
|
|
32
|
+
return inner;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<FormFieldContext.Provider value={inputState}>
|
|
37
|
+
<FormField {...props}>{inner}</FormField>
|
|
38
|
+
</FormFieldContext.Provider>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default BooleanInput;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StratoCommonInputProps } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @internal Stub component for react-docgen-typescript to extract StratoCommonInputProps.
|
|
5
|
+
* Not intended for direct use.
|
|
6
|
+
*/
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
export const CommonInputProps = (_props: StratoCommonInputProps) => null;
|
package/src/input/FieldTitle.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
1
|
import React from 'react';
|
|
3
|
-
import { useTranslate, useResourceDefinitions } from '@strato-admin/core';
|
|
2
|
+
import { useTranslate, useResourceDefinitions } from '@strato-admin/ra-core';
|
|
4
3
|
import { humanize } from 'inflection';
|
|
5
4
|
|
|
6
5
|
export interface FieldTitleProps {
|
|
@@ -11,7 +10,7 @@ export interface FieldTitleProps {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export const FieldTitle = (props: FieldTitleProps) => {
|
|
14
|
-
const { resource, source, label
|
|
13
|
+
const { resource, source, label } = props;
|
|
15
14
|
const translate = useTranslate();
|
|
16
15
|
const definitions = useResourceDefinitions();
|
|
17
16
|
|
|
@@ -36,18 +35,7 @@ export const FieldTitle = (props: FieldTitleProps) => {
|
|
|
36
35
|
});
|
|
37
36
|
}, [label, translate, resource, source, definitions]);
|
|
38
37
|
|
|
39
|
-
return
|
|
40
|
-
<span>
|
|
41
|
-
{labelString}
|
|
42
|
-
{!isRequired && (
|
|
43
|
-
<span
|
|
44
|
-
style={{ color: '#687078', fontWeight: 'normal', fontStyle: 'italic', fontSize: '12px', marginLeft: '4px' }}
|
|
45
|
-
>
|
|
46
|
-
(optional)
|
|
47
|
-
</span>
|
|
48
|
-
)}
|
|
49
|
-
</span>
|
|
50
|
-
);
|
|
38
|
+
return <span>{labelString}</span>;
|
|
51
39
|
};
|
|
52
40
|
|
|
53
41
|
export default FieldTitle;
|
package/src/input/FormField.tsx
CHANGED
|
@@ -1,87 +1,98 @@
|
|
|
1
|
-
|
|
2
1
|
import React from 'react';
|
|
3
|
-
import { useInput, useResourceContext, ValidationError } from '@strato-admin/core';
|
|
2
|
+
import { useInput, useResourceContext, ValidationError, useTranslate } from '@strato-admin/ra-core';
|
|
4
3
|
import CloudscapeFormField from '@cloudscape-design/components/form-field';
|
|
5
4
|
import { FieldTitle } from './FieldTitle';
|
|
6
5
|
import { FormFieldContext, useFormFieldContext } from './FormFieldContext';
|
|
7
6
|
import { InputProps } from './types';
|
|
8
7
|
|
|
9
8
|
export interface FormFieldProps extends InputProps {
|
|
10
|
-
|
|
9
|
+
children: React.ReactNode;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export const FormField = (props: FormFieldProps) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
const {
|
|
14
|
+
children,
|
|
15
|
+
label,
|
|
16
|
+
source,
|
|
17
|
+
defaultValue,
|
|
18
|
+
validate,
|
|
19
|
+
description,
|
|
20
|
+
constraintText,
|
|
21
|
+
info,
|
|
22
|
+
secondaryControl,
|
|
23
|
+
stretch,
|
|
24
|
+
i18nStrings,
|
|
25
|
+
...rest
|
|
26
|
+
} = props;
|
|
27
|
+
const resource = useResourceContext();
|
|
28
|
+
const translate = useTranslate();
|
|
29
|
+
const context = useFormFieldContext();
|
|
30
|
+
const inputState =
|
|
31
|
+
context ??
|
|
32
|
+
useInput({
|
|
33
|
+
source,
|
|
34
|
+
defaultValue,
|
|
35
|
+
validate,
|
|
36
|
+
...rest,
|
|
37
|
+
});
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
const contextValue = React.useMemo(() => {
|
|
40
|
+
if (!inputState) return undefined;
|
|
41
|
+
return {
|
|
42
|
+
...inputState,
|
|
43
|
+
source: source || context?.source || '',
|
|
44
|
+
};
|
|
45
|
+
}, [inputState, source, context?.source]);
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
const {
|
|
48
|
+
id,
|
|
49
|
+
fieldState: { isTouched, invalid, error },
|
|
50
|
+
formState: { isSubmitted },
|
|
51
|
+
isRequired,
|
|
52
|
+
} = inputState;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const errorToProcess = (error as any)?.message || (error as any)?.root?.message;
|
|
55
|
+
const errorText =
|
|
56
|
+
(isTouched || isSubmitted) && invalid && typeof errorToProcess === 'string' ? (
|
|
57
|
+
<ValidationError error={errorToProcess} />
|
|
58
|
+
) : undefined;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
description={description}
|
|
69
|
-
constraintText={constraintText}
|
|
70
|
-
info={info}
|
|
71
|
-
secondaryControl={secondaryControl}
|
|
72
|
-
stretch={stretch}
|
|
73
|
-
i18nStrings={i18nStrings}
|
|
74
|
-
errorText={errorText}
|
|
75
|
-
>
|
|
76
|
-
{children}
|
|
77
|
-
</CloudscapeFormField>
|
|
60
|
+
const finalConstraintText = React.useMemo(() => {
|
|
61
|
+
if (isRequired) return constraintText;
|
|
62
|
+
const optionalLabel = `(${translate('optional')})`;
|
|
63
|
+
if (!constraintText) return optionalLabel;
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
{constraintText} ({optionalLabel})
|
|
67
|
+
</>
|
|
78
68
|
);
|
|
69
|
+
}, [isRequired, constraintText, translate]);
|
|
70
|
+
|
|
71
|
+
const content = (
|
|
72
|
+
<CloudscapeFormField
|
|
73
|
+
id={id}
|
|
74
|
+
label={
|
|
75
|
+
label === false ? undefined : (
|
|
76
|
+
<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
description={label === false ? undefined : description}
|
|
80
|
+
constraintText={label === false ? undefined : finalConstraintText}
|
|
81
|
+
info={info}
|
|
82
|
+
secondaryControl={secondaryControl}
|
|
83
|
+
stretch={stretch}
|
|
84
|
+
i18nStrings={i18nStrings}
|
|
85
|
+
errorText={errorText}
|
|
86
|
+
>
|
|
87
|
+
{children}
|
|
88
|
+
</CloudscapeFormField>
|
|
89
|
+
);
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
if (context) {
|
|
92
|
+
return content;
|
|
93
|
+
}
|
|
83
94
|
|
|
84
|
-
|
|
95
|
+
return <FormFieldContext.Provider value={contextValue}>{content}</FormFieldContext.Provider>;
|
|
85
96
|
};
|
|
86
97
|
|
|
87
98
|
export default FormField;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
import { useInput } from '@strato-admin/core';
|
|
1
|
+
import { useInput } from '@strato-admin/ra-core';
|
|
3
2
|
import CloudscapeInput, { InputProps as CloudscapeInputProps } from '@cloudscape-design/components/input';
|
|
4
3
|
import { FormField } from './FormField';
|
|
5
4
|
import { FormFieldContext, useFormFieldContext } from './FormFieldContext';
|
|
6
5
|
import { InputProps } from './types';
|
|
7
6
|
|
|
8
7
|
export interface NumberInputProps
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
extends InputProps,
|
|
9
|
+
Pick<CloudscapeInputProps, 'placeholder' | 'disabled' | 'readOnly' | 'autoFocus' | 'step'> {
|
|
10
|
+
type?: CloudscapeInputProps['type'];
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export const NumberInput = (props: NumberInputProps) => {
|
|
15
|
-
|
|
14
|
+
const { label, source, defaultValue, validate, type = 'number', placeholder, disabled, readOnly, autoFocus, step, ...rest } = props;
|
|
16
15
|
const context = useFormFieldContext();
|
|
17
16
|
const inputState =
|
|
18
17
|
context ??
|
|
@@ -32,10 +31,14 @@ export const NumberInput = (props: NumberInputProps) => {
|
|
|
32
31
|
|
|
33
32
|
const inner = (
|
|
34
33
|
<CloudscapeInput
|
|
35
|
-
{...rest}
|
|
36
34
|
{...field}
|
|
37
35
|
id={id}
|
|
38
36
|
type={type}
|
|
37
|
+
placeholder={placeholder}
|
|
38
|
+
disabled={disabled}
|
|
39
|
+
readOnly={readOnly}
|
|
40
|
+
autoFocus={autoFocus}
|
|
41
|
+
step={step}
|
|
39
42
|
value={field.value?.toString() || ''}
|
|
40
43
|
onChange={(event) => handleChange(event.detail.value)}
|
|
41
44
|
onBlur={() => field.onBlur()}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
2
1
|
import { render } from '@testing-library/react';
|
|
3
2
|
import { vi, describe, it, expect } from 'vitest';
|
|
4
3
|
import { ReferenceInput } from './ReferenceInput';
|
|
5
4
|
|
|
6
5
|
// Mock ra-core
|
|
7
|
-
vi.mock('@strato-admin/core', () => ({
|
|
6
|
+
vi.mock('@strato-admin/ra-core', () => ({
|
|
8
7
|
ReferenceInputBase: ({ children }: any) => <div data-testid="reference-input-base">{children}</div>,
|
|
8
|
+
useInput: vi.fn(() => ({
|
|
9
|
+
id: 'test',
|
|
10
|
+
field: { value: '', onChange: vi.fn(), onBlur: vi.fn() },
|
|
11
|
+
fieldState: { isTouched: false, invalid: false, error: null },
|
|
12
|
+
formState: { isSubmitted: false },
|
|
13
|
+
isRequired: false,
|
|
14
|
+
})),
|
|
15
|
+
useResourceContext: vi.fn(() => 'products'),
|
|
16
|
+
useTranslate: vi.fn(() => (key: string) => key),
|
|
17
|
+
useResourceDefinitions: vi.fn(() => ({})),
|
|
18
|
+
ValidationError: ({ error }: any) => <span>{error}</span>,
|
|
9
19
|
}));
|
|
10
20
|
|
|
11
21
|
describe('ReferenceInput', () => {
|