@axinom/mosaic-ui 0.63.0-rc.4 → 0.63.0-rc.5
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/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +1 -1
- package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.model.d.ts +1 -1
- package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
- package/dist/components/FieldSelection/FieldSelection.d.ts +2 -0
- package/dist/components/FieldSelection/FieldSelection.d.ts.map +1 -1
- package/dist/components/FormStation/FormStation.d.ts +11 -1
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +1 -0
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
- package/dist/components/FormStation/helpers/useDataProvider.d.ts +1 -1
- package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -1
- package/dist/index.es.js +2 -2
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +22 -18
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +59 -21
- package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +5 -8
- package/src/components/Explorer/Explorer.model.ts +1 -1
- package/src/components/FieldSelection/FieldSelection.tsx +7 -0
- package/src/components/FormStation/FormStation.tsx +14 -1
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +4 -1
- package/src/components/FormStation/helpers/useDataProvider.ts +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.63.0-rc.
|
|
3
|
+
"version": "0.63.0-rc.5",
|
|
4
4
|
"description": "UI components for building Axinom Mosaic applications",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"publishConfig": {
|
|
113
113
|
"access": "public"
|
|
114
114
|
},
|
|
115
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "61dacf2b863f576ca0680aaa3431b5a195686e7e"
|
|
116
116
|
}
|
|
@@ -16,12 +16,22 @@ jest.mock('../../FormElements', () => ({
|
|
|
16
16
|
)),
|
|
17
17
|
}));
|
|
18
18
|
|
|
19
|
+
jest.mock('../../FieldSelection', () => ({
|
|
20
|
+
FieldSelection: jest.fn(({ children }) => (
|
|
21
|
+
<div data-testid="FieldSelection">{children}</div>
|
|
22
|
+
)),
|
|
23
|
+
}));
|
|
24
|
+
|
|
19
25
|
jest.mock('formik', () => ({
|
|
20
26
|
Field: jest.fn(({ name, label, as: Component }) => (
|
|
21
27
|
<div data-testid={`Field-${name}`}>
|
|
22
28
|
<Component name={name} label={label} />
|
|
23
29
|
</div>
|
|
24
30
|
)),
|
|
31
|
+
useFormikContext: jest.fn(() => ({
|
|
32
|
+
setFieldValue: jest.fn(),
|
|
33
|
+
setFieldTouched: jest.fn(),
|
|
34
|
+
})),
|
|
25
35
|
}));
|
|
26
36
|
|
|
27
37
|
describe('BulkEditFormFieldsConfigConverter', () => {
|
|
@@ -35,9 +45,7 @@ describe('BulkEditFormFieldsConfigConverter', () => {
|
|
|
35
45
|
},
|
|
36
46
|
};
|
|
37
47
|
|
|
38
|
-
const { getByTestId } = render(
|
|
39
|
-
<>{BulkEditFormFieldsConfigConverter(config)}</>,
|
|
40
|
-
);
|
|
48
|
+
const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));
|
|
41
49
|
|
|
42
50
|
expect(getByTestId('Field-title')).toBeInTheDocument();
|
|
43
51
|
expect(getByTestId('SingleLineTextField')).toHaveTextContent('title-Title');
|
|
@@ -53,9 +61,7 @@ describe('BulkEditFormFieldsConfigConverter', () => {
|
|
|
53
61
|
},
|
|
54
62
|
};
|
|
55
63
|
|
|
56
|
-
const { getByTestId } = render(
|
|
57
|
-
<>{BulkEditFormFieldsConfigConverter(config)}</>,
|
|
58
|
-
);
|
|
64
|
+
const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));
|
|
59
65
|
|
|
60
66
|
expect(getByTestId('Field-isArchived')).toBeInTheDocument();
|
|
61
67
|
expect(getByTestId('CheckboxField')).toHaveTextContent(
|
|
@@ -77,9 +83,7 @@ describe('BulkEditFormFieldsConfigConverter', () => {
|
|
|
77
83
|
},
|
|
78
84
|
};
|
|
79
85
|
|
|
80
|
-
const { getByTestId } = render(
|
|
81
|
-
<>{BulkEditFormFieldsConfigConverter(config)}</>,
|
|
82
|
-
);
|
|
86
|
+
const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));
|
|
83
87
|
|
|
84
88
|
expect(getByTestId('Field-tags')).toBeInTheDocument();
|
|
85
89
|
expect(getByTestId('CustomTagsField')).toHaveTextContent('tags-Tags');
|
|
@@ -111,9 +115,7 @@ describe('BulkEditFormFieldsConfigConverter', () => {
|
|
|
111
115
|
},
|
|
112
116
|
};
|
|
113
117
|
|
|
114
|
-
const { getByTestId } = render(
|
|
115
|
-
<>{BulkEditFormFieldsConfigConverter(config)}</>,
|
|
116
|
-
);
|
|
118
|
+
const { getByTestId } = render(BulkEditFormFieldsConfigConverter(config));
|
|
117
119
|
|
|
118
120
|
expect(getByTestId('Field-title')).toBeInTheDocument();
|
|
119
121
|
expect(getByTestId('SingleLineTextField')).toHaveTextContent('title-Title');
|
|
@@ -138,14 +140,15 @@ describe('BulkEditFormFieldsConfigConverter', () => {
|
|
|
138
140
|
},
|
|
139
141
|
};
|
|
140
142
|
|
|
141
|
-
const { container } = render(
|
|
142
|
-
|
|
143
|
+
const { container, getByTestId } = render(
|
|
144
|
+
BulkEditFormFieldsConfigConverter(config),
|
|
143
145
|
);
|
|
144
146
|
|
|
145
147
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
146
148
|
'No component found for field type: UnsupportedType',
|
|
147
149
|
);
|
|
148
|
-
expect(
|
|
150
|
+
expect(getByTestId('FieldSelection')).toBeInTheDocument();
|
|
151
|
+
expect(container.firstChild).toBeEmptyDOMElement();
|
|
149
152
|
|
|
150
153
|
consoleWarnSpy.mockRestore();
|
|
151
154
|
});
|
|
@@ -153,10 +156,11 @@ describe('BulkEditFormFieldsConfigConverter', () => {
|
|
|
153
156
|
it('handles empty configuration gracefully', () => {
|
|
154
157
|
const config: BulkEditFieldConfigMap = {};
|
|
155
158
|
|
|
156
|
-
const { container } = render(
|
|
157
|
-
|
|
159
|
+
const { container, getByTestId } = render(
|
|
160
|
+
BulkEditFormFieldsConfigConverter(config),
|
|
158
161
|
);
|
|
159
162
|
|
|
160
|
-
expect(
|
|
163
|
+
expect(getByTestId('FieldSelection')).toBeInTheDocument();
|
|
164
|
+
expect(container.firstChild).toBeEmptyDOMElement();
|
|
161
165
|
});
|
|
162
166
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Field } from 'formik';
|
|
2
|
-
import React from 'react';
|
|
1
|
+
import { Field, useFormikContext } from 'formik';
|
|
2
|
+
import React, { useEffect, useMemo } from 'react';
|
|
3
|
+
import { Data } from '../../../types';
|
|
4
|
+
import { FieldSelection } from '../../FieldSelection';
|
|
3
5
|
import {
|
|
4
6
|
CheckboxField,
|
|
5
7
|
CustomTagsField,
|
|
@@ -17,29 +19,65 @@ export const defaultComponentMap = {
|
|
|
17
19
|
export const BulkEditFormFieldsConfigConverter = (
|
|
18
20
|
config: BulkEditFieldConfigMap,
|
|
19
21
|
componentMap: Record<string, React.ElementType> = defaultComponentMap,
|
|
20
|
-
): JSX.Element
|
|
22
|
+
): JSX.Element => {
|
|
21
23
|
const keys = Object.keys(config);
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const fieldConfig = config[key];
|
|
25
|
+
const FormFields: React.FC = () => {
|
|
26
|
+
const { setFieldValue, setFieldTouched, values } = useFormikContext<Data>();
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const onFieldRemoved = (field: string): void => {
|
|
29
|
+
setFieldValue(field, undefined); // Clear the field value when removed
|
|
30
|
+
setFieldTouched(field, false); // Mark the field as not touched
|
|
31
|
+
};
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
// Effect to clear empty fields
|
|
34
|
+
// This will set fields with empty strings or empty arrays to undefined
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
values &&
|
|
37
|
+
Object.keys(values).forEach((key) => {
|
|
38
|
+
if (values[key] === '' || values[key].length === 0) {
|
|
39
|
+
setFieldValue(key, undefined);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}, [setFieldValue, values]);
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
const fields = useMemo(
|
|
45
|
+
() =>
|
|
46
|
+
keys
|
|
47
|
+
.map((key) => {
|
|
48
|
+
const fieldConfig = config[key];
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
// Determine the type of the field
|
|
51
|
+
const fieldType = Array.isArray(fieldConfig.type)
|
|
52
|
+
? 'Array' // Use 'Array' as the key for array types
|
|
53
|
+
: fieldConfig.type;
|
|
54
|
+
|
|
55
|
+
const Component =
|
|
56
|
+
componentMap[fieldType as keyof typeof componentMap];
|
|
57
|
+
|
|
58
|
+
if (!Component) {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.warn(`No component found for field type: ${fieldType}`);
|
|
61
|
+
return null; // Filter out null entries later
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Field
|
|
66
|
+
name={key}
|
|
67
|
+
key={key}
|
|
68
|
+
label={fieldConfig.label}
|
|
69
|
+
as={Component}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
})
|
|
73
|
+
.filter((element): element is JSX.Element => element !== null),
|
|
74
|
+
[],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<FieldSelection onFieldRemoved={onFieldRemoved}>{fields}</FieldSelection>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return <FormFields />; // Return the FormFields component
|
|
45
83
|
};
|
|
@@ -6,7 +6,6 @@ import React, {
|
|
|
6
6
|
useState,
|
|
7
7
|
} from 'react';
|
|
8
8
|
import { Data } from '../../../types';
|
|
9
|
-
import { FieldSelection } from '../../FieldSelection';
|
|
10
9
|
import { FormStation } from '../../FormStation';
|
|
11
10
|
import { IconName } from '../../Icons';
|
|
12
11
|
import {
|
|
@@ -74,7 +73,7 @@ export const useBulkEdit = <T extends Data>({
|
|
|
74
73
|
() =>
|
|
75
74
|
bulkEditRegistration
|
|
76
75
|
? {
|
|
77
|
-
label: bulkEditRegistration.label,
|
|
76
|
+
label: bulkEditRegistration.label ?? 'Bulk Edit',
|
|
78
77
|
icon: bulkEditRegistration.icon ?? IconName.BulkEdit,
|
|
79
78
|
onClick: () => setIsBulkEditMode((prev) => !prev),
|
|
80
79
|
}
|
|
@@ -93,12 +92,8 @@ export const useBulkEdit = <T extends Data>({
|
|
|
93
92
|
if (bulkEditRegistration?.component) {
|
|
94
93
|
return bulkEditRegistration.component;
|
|
95
94
|
} else if (bulkEditRegistration?.config) {
|
|
96
|
-
return (
|
|
97
|
-
|
|
98
|
-
{BulkEditFormFieldsConfigConverter(
|
|
99
|
-
bulkEditRegistration?.config.fields,
|
|
100
|
-
)}
|
|
101
|
-
</FieldSelection>
|
|
95
|
+
return BulkEditFormFieldsConfigConverter(
|
|
96
|
+
bulkEditRegistration?.config.fields,
|
|
102
97
|
);
|
|
103
98
|
}
|
|
104
99
|
return null;
|
|
@@ -116,6 +111,8 @@ export const useBulkEdit = <T extends Data>({
|
|
|
116
111
|
: undefined
|
|
117
112
|
}
|
|
118
113
|
showSaveHeaderAction={!noItemsSelected}
|
|
114
|
+
saveHeaderActionTitle="Apply"
|
|
115
|
+
saveNotificationMessage="Your changes are being applied to the selected items."
|
|
119
116
|
>
|
|
120
117
|
{BulkEditContent}
|
|
121
118
|
</FormStation>
|
|
@@ -127,7 +127,7 @@ export interface QuickEditRegistration<T> {
|
|
|
127
127
|
|
|
128
128
|
export interface BulkEditRegistration<T extends Data> {
|
|
129
129
|
/** The label of the action. */
|
|
130
|
-
label
|
|
130
|
+
label?: string;
|
|
131
131
|
/** Optional built in icon. This prop also accepts an img src. */
|
|
132
132
|
icon?: IconName | string;
|
|
133
133
|
/** Component to render. This will override the component that is generated. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
+
import { noop } from '../../helpers/utils';
|
|
2
3
|
import { Accordion, AccordionItem } from '../Accordion';
|
|
3
4
|
import { Button, ButtonContext } from '../Buttons';
|
|
4
5
|
import { Select } from '../FormElements';
|
|
@@ -8,6 +9,8 @@ import classes from './FieldSelection.scss';
|
|
|
8
9
|
|
|
9
10
|
interface FieldSelectionProps {
|
|
10
11
|
className?: string;
|
|
12
|
+
onFieldAdded?: (field: string) => void;
|
|
13
|
+
onFieldRemoved?: (field: string) => void;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
interface FieldDefinition {
|
|
@@ -19,6 +22,8 @@ interface FieldDefinition {
|
|
|
19
22
|
|
|
20
23
|
export const FieldSelection: React.FC<FieldSelectionProps> = ({
|
|
21
24
|
className,
|
|
25
|
+
onFieldAdded = noop,
|
|
26
|
+
onFieldRemoved = noop,
|
|
22
27
|
children,
|
|
23
28
|
}) => {
|
|
24
29
|
useEffect(() => {
|
|
@@ -70,6 +75,7 @@ export const FieldSelection: React.FC<FieldSelectionProps> = ({
|
|
|
70
75
|
...currentFields,
|
|
71
76
|
newField,
|
|
72
77
|
]);
|
|
78
|
+
onFieldAdded(newField.value);
|
|
73
79
|
}
|
|
74
80
|
}}
|
|
75
81
|
/>
|
|
@@ -88,6 +94,7 @@ export const FieldSelection: React.FC<FieldSelectionProps> = ({
|
|
|
88
94
|
setAvailableFields((currentFields) =>
|
|
89
95
|
[...currentFields, field].sort((a, b) => a.index - b.index),
|
|
90
96
|
);
|
|
97
|
+
onFieldRemoved(field.value);
|
|
91
98
|
}}
|
|
92
99
|
/>
|
|
93
100
|
}
|
|
@@ -39,6 +39,16 @@ export interface FormStationProps<
|
|
|
39
39
|
actionsWidth?: string;
|
|
40
40
|
/** If set to true, the save header action is shown. (default: true) */
|
|
41
41
|
showSaveHeaderAction?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* If set, this will override the default title of the save header action.
|
|
44
|
+
* This is useful when you want to have a custom title for the save action.
|
|
45
|
+
*/
|
|
46
|
+
saveHeaderActionTitle?: string;
|
|
47
|
+
/**
|
|
48
|
+
* If set, this will override the default notification message shown
|
|
49
|
+
* after a successful save.
|
|
50
|
+
*/
|
|
51
|
+
saveNotificationMessage?: string;
|
|
42
52
|
/**
|
|
43
53
|
* An object containing the initial data of the form.
|
|
44
54
|
*/
|
|
@@ -91,6 +101,8 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
91
101
|
className = '',
|
|
92
102
|
setTabTitle = true,
|
|
93
103
|
showSaveHeaderAction = true,
|
|
104
|
+
saveHeaderActionTitle,
|
|
105
|
+
saveNotificationMessage,
|
|
94
106
|
}: PropsWithChildren<
|
|
95
107
|
FormStationProps<TValues, TSubmitResponse>
|
|
96
108
|
>): JSX.Element => {
|
|
@@ -103,7 +115,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
103
115
|
setStationError,
|
|
104
116
|
isFormSubmitting,
|
|
105
117
|
lastSubmittedResponse,
|
|
106
|
-
} = useDataProvider(initialData, saveData);
|
|
118
|
+
} = useDataProvider(initialData, saveData, saveNotificationMessage);
|
|
107
119
|
|
|
108
120
|
const { setValidationError, validationWatcher } = useValidationError(
|
|
109
121
|
stationError,
|
|
@@ -137,6 +149,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
137
149
|
className={classes.header}
|
|
138
150
|
setTabTitle={setTabTitle}
|
|
139
151
|
showSaveHeaderAction={showSaveHeaderAction}
|
|
152
|
+
saveHeaderActionTitle={saveHeaderActionTitle}
|
|
140
153
|
/>
|
|
141
154
|
{!bulkEditContext && (
|
|
142
155
|
<SaveOnNavigate
|
|
@@ -22,6 +22,7 @@ export const FormStationHeader: React.FC<
|
|
|
22
22
|
cancelNavigationUrl?: string;
|
|
23
23
|
setTabTitle?: boolean;
|
|
24
24
|
showSaveHeaderAction?: boolean;
|
|
25
|
+
saveHeaderActionTitle?: string;
|
|
25
26
|
}
|
|
26
27
|
> = ({
|
|
27
28
|
titleProperty,
|
|
@@ -31,6 +32,7 @@ export const FormStationHeader: React.FC<
|
|
|
31
32
|
className,
|
|
32
33
|
setTabTitle,
|
|
33
34
|
showSaveHeaderAction,
|
|
35
|
+
saveHeaderActionTitle = 'Save',
|
|
34
36
|
}) => {
|
|
35
37
|
const { dirty, submitForm, resetForm } = useFormikContext<FormikValues>();
|
|
36
38
|
const quickEditContext = useContext(QuickEditContext);
|
|
@@ -56,7 +58,7 @@ export const FormStationHeader: React.FC<
|
|
|
56
58
|
|
|
57
59
|
if (showSaveHeaderAction) {
|
|
58
60
|
actionItems.push({
|
|
59
|
-
label:
|
|
61
|
+
label: saveHeaderActionTitle,
|
|
60
62
|
icon: IconName.Save,
|
|
61
63
|
kind: 'action',
|
|
62
64
|
actionType: PageHeaderActionType.Context,
|
|
@@ -116,6 +118,7 @@ export const FormStationHeader: React.FC<
|
|
|
116
118
|
history,
|
|
117
119
|
quickEditContext,
|
|
118
120
|
resetForm,
|
|
121
|
+
saveHeaderActionTitle,
|
|
119
122
|
showSaveHeaderAction,
|
|
120
123
|
submitForm,
|
|
121
124
|
]);
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
export type FormStationDataProvider = <TValues extends Data, TSubmitResponse>(
|
|
21
21
|
initialData: InitialFormData<TValues>,
|
|
22
22
|
saveData: SaveDataFunction<TValues, TSubmitResponse>,
|
|
23
|
+
saveNotificationMessage?: string,
|
|
23
24
|
) => {
|
|
24
25
|
onSubmit: (
|
|
25
26
|
values: TValues,
|
|
@@ -38,6 +39,7 @@ export const useDataProvider: FormStationDataProvider = <
|
|
|
38
39
|
>(
|
|
39
40
|
initialData: InitialFormData<TValues>,
|
|
40
41
|
saveData: SaveDataFunction<TValues, TSubmitResponse>,
|
|
42
|
+
saveNotificationMessage?: string,
|
|
41
43
|
) => {
|
|
42
44
|
const [stationError, setStationError] = useState<StationErrorStateType>();
|
|
43
45
|
const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
|
|
@@ -92,7 +94,9 @@ export const useDataProvider: FormStationDataProvider = <
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
showNotification({
|
|
95
|
-
title:
|
|
97
|
+
title:
|
|
98
|
+
saveNotificationMessage ??
|
|
99
|
+
'Your changes were saved successfully.',
|
|
96
100
|
options: {
|
|
97
101
|
type: 'success',
|
|
98
102
|
autoClose: 1500,
|
|
@@ -115,7 +119,7 @@ export const useDataProvider: FormStationDataProvider = <
|
|
|
115
119
|
setIsFormSubmitting(false);
|
|
116
120
|
}
|
|
117
121
|
},
|
|
118
|
-
[isFormSubmitting, initialData, saveData,
|
|
122
|
+
[isFormSubmitting, initialData, saveData, saveNotificationMessage],
|
|
119
123
|
);
|
|
120
124
|
|
|
121
125
|
return {
|