@strato-admin/cloudscape 0.1.1 → 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 +2 -1
- package/dist/button/BulkDeleteButton.js +17 -11
- 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 +8 -5
- 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 +17 -4
- package/src/button/BulkDeleteButton.tsx +24 -29
- 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
|
@@ -1,22 +1,39 @@
|
|
|
1
|
-
|
|
2
1
|
import React from 'react';
|
|
3
2
|
import { render, screen, cleanup } from '@testing-library/react';
|
|
4
3
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
5
4
|
import ReferenceManyField from './ReferenceManyField';
|
|
5
|
+
import Table from '../list/Table';
|
|
6
6
|
|
|
7
|
-
// Mock ra-core
|
|
7
|
+
// Mock ra-core
|
|
8
|
+
vi.mock('@strato-admin/ra-core', () => ({
|
|
9
|
+
ReferenceManyFieldBase: vi.fn(({ children }: any) => (
|
|
10
|
+
<div data-testid="ra-reference-many-field-base">{children}</div>
|
|
11
|
+
)),
|
|
12
|
+
ResourceContextProvider: ({ children }: any) => <div data-testid="resource-context-provider">{children}</div>,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
// Mock strato-core
|
|
8
16
|
vi.mock('@strato-admin/core', () => ({
|
|
9
|
-
ReferenceManyFieldBase: vi.fn(({ children }: any) => <div data-testid="ra-reference-many-field-base">{children}</div>),
|
|
10
17
|
ResourceSchemaProvider: vi.fn(({ children }: any) => <div data-testid="resource-schema-provider">{children}</div>),
|
|
11
|
-
|
|
18
|
+
useResourceSchema: vi.fn((resource: string) => ({
|
|
19
|
+
listComponent: resource === 'with-custom-list' ? MockCustomList : undefined,
|
|
20
|
+
})),
|
|
12
21
|
}));
|
|
13
22
|
|
|
23
|
+
// Mock Table
|
|
24
|
+
vi.mock('../list/Table', () => ({
|
|
25
|
+
default: vi.fn(({ title }: any) => <div data-testid="default-table">{title}</div>),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const MockCustomList = vi.fn(({ title }: any) => <div data-testid="custom-list">{title}</div>);
|
|
29
|
+
|
|
14
30
|
describe('ReferenceManyField', () => {
|
|
15
31
|
beforeEach(() => {
|
|
16
32
|
cleanup();
|
|
33
|
+
vi.clearAllMocks();
|
|
17
34
|
});
|
|
18
35
|
|
|
19
|
-
it('should render children within providers', () => {
|
|
36
|
+
it('should render children within providers if provided', () => {
|
|
20
37
|
render(
|
|
21
38
|
<ReferenceManyField reference="comments" target="post_id">
|
|
22
39
|
<div data-testid="child">Child Content</div>
|
|
@@ -29,13 +46,41 @@ describe('ReferenceManyField', () => {
|
|
|
29
46
|
expect(screen.getByTestId('child').textContent).toBe('Child Content');
|
|
30
47
|
});
|
|
31
48
|
|
|
32
|
-
it('should
|
|
49
|
+
it('should render default Table if no children provided', () => {
|
|
50
|
+
render(<ReferenceManyField reference="comments" target="post_id" title="Default Table Title" />);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId('default-table')).toBeDefined();
|
|
53
|
+
expect(screen.getByTestId('default-table').textContent).toBe('Default Table Title');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should render custom listComponent from schema if no children provided', () => {
|
|
57
|
+
render(<ReferenceManyField reference="with-custom-list" target="post_id" title="Custom List Title" />);
|
|
58
|
+
|
|
59
|
+
expect(screen.getByTestId('custom-list')).toBeDefined();
|
|
60
|
+
expect(screen.getByTestId('custom-list').textContent).toBe('Custom List Title');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should pass list props to the default component', () => {
|
|
33
64
|
render(
|
|
34
|
-
<ReferenceManyField
|
|
35
|
-
|
|
36
|
-
|
|
65
|
+
<ReferenceManyField
|
|
66
|
+
reference="comments"
|
|
67
|
+
target="post_id"
|
|
68
|
+
include={['id', 'name']}
|
|
69
|
+
exclude={['date']}
|
|
70
|
+
display={['id']}
|
|
71
|
+
filtering={false}
|
|
72
|
+
preferences={true}
|
|
73
|
+
/>,
|
|
37
74
|
);
|
|
38
75
|
|
|
39
|
-
expect(
|
|
76
|
+
expect(Table).toHaveBeenCalled();
|
|
77
|
+
const props = (Table as any).mock.calls[0][0];
|
|
78
|
+
expect(props).toMatchObject({
|
|
79
|
+
include: ['id', 'name'],
|
|
80
|
+
exclude: ['date'],
|
|
81
|
+
display: ['id'],
|
|
82
|
+
filtering: false,
|
|
83
|
+
preferences: true,
|
|
84
|
+
});
|
|
40
85
|
});
|
|
41
86
|
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { ReferenceManyFieldBase, type RaRecord
|
|
2
|
+
import { ReferenceManyFieldBase, type RaRecord } from '@strato-admin/ra-core';
|
|
3
|
+
import { ResourceSchemaProvider, useResourceSchema } from '@strato-admin/core';
|
|
3
4
|
import { type FieldProps } from './types';
|
|
5
|
+
import Table from '../list/Table';
|
|
4
6
|
|
|
5
|
-
export interface ReferenceManyFieldProps<
|
|
6
|
-
RecordType extends RaRecord = RaRecord,
|
|
7
|
-
ReferenceRecordType extends RaRecord = RaRecord,
|
|
8
|
-
> extends FieldProps<RecordType> {
|
|
7
|
+
export interface ReferenceManyFieldProps<RecordType extends RaRecord = RaRecord> extends FieldProps<RecordType> {
|
|
9
8
|
children?: ReactNode;
|
|
10
9
|
reference: string;
|
|
11
10
|
target: string;
|
|
@@ -39,6 +38,30 @@ export interface ReferenceManyFieldProps<
|
|
|
39
38
|
* @default false
|
|
40
39
|
*/
|
|
41
40
|
synchronizeWithLocation?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Include only these fields from the schema.
|
|
43
|
+
*/
|
|
44
|
+
include?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Exclude these fields from the schema.
|
|
47
|
+
*/
|
|
48
|
+
exclude?: string[];
|
|
49
|
+
/**
|
|
50
|
+
* The fields to display by default.
|
|
51
|
+
*/
|
|
52
|
+
display?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Whether to enable text filtering.
|
|
55
|
+
*/
|
|
56
|
+
filtering?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Whether to show the preferences button or custom preferences content.
|
|
59
|
+
*/
|
|
60
|
+
preferences?: boolean | React.ReactNode;
|
|
61
|
+
/**
|
|
62
|
+
* The title of the list.
|
|
63
|
+
*/
|
|
64
|
+
title?: React.ReactNode;
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
/**
|
|
@@ -52,22 +75,70 @@ export interface ReferenceManyFieldProps<
|
|
|
52
75
|
* <Table.Column source="created_at" />
|
|
53
76
|
* </Table>
|
|
54
77
|
* </ReferenceManyField>
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Honors the default listComponent from the reviews resource schema
|
|
81
|
+
* <ReferenceManyField reference="reviews" target="product_id" />
|
|
55
82
|
*/
|
|
56
|
-
export const ReferenceManyField = <
|
|
57
|
-
RecordType
|
|
58
|
-
ReferenceRecordType extends RaRecord = RaRecord,
|
|
59
|
-
>(
|
|
60
|
-
props: ReferenceManyFieldProps<RecordType, ReferenceRecordType>
|
|
83
|
+
export const ReferenceManyField = <RecordType extends RaRecord = RaRecord>(
|
|
84
|
+
props: ReferenceManyFieldProps<RecordType>,
|
|
61
85
|
) => {
|
|
62
|
-
const { children, reference, fieldSchema, ...rest } = props;
|
|
86
|
+
const { children, reference, fieldSchema, include, exclude, display, filtering, preferences, title, ...rest } = props;
|
|
87
|
+
const { queryOptions } = useResourceSchema(reference);
|
|
63
88
|
|
|
64
89
|
return (
|
|
65
|
-
<ReferenceManyFieldBase reference={reference} {...rest}>
|
|
90
|
+
<ReferenceManyFieldBase reference={reference} queryOptions={queryOptions} {...rest}>
|
|
66
91
|
<ResourceSchemaProvider resource={reference} fieldSchema={fieldSchema}>
|
|
67
|
-
|
|
92
|
+
<ReferenceManyFieldUI
|
|
93
|
+
reference={reference}
|
|
94
|
+
include={include}
|
|
95
|
+
exclude={exclude}
|
|
96
|
+
display={display}
|
|
97
|
+
filtering={filtering}
|
|
98
|
+
preferences={preferences}
|
|
99
|
+
title={title}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</ReferenceManyFieldUI>
|
|
68
103
|
</ResourceSchemaProvider>
|
|
69
104
|
</ReferenceManyFieldBase>
|
|
70
105
|
);
|
|
71
106
|
};
|
|
72
107
|
|
|
108
|
+
const ReferenceManyFieldUI = ({
|
|
109
|
+
children,
|
|
110
|
+
reference,
|
|
111
|
+
include,
|
|
112
|
+
exclude,
|
|
113
|
+
display,
|
|
114
|
+
filtering,
|
|
115
|
+
preferences,
|
|
116
|
+
title,
|
|
117
|
+
}: {
|
|
118
|
+
children?: ReactNode;
|
|
119
|
+
reference: string;
|
|
120
|
+
include?: string[];
|
|
121
|
+
exclude?: string[];
|
|
122
|
+
display?: string[];
|
|
123
|
+
filtering?: boolean;
|
|
124
|
+
preferences?: boolean | React.ReactNode;
|
|
125
|
+
title?: React.ReactNode;
|
|
126
|
+
}) => {
|
|
127
|
+
const { listComponent: ListComponent = Table } = useResourceSchema(reference);
|
|
128
|
+
|
|
129
|
+
const finalChildren = children || (
|
|
130
|
+
<ListComponent
|
|
131
|
+
include={include}
|
|
132
|
+
exclude={exclude}
|
|
133
|
+
display={display}
|
|
134
|
+
filtering={filtering}
|
|
135
|
+
preferences={preferences}
|
|
136
|
+
title={title}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return <>{finalChildren}</>;
|
|
141
|
+
};
|
|
142
|
+
|
|
73
143
|
export default ReferenceManyField;
|
|
144
|
+
ReferenceManyField.isCollectionField = true;
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import { render, cleanup } from '@testing-library/react';
|
|
2
2
|
import { vi, describe, it, expect, afterEach } from 'vitest';
|
|
3
|
-
import { useFieldValue, useRecordContext } from '@strato-admin/core';
|
|
3
|
+
import { useFieldValue, useRecordContext } from '@strato-admin/ra-core';
|
|
4
4
|
import StatusIndicatorField from './StatusIndicatorField';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
vi.mock('@strato-admin/ra-core', () => import('../__mocks__/ra-core'));
|
|
7
7
|
vi.mock('@strato-admin/core', () => import('../__mocks__/strato-core'));
|
|
8
8
|
|
|
9
9
|
// Mock Cloudscape components
|
|
10
10
|
vi.mock('@cloudscape-design/components/status-indicator', () => ({
|
|
11
11
|
default: ({ children, type, colorOverride, iconAriaLabel }: any) => (
|
|
12
|
-
<div
|
|
13
|
-
data-testid="status-indicator"
|
|
14
|
-
data-type={type}
|
|
15
|
-
data-color={colorOverride}
|
|
16
|
-
data-aria={iconAriaLabel}
|
|
17
|
-
>
|
|
12
|
+
<div data-testid="status-indicator" data-type={type} data-color={colorOverride} data-aria={iconAriaLabel}>
|
|
18
13
|
{children}
|
|
19
14
|
</div>
|
|
20
15
|
),
|
|
@@ -67,10 +62,7 @@ describe('StatusIndicatorField', () => {
|
|
|
67
62
|
(useFieldValue as any).mockReturnValue('error');
|
|
68
63
|
|
|
69
64
|
const { getByTestId } = render(
|
|
70
|
-
<StatusIndicatorField
|
|
71
|
-
source="status"
|
|
72
|
-
type={(val) => (val === 'error' ? 'error' : 'info')}
|
|
73
|
-
/>,
|
|
65
|
+
<StatusIndicatorField source="status" type={(val) => (val === 'error' ? 'error' : 'info')} />,
|
|
74
66
|
);
|
|
75
67
|
|
|
76
68
|
const indicator = getByTestId('status-indicator');
|
|
@@ -90,9 +82,7 @@ describe('StatusIndicatorField', () => {
|
|
|
90
82
|
(useRecordContext as any).mockReturnValue({});
|
|
91
83
|
(useFieldValue as any).mockReturnValue(undefined);
|
|
92
84
|
|
|
93
|
-
const { getByText } = render(
|
|
94
|
-
<StatusIndicatorField source="status" emptyText="No Status" />,
|
|
95
|
-
);
|
|
85
|
+
const { getByText } = render(<StatusIndicatorField source="status" emptyText="No Status" />);
|
|
96
86
|
|
|
97
87
|
expect(getByText('No Status')).toBeDefined();
|
|
98
88
|
});
|
|
@@ -102,9 +92,7 @@ describe('StatusIndicatorField', () => {
|
|
|
102
92
|
(useRecordContext as any).mockReturnValue(record);
|
|
103
93
|
(useFieldValue as any).mockReturnValue('custom');
|
|
104
94
|
|
|
105
|
-
const { getByTestId } = render(
|
|
106
|
-
<StatusIndicatorField source="status" type="success" colorOverride="blue" />,
|
|
107
|
-
);
|
|
95
|
+
const { getByTestId } = render(<StatusIndicatorField source="status" type="success" colorOverride="blue" />);
|
|
108
96
|
|
|
109
97
|
const indicator = getByTestId('status-indicator');
|
|
110
98
|
expect(indicator.getAttribute('data-color')).toBe('blue');
|
|
@@ -115,9 +103,7 @@ describe('StatusIndicatorField', () => {
|
|
|
115
103
|
(useRecordContext as any).mockReturnValue(record);
|
|
116
104
|
(useFieldValue as any).mockReturnValue('active');
|
|
117
105
|
|
|
118
|
-
const { getByTestId } = render(
|
|
119
|
-
<StatusIndicatorField source="status" iconAriaLabel="Active status" />,
|
|
120
|
-
);
|
|
106
|
+
const { getByTestId } = render(<StatusIndicatorField source="status" iconAriaLabel="Active status" />);
|
|
121
107
|
|
|
122
108
|
const indicator = getByTestId('status-indicator');
|
|
123
109
|
expect(indicator.getAttribute('data-aria')).toBe('Active status');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import StatusIndicator, { type StatusIndicatorProps } from '@cloudscape-design/components/status-indicator';
|
|
2
2
|
import React, { type ReactElement } from 'react';
|
|
3
|
-
import { type RaRecord, useFieldValue, useRecordContext, useTranslate } from '@strato-admin/core';
|
|
3
|
+
import { type RaRecord, useFieldValue, useRecordContext, useTranslate } from '@strato-admin/ra-core';
|
|
4
4
|
import RecordLink from '../RecordLink';
|
|
5
5
|
import { type FieldProps } from './types';
|
|
6
6
|
|
|
@@ -28,8 +28,7 @@ export interface StatusIndicatorLabelProps {
|
|
|
28
28
|
*/
|
|
29
29
|
export const StatusIndicatorLabel = (_: StatusIndicatorLabelProps) => null;
|
|
30
30
|
|
|
31
|
-
export interface StatusIndicatorFieldProps<RecordType extends RaRecord = RaRecord>
|
|
32
|
-
extends FieldProps<RecordType> {
|
|
31
|
+
export interface StatusIndicatorFieldProps<RecordType extends RaRecord = RaRecord> extends FieldProps<RecordType> {
|
|
33
32
|
/**
|
|
34
33
|
* The type of the status indicator.
|
|
35
34
|
* If provided as a string, it will be used for all values.
|
|
@@ -55,20 +54,8 @@ export interface StatusIndicatorFieldProps<RecordType extends RaRecord = RaRecor
|
|
|
55
54
|
children?: React.ReactNode;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
const StatusIndicatorField = <RecordType extends RaRecord = RaRecord>(
|
|
59
|
-
|
|
60
|
-
) => {
|
|
61
|
-
const {
|
|
62
|
-
source,
|
|
63
|
-
record: recordProp,
|
|
64
|
-
emptyText,
|
|
65
|
-
link,
|
|
66
|
-
type,
|
|
67
|
-
mapping,
|
|
68
|
-
iconAriaLabel,
|
|
69
|
-
colorOverride,
|
|
70
|
-
children,
|
|
71
|
-
} = props;
|
|
57
|
+
const StatusIndicatorField = <RecordType extends RaRecord = RaRecord>(props: StatusIndicatorFieldProps<RecordType>) => {
|
|
58
|
+
const { source, record: recordProp, emptyText, link, type, mapping, iconAriaLabel, colorOverride, children } = props;
|
|
72
59
|
|
|
73
60
|
const record = useRecordContext<RecordType>({ record: recordProp });
|
|
74
61
|
const value = useFieldValue<RecordType>({ source: source as any, record });
|
|
@@ -93,9 +80,10 @@ const StatusIndicatorField = <RecordType extends RaRecord = RaRecord>(
|
|
|
93
80
|
finalColorOverride = matchingLabel.props.color;
|
|
94
81
|
}
|
|
95
82
|
if (matchingLabel.props.label) {
|
|
96
|
-
label =
|
|
97
|
-
|
|
98
|
-
|
|
83
|
+
label =
|
|
84
|
+
typeof matchingLabel.props.label === 'string'
|
|
85
|
+
? translate(matchingLabel.props.label)
|
|
86
|
+
: matchingLabel.props.label;
|
|
99
87
|
}
|
|
100
88
|
} else if (typeof type === 'function') {
|
|
101
89
|
statusType = type(value, record as RecordType);
|
package/src/field/TextField.tsx
CHANGED
package/src/field/types.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type BaseFieldProps, type RaRecord } from '@strato-admin/core';
|
|
2
|
+
import { ExtractRecordPaths, type BaseFieldProps, type RaRecord } from '@strato-admin/ra-core';
|
|
3
3
|
import { type RecordLinkType } from '../RecordLink';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Common props for all field components in strato-cloudscape.
|
|
7
7
|
*/
|
|
8
|
-
export interface FieldProps<RecordType extends RaRecord = RaRecord>
|
|
9
|
-
extends Omit<BaseFieldProps<RecordType>, 'source'> {
|
|
8
|
+
export interface FieldProps<RecordType extends RaRecord = RaRecord> extends Omit<BaseFieldProps<RecordType>, 'source'> {
|
|
10
9
|
/**
|
|
11
10
|
* The property name in the record that should be displayed.
|
|
12
11
|
*/
|
|
13
|
-
source?:
|
|
12
|
+
source?: ExtractRecordPaths<RecordType>;
|
|
14
13
|
/**
|
|
15
|
-
* The label to display for this field.
|
|
14
|
+
* The label to display for this field. If not provided, it will be inferred from the `source`.
|
|
16
15
|
*/
|
|
17
|
-
label?: ReactNode;
|
|
16
|
+
label?: string | ReactNode;
|
|
18
17
|
/**
|
|
19
18
|
* Whether the field is sortable in a table.
|
|
20
19
|
* @default true
|
|
@@ -22,8 +21,8 @@ export interface FieldProps<RecordType extends RaRecord = RaRecord>
|
|
|
22
21
|
sortable?: boolean;
|
|
23
22
|
/**
|
|
24
23
|
* Whether to link the field to another page.
|
|
25
|
-
* - true: links to the '
|
|
26
|
-
* - 'edit' | '
|
|
24
|
+
* - true: links to the 'detail' page of the current resource
|
|
25
|
+
* - 'edit' | 'detail': links to the specified page type
|
|
27
26
|
* - string: a custom URL
|
|
28
27
|
* - function: (record, resource) => string
|
|
29
28
|
*/
|
|
@@ -31,7 +30,7 @@ export interface FieldProps<RecordType extends RaRecord = RaRecord>
|
|
|
31
30
|
/**
|
|
32
31
|
* The text to display if the value is empty or null.
|
|
33
32
|
*/
|
|
34
|
-
emptyText?: ReactNode;
|
|
33
|
+
emptyText?: string | ReactNode;
|
|
35
34
|
/**
|
|
36
35
|
* Configuration for the inferred form input.
|
|
37
36
|
* - object: Props passed to the inferred Input component.
|
|
@@ -40,8 +39,8 @@ export interface FieldProps<RecordType extends RaRecord = RaRecord>
|
|
|
40
39
|
*/
|
|
41
40
|
input?: Record<string, any> | React.ReactElement | false;
|
|
42
41
|
/**
|
|
43
|
-
* Whether the field is required.
|
|
44
|
-
* This is used to automatically add validation to the inferred input
|
|
42
|
+
* Whether the field is required.
|
|
43
|
+
* This is used to automatically add validation to the inferred input
|
|
45
44
|
* and potentially show warnings in display views.
|
|
46
45
|
*/
|
|
47
46
|
isRequired?: boolean;
|
|
@@ -49,10 +48,10 @@ export interface FieldProps<RecordType extends RaRecord = RaRecord>
|
|
|
49
48
|
* Additional text to help the user fill in the field.
|
|
50
49
|
* Passed to the inferred Input component's FormField.
|
|
51
50
|
*/
|
|
52
|
-
description?: ReactNode;
|
|
51
|
+
description?: string | ReactNode;
|
|
53
52
|
/**
|
|
54
53
|
* Text describing constraints (e.g., "Must be between 1 and 100").
|
|
55
54
|
* Passed to the inferred Input component's FormField.
|
|
56
55
|
*/
|
|
57
|
-
constraintText?: ReactNode;
|
|
56
|
+
constraintText?: string | ReactNode;
|
|
58
57
|
}
|
package/src/form/Form.test.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
4
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
4
5
|
import { Form } from './Form';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
vi.mock('@strato-admin/ra-core', () => import('../__mocks__/ra-core'));
|
|
7
8
|
vi.mock('@strato-admin/core', () => import('../__mocks__/strato-core'));
|
|
8
9
|
|
|
9
10
|
// Mock Cloudscape components
|
|
@@ -31,14 +32,17 @@ describe('Form', () => {
|
|
|
31
32
|
|
|
32
33
|
it('should render children and default toolbar', () => {
|
|
33
34
|
const { getByTestId, getByText } = render(
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
35
|
+
<MemoryRouter>
|
|
36
|
+
<Form>
|
|
37
|
+
<div data-testid="child" />
|
|
38
|
+
</Form>
|
|
39
|
+
</MemoryRouter>,
|
|
37
40
|
);
|
|
38
41
|
|
|
39
42
|
expect(getByTestId('ra-form')).toBeDefined();
|
|
40
43
|
expect(getByTestId('cloudscape-form')).toBeDefined();
|
|
41
44
|
expect(getByTestId('child')).toBeDefined();
|
|
45
|
+
expect(getByText('Cancel')).toBeDefined();
|
|
42
46
|
expect(getByText('Save')).toBeDefined();
|
|
43
47
|
});
|
|
44
48
|
|
package/src/form/Form.tsx
CHANGED
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Form as RaForm, type FormProps as RaFormProps, useSaveContext,
|
|
2
|
+
import { Form as RaForm, type FormProps as RaFormProps, useSaveContext, useRecordContext } from '@strato-admin/ra-core';
|
|
3
|
+
import { useSchemaFields } from '../hooks/useSchemaFields';
|
|
3
4
|
import CloudscapeForm from '@cloudscape-design/components/form';
|
|
4
5
|
import SpaceBetween from '@cloudscape-design/components/space-between';
|
|
5
6
|
import { SaveButton } from '../button/SaveButton';
|
|
7
|
+
import { CancelButton } from '../button/CancelButton';
|
|
6
8
|
import { FormField } from '../input/FormField';
|
|
7
9
|
|
|
8
10
|
export interface FormProps extends Omit<RaFormProps, 'children'> {
|
|
9
11
|
children?: React.ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* List of field sources to include in the form.
|
|
14
|
+
*/
|
|
10
15
|
include?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* List of field sources to exclude from the form.
|
|
18
|
+
*/
|
|
11
19
|
exclude?: string[];
|
|
12
20
|
toolbar?: React.ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* Label for the save button.
|
|
23
|
+
*/
|
|
24
|
+
saveButtonLabel?: string;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
|
-
export const Form = ({ children, include, exclude, toolbar, ...props }: FormProps) => {
|
|
27
|
+
export const Form = ({ children, include, exclude, toolbar, saveButtonLabel, ...props }: FormProps) => {
|
|
16
28
|
const saveContext = useSaveContext();
|
|
17
|
-
const
|
|
29
|
+
const record = useRecordContext();
|
|
30
|
+
const isEditMode = record?.id != null;
|
|
18
31
|
|
|
19
|
-
const
|
|
20
|
-
const baseChildren = children || schemaChildren;
|
|
21
|
-
let result = React.Children.toArray(baseChildren);
|
|
32
|
+
const { getEditFields, getCreateFields } = useSchemaFields();
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
result = result.filter(
|
|
29
|
-
(child) => React.isValidElement(child) && !exclude.includes((child.props as any).source)
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return result;
|
|
34
|
-
}, [children, schemaChildren, include, exclude]);
|
|
34
|
+
const finalChildren = React.useMemo(
|
|
35
|
+
() =>
|
|
36
|
+
isEditMode ? getEditFields(children, { include, exclude }) : getCreateFields(children, { include, exclude }),
|
|
37
|
+
[isEditMode, getEditFields, getCreateFields, children, include, exclude],
|
|
38
|
+
);
|
|
35
39
|
|
|
36
40
|
const handleSubmit = async (values: any, event: any) => {
|
|
37
41
|
if (props.onSubmit) {
|
|
@@ -48,7 +52,8 @@ export const Form = ({ children, include, exclude, toolbar, ...props }: FormProp
|
|
|
48
52
|
actions={
|
|
49
53
|
toolbar || (
|
|
50
54
|
<SpaceBetween direction="horizontal" size="xs">
|
|
51
|
-
<
|
|
55
|
+
<CancelButton />
|
|
56
|
+
<SaveButton label={saveButtonLabel} />
|
|
52
57
|
</SpaceBetween>
|
|
53
58
|
)
|
|
54
59
|
}
|
package/src/form/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './Form';
|
|
2
|
-
export { ValidationError } from '@strato-admin/core';
|
|
2
|
+
export { ValidationError } from '@strato-admin/ra-core';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useResourceSchema } from '@strato-admin/core';
|
|
3
|
+
|
|
4
|
+
const noCollectionFields = (child: React.ReactElement) => !(child.type as any).isCollectionField;
|
|
5
|
+
|
|
6
|
+
function filterFields(
|
|
7
|
+
base: React.ReactNode,
|
|
8
|
+
options: {
|
|
9
|
+
include?: string[];
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
defaultFilter?: (child: React.ReactElement) => boolean;
|
|
12
|
+
} = {},
|
|
13
|
+
): React.ReactElement[] {
|
|
14
|
+
const { include, exclude, defaultFilter } = options;
|
|
15
|
+
let result = React.Children.toArray(base) as React.ReactElement[];
|
|
16
|
+
|
|
17
|
+
if (include) {
|
|
18
|
+
return result.filter((child) => React.isValidElement(child) && include.includes((child.props as any).source));
|
|
19
|
+
}
|
|
20
|
+
if (defaultFilter) {
|
|
21
|
+
result = result.filter((child) => React.isValidElement(child) && defaultFilter(child));
|
|
22
|
+
}
|
|
23
|
+
if (exclude) {
|
|
24
|
+
result = result.filter((child) => React.isValidElement(child) && !exclude.includes((child.props as any).source));
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useSchemaFields() {
|
|
30
|
+
const {
|
|
31
|
+
fieldSchema,
|
|
32
|
+
inputSchema,
|
|
33
|
+
listInclude,
|
|
34
|
+
listExclude,
|
|
35
|
+
detailInclude,
|
|
36
|
+
detailExclude,
|
|
37
|
+
formInclude,
|
|
38
|
+
formExclude,
|
|
39
|
+
editInclude,
|
|
40
|
+
editExclude,
|
|
41
|
+
createInclude,
|
|
42
|
+
createExclude,
|
|
43
|
+
} = useResourceSchema();
|
|
44
|
+
|
|
45
|
+
const getListFields = React.useCallback(
|
|
46
|
+
(children: React.ReactNode, opts: { include?: string[]; exclude?: string[] } = {}) =>
|
|
47
|
+
filterFields(children || fieldSchema, {
|
|
48
|
+
include: opts.include || listInclude,
|
|
49
|
+
exclude: opts.exclude || listExclude,
|
|
50
|
+
defaultFilter: noCollectionFields,
|
|
51
|
+
}),
|
|
52
|
+
[fieldSchema, listInclude, listExclude],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const getDetailFields = React.useCallback(
|
|
56
|
+
(children: React.ReactNode, opts: { include?: string[]; exclude?: string[] } = {}) => {
|
|
57
|
+
const hasExplicit = React.Children.count(children) > 0;
|
|
58
|
+
const all = filterFields(children || fieldSchema, {
|
|
59
|
+
include: hasExplicit ? undefined : opts.include || detailInclude,
|
|
60
|
+
exclude: hasExplicit ? undefined : opts.exclude || detailExclude,
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
scalarFields: all.filter((c) => !(c.type as any).isCollectionField),
|
|
64
|
+
collectionFields: all.filter((c) => (c.type as any).isCollectionField),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
[fieldSchema, detailInclude, detailExclude],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const getEditFields = React.useCallback(
|
|
71
|
+
(children: React.ReactNode, opts: { include?: string[]; exclude?: string[] } = {}) =>
|
|
72
|
+
filterFields(children || inputSchema, {
|
|
73
|
+
include: opts.include || editInclude || formInclude,
|
|
74
|
+
exclude: opts.exclude || editExclude || formExclude,
|
|
75
|
+
}),
|
|
76
|
+
[inputSchema, editInclude, editExclude, formInclude, formExclude],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const getCreateFields = React.useCallback(
|
|
80
|
+
(children: React.ReactNode, opts: { include?: string[]; exclude?: string[] } = {}) =>
|
|
81
|
+
filterFields(children || inputSchema, {
|
|
82
|
+
include: opts.include || createInclude || formInclude,
|
|
83
|
+
exclude: opts.exclude || createExclude || formExclude,
|
|
84
|
+
}),
|
|
85
|
+
[inputSchema, createInclude, createExclude, formInclude, formExclude],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return { getListFields, getDetailFields, getEditFields, getCreateFields };
|
|
89
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useTranslate } from '@strato-admin/ra-core';
|
|
2
|
+
import { MessageProps } from './types';
|
|
3
|
+
|
|
4
|
+
export type { MessageProps };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders a translatable string inline. The children text is the English source
|
|
8
|
+
* string and serves as both the translation key and the fallback value.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* <Message>Product Name</Message>
|
|
12
|
+
* <Message id="resources.products.actions.archive">Archive</Message>
|
|
13
|
+
* <Message context="verb">Archive</Message>
|
|
14
|
+
* <Message context="noun">Archive</Message>
|
|
15
|
+
* <Message comment="Appears in the confirmation dialog">Are you sure?</Message>
|
|
16
|
+
* <Message count={n}>{'Found {count, plural, one {# result} other {# results}}'}</Message>
|
|
17
|
+
*/
|
|
18
|
+
export const Message = ({ children, id, context, comment: _comment, vars }: MessageProps) => {
|
|
19
|
+
const translate = useTranslate();
|
|
20
|
+
const key = id ?? (context ? `${context}\x04${children}` : children);
|
|
21
|
+
return <>{translate(key, { ...vars, _: children })}</>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useTranslate, useRecordContext } from '@strato-admin/ra-core';
|
|
2
|
+
import { MessageProps } from './types';
|
|
3
|
+
|
|
4
|
+
export interface RecordMessageProps extends MessageProps {
|
|
5
|
+
/** Explicit record to use instead of the one from context. */
|
|
6
|
+
record?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders a translatable string with all fields of the current record available
|
|
11
|
+
* as ICU variables. Must be used inside a record context (Detail, Edit, etc.).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <RecordMessage>{'Product: {name}'}</RecordMessage>
|
|
15
|
+
* <RecordMessage>{'Order #{id} — {status}'}</RecordMessage>
|
|
16
|
+
*/
|
|
17
|
+
export const RecordMessage = ({ children, id, context, comment: _comment, record, vars }: RecordMessageProps) => {
|
|
18
|
+
const translate = useTranslate();
|
|
19
|
+
const resolvedRecord = useRecordContext({ record }) ?? {};
|
|
20
|
+
const key = id ?? (context ? `${context}\x04${children}` : children);
|
|
21
|
+
return <>{translate(key, { ...resolvedRecord, ...vars, _: children })}</>;
|
|
22
|
+
};
|