@axinom/mosaic-ui 0.63.0-rc.1 → 0.63.0-rc.10
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/Accordion/Accordion.d.ts.map +1 -1
- 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.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/FormElements/DateTimeField/DateTimeText.d.ts +1 -1
- package/dist/components/FormElements/DateTimeField/DateTimeText.d.ts.map +1 -1
- package/dist/components/FormElements/DateTimeField/DateTimeTextField.d.ts.map +1 -1
- package/dist/components/FormStation/FormStation.d.ts +12 -1
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +3 -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/components/Icons/Icons.d.ts.map +1 -1
- package/dist/components/Icons/Icons.models.d.ts +2 -1
- package/dist/components/Icons/Icons.models.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +2 -0
- package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -1
- package/dist/index.es.js +4 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Accordion/Accordion.scss +14 -4
- package/src/components/Accordion/Accordion.spec.tsx +44 -64
- package/src/components/Accordion/Accordion.stories.tsx +8 -0
- package/src/components/Accordion/Accordion.tsx +9 -3
- package/src/components/Accordion/AccordionItem/AccordionItem.spec.tsx +46 -84
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +22 -18
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +94 -20
- package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +6 -8
- package/src/components/Explorer/Explorer.model.ts +1 -1
- package/src/components/Explorer/Explorer.tsx +1 -0
- package/src/components/Explorer/helpers/useActions.ts +1 -1
- package/src/components/FieldSelection/FieldSelection.scss +7 -0
- package/src/components/FieldSelection/FieldSelection.spec.tsx +0 -2
- package/src/components/FieldSelection/FieldSelection.tsx +13 -11
- package/src/components/FormElements/DateTimeField/DateTimeText.tsx +8 -14
- package/src/components/FormElements/DateTimeField/DateTimeTextField.tsx +18 -5
- package/src/components/FormStation/Create/Create.stories.tsx +9 -0
- package/src/components/FormStation/FormStation.stories.tsx +2 -2
- package/src/components/FormStation/FormStation.tsx +16 -1
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +20 -3
- package/src/components/FormStation/helpers/useDataProvider.ts +6 -2
- package/src/components/Icons/Icons.models.ts +1 -0
- package/src/components/Icons/Icons.tsx +17 -0
- package/src/components/PageHeader/PageHeader.stories.tsx +8 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +2 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.scss +28 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.spec.tsx +0 -10
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +4 -3
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +5 -1
- package/src/styles/variables.scss +3 -0
|
@@ -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,8 +73,9 @@ 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,
|
|
78
|
+
tag: 'BETA',
|
|
79
79
|
onClick: () => setIsBulkEditMode((prev) => !prev),
|
|
80
80
|
}
|
|
81
81
|
: undefined,
|
|
@@ -93,12 +93,8 @@ export const useBulkEdit = <T extends Data>({
|
|
|
93
93
|
if (bulkEditRegistration?.component) {
|
|
94
94
|
return bulkEditRegistration.component;
|
|
95
95
|
} else if (bulkEditRegistration?.config) {
|
|
96
|
-
return (
|
|
97
|
-
|
|
98
|
-
{BulkEditFormFieldsConfigConverter(
|
|
99
|
-
bulkEditRegistration?.config.fields,
|
|
100
|
-
)}
|
|
101
|
-
</FieldSelection>
|
|
96
|
+
return BulkEditFormFieldsConfigConverter(
|
|
97
|
+
bulkEditRegistration?.config.fields,
|
|
102
98
|
);
|
|
103
99
|
}
|
|
104
100
|
return null;
|
|
@@ -116,6 +112,8 @@ export const useBulkEdit = <T extends Data>({
|
|
|
116
112
|
: undefined
|
|
117
113
|
}
|
|
118
114
|
showSaveHeaderAction={!noItemsSelected}
|
|
115
|
+
saveHeaderActionConfig={{ label: 'Apply', icon: IconName.Checkmark }}
|
|
116
|
+
saveNotificationMessage="Your changes are being applied to the selected items."
|
|
119
117
|
>
|
|
120
118
|
{BulkEditContent}
|
|
121
119
|
</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. */
|
|
@@ -487,6 +487,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
487
487
|
onItemSelected={itemSelectedHandler}
|
|
488
488
|
onRequestMoreData={onRequestMoreData}
|
|
489
489
|
onSortChanged={(sortData: SortData<T>) => {
|
|
490
|
+
listRef.current?.resetSelection();
|
|
490
491
|
onSortChanged(sortData);
|
|
491
492
|
setSortOrder(sortData);
|
|
492
493
|
}}
|
|
@@ -63,6 +63,7 @@ export const useActions = <T extends Data>({
|
|
|
63
63
|
}: UseActionsProps<T>): UseActionsReturnType => {
|
|
64
64
|
const bulkActionItems: PageHeaderJsActionProps[] = useMemo(
|
|
65
65
|
() => [
|
|
66
|
+
...(bulkEditAction ? [bulkEditAction] : []),
|
|
66
67
|
...(bulkActions ?? []).map((action) => ({
|
|
67
68
|
...action,
|
|
68
69
|
onClick: async () => {
|
|
@@ -101,7 +102,6 @@ export const useActions = <T extends Data>({
|
|
|
101
102
|
}
|
|
102
103
|
},
|
|
103
104
|
})),
|
|
104
|
-
...(bulkEditAction ? [bulkEditAction] : []),
|
|
105
105
|
],
|
|
106
106
|
[
|
|
107
107
|
bulkActions,
|
|
@@ -34,7 +34,6 @@ describe('FieldSelection', () => {
|
|
|
34
34
|
target: { value: 'field1' },
|
|
35
35
|
});
|
|
36
36
|
fireEvent.click(screen.getByText('Field 1'));
|
|
37
|
-
fireEvent.click(screen.getByTestId('add-field-button'));
|
|
38
37
|
|
|
39
38
|
expect(screen.getByTestId('field-field1')).toBeInTheDocument();
|
|
40
39
|
});
|
|
@@ -51,7 +50,6 @@ describe('FieldSelection', () => {
|
|
|
51
50
|
target: { value: 'field1' },
|
|
52
51
|
});
|
|
53
52
|
fireEvent.click(screen.getByText('Field 1'));
|
|
54
|
-
fireEvent.click(screen.getByTestId('add-field-button'));
|
|
55
53
|
|
|
56
54
|
expect(screen.getByTestId('field-field1')).toBeInTheDocument();
|
|
57
55
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React, {
|
|
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,14 +75,15 @@ export const FieldSelection: React.FC<FieldSelectionProps> = ({
|
|
|
70
75
|
...currentFields,
|
|
71
76
|
newField,
|
|
72
77
|
]);
|
|
78
|
+
onFieldAdded(newField.value);
|
|
73
79
|
}
|
|
74
80
|
}}
|
|
75
81
|
/>
|
|
76
82
|
}
|
|
77
83
|
>
|
|
78
|
-
{selectedFields.map((field
|
|
84
|
+
{selectedFields.map((field) => (
|
|
79
85
|
<AccordionItem
|
|
80
|
-
key={
|
|
86
|
+
key={field.value}
|
|
81
87
|
header={
|
|
82
88
|
<FieldSelectionItemHeader
|
|
83
89
|
label={field.label as string}
|
|
@@ -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
|
}
|
|
@@ -105,7 +112,7 @@ const FieldSelectionHeader: React.FC<{
|
|
|
105
112
|
}> = ({ fields, onAddField }) => {
|
|
106
113
|
const [value, setValue] = React.useState<string>();
|
|
107
114
|
|
|
108
|
-
|
|
115
|
+
useEffect(() => {
|
|
109
116
|
if (value) {
|
|
110
117
|
onAddField(value);
|
|
111
118
|
setValue(undefined);
|
|
@@ -113,22 +120,17 @@ const FieldSelectionHeader: React.FC<{
|
|
|
113
120
|
}, [onAddField, value]);
|
|
114
121
|
|
|
115
122
|
return (
|
|
116
|
-
<div className={classes.
|
|
123
|
+
<div className={classes.selectionHeader}>
|
|
117
124
|
<Select
|
|
118
125
|
label="Field"
|
|
119
126
|
name="field"
|
|
120
127
|
placeholder="Select Field"
|
|
121
128
|
options={fields}
|
|
129
|
+
disabled={fields.length === 0}
|
|
122
130
|
inlineMode={true}
|
|
123
131
|
onChange={(e) => setValue(e.currentTarget.value)}
|
|
124
132
|
value={value}
|
|
125
133
|
/>
|
|
126
|
-
<Button
|
|
127
|
-
icon={IconName.Plus}
|
|
128
|
-
buttonContext={value ? ButtonContext.Active : ButtonContext.Icon}
|
|
129
|
-
onButtonClicked={handleButtonClicked}
|
|
130
|
-
dataTestId="add-field-button"
|
|
131
|
-
/>
|
|
132
134
|
</div>
|
|
133
135
|
);
|
|
134
136
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { noop } from '../../../helpers/utils';
|
|
4
5
|
import { Button, ButtonContext } from '../../Buttons';
|
|
5
6
|
import { DateTimePicker } from '../../DateTime/DateTimePicker';
|
|
6
7
|
import { IconName } from '../../Icons';
|
|
@@ -18,7 +19,7 @@ export interface DateTimeTextProps extends BaseFormControl {
|
|
|
18
19
|
autoFocus?: boolean;
|
|
19
20
|
/** Whether or not the control supports auto complete */
|
|
20
21
|
autoComplete?: 'on' | 'off';
|
|
21
|
-
/** Whether the control modifies the time portion of the value */
|
|
22
|
+
/** Whether the control modifies the time portion of the value (default: true) */
|
|
22
23
|
modifyTime?: boolean;
|
|
23
24
|
/** Callback when the datepicker popup is closed or the date field value is changed */
|
|
24
25
|
onChange?: (value: string | null, isValidDate: boolean) => void;
|
|
@@ -39,7 +40,7 @@ export const DateTimeText: React.FC<DateTimeTextProps> = ({
|
|
|
39
40
|
error = undefined,
|
|
40
41
|
autoFocus = false,
|
|
41
42
|
autoComplete,
|
|
42
|
-
onChange,
|
|
43
|
+
onChange = noop,
|
|
43
44
|
modifyTime = true,
|
|
44
45
|
className = '',
|
|
45
46
|
...rest
|
|
@@ -60,34 +61,27 @@ export const DateTimeText: React.FC<DateTimeTextProps> = ({
|
|
|
60
61
|
|
|
61
62
|
const onBlurHandler = useCallback(
|
|
62
63
|
(e: React.FocusEvent<HTMLInputElement>) => {
|
|
63
|
-
// `display` represents the formatted version of the `value` prop and is updated whenever `value` changes.
|
|
64
|
-
// Comparing `e.target.value` with `display` ensures that onChange is only triggered when the input value has changed.
|
|
65
|
-
if (e.target.value === display) {
|
|
66
|
-
// If the value hasn't changed, do not trigger onChange
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
64
|
const textValue = e.target.value;
|
|
71
65
|
const parsedValue = modifyTime
|
|
72
66
|
? DateTime.fromFormat(textValue, 'f')
|
|
73
67
|
: DateTime.fromFormat(textValue, 'D');
|
|
74
68
|
|
|
75
69
|
if (parsedValue.isValid) {
|
|
76
|
-
onChange
|
|
70
|
+
onChange(parsedValue.toISO(), parsedValue.isValid);
|
|
77
71
|
} else if (textValue === '') {
|
|
78
|
-
onChange
|
|
72
|
+
onChange(null, true);
|
|
79
73
|
} else {
|
|
80
74
|
// Fallback parser if the user types in an ISO date
|
|
81
75
|
const isoParsedValue = DateTime.fromISO(textValue);
|
|
82
76
|
|
|
83
77
|
if (isoParsedValue.isValid) {
|
|
84
|
-
onChange
|
|
78
|
+
onChange(isoParsedValue.toISO(), isoParsedValue.isValid);
|
|
85
79
|
} else {
|
|
86
|
-
onChange
|
|
80
|
+
onChange(textValue, parsedValue.isValid);
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
83
|
},
|
|
90
|
-
[
|
|
84
|
+
[modifyTime, onChange],
|
|
91
85
|
);
|
|
92
86
|
|
|
93
87
|
return (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useField } from 'formik';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import { useFormikError } from '../useFormikError';
|
|
4
5
|
import { DateTimeText, DateTimeTextProps } from './DateTimeText';
|
|
@@ -14,16 +15,28 @@ export const DateTimeTextField: React.FC<
|
|
|
14
15
|
const error = useFormikError(props.name);
|
|
15
16
|
const [, meta, helpers] = useField(props.name);
|
|
16
17
|
|
|
17
|
-
const handleChange = (value: string | null, valid
|
|
18
|
+
const handleChange = (value: string | null, valid: boolean): void => {
|
|
18
19
|
if (!valid) {
|
|
19
20
|
helpers.setError('Invalid Date');
|
|
20
21
|
helpers.setValue(value, false);
|
|
21
22
|
} else {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const initialDateTime = DateTime.fromISO(meta.initialValue);
|
|
24
|
+
const newDateTime = DateTime.fromISO(value ?? '');
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
if (newDateTime.setZone(initialDateTime.zone).equals(initialDateTime)) {
|
|
27
|
+
// If the new date is the same as the initial value and the timezone is the only change,
|
|
28
|
+
// set it to the initial value
|
|
29
|
+
helpers.setValue(meta.initialValue);
|
|
30
|
+
} else if (
|
|
31
|
+
(value === null || value === '') &&
|
|
32
|
+
(!meta.initialValue || meta.initialValue === '')
|
|
33
|
+
) {
|
|
34
|
+
// If the value is null or empty and the initial value is also null or empty,
|
|
35
|
+
// set it to the initial value
|
|
36
|
+
helpers.setValue(meta.initialValue);
|
|
37
|
+
} else {
|
|
38
|
+
helpers.setValue(value);
|
|
39
|
+
}
|
|
27
40
|
}
|
|
28
41
|
};
|
|
29
42
|
|
|
@@ -7,6 +7,7 @@ import * as Yup from 'yup';
|
|
|
7
7
|
import { createGroups } from '../../../helpers/storybook';
|
|
8
8
|
import {
|
|
9
9
|
CustomTagsField,
|
|
10
|
+
DateTimeTextField,
|
|
10
11
|
FileUpload,
|
|
11
12
|
SelectField,
|
|
12
13
|
SingleLineTextField,
|
|
@@ -23,6 +24,7 @@ interface CreateValues {
|
|
|
23
24
|
shortDescription?: string;
|
|
24
25
|
longDescription?: string;
|
|
25
26
|
cast?: string[];
|
|
27
|
+
released?: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
interface APIResponse {
|
|
@@ -108,6 +110,12 @@ export const Default: StoryObj<CreateStoryType> = {
|
|
|
108
110
|
as={SingleLineTextField}
|
|
109
111
|
type="password"
|
|
110
112
|
/>
|
|
113
|
+
<Field
|
|
114
|
+
name="released"
|
|
115
|
+
label="Released"
|
|
116
|
+
as={DateTimeTextField}
|
|
117
|
+
modifyTime={false}
|
|
118
|
+
/>
|
|
111
119
|
</>
|
|
112
120
|
),
|
|
113
121
|
title: 'Create a Movie',
|
|
@@ -127,6 +135,7 @@ export const Default: StoryObj<CreateStoryType> = {
|
|
|
127
135
|
shortDescription: '',
|
|
128
136
|
longDescription: '',
|
|
129
137
|
cast: [],
|
|
138
|
+
released: '',
|
|
130
139
|
},
|
|
131
140
|
},
|
|
132
141
|
cancelNavigationUrl: '/',
|
|
@@ -169,7 +169,7 @@ export const Extended: StoryObj<typeof Details> = (() => {
|
|
|
169
169
|
shortDescription: 'Some short abstract...',
|
|
170
170
|
longDescription: '',
|
|
171
171
|
cast: ['Jane Doe', 'John Doe'],
|
|
172
|
-
released: '2020-04-
|
|
172
|
+
released: '2020-04-03',
|
|
173
173
|
list: listData,
|
|
174
174
|
archived: false,
|
|
175
175
|
timestamp: '00:00:00.001',
|
|
@@ -227,7 +227,7 @@ export const Extended: StoryObj<typeof Details> = (() => {
|
|
|
227
227
|
name="released"
|
|
228
228
|
label="Released"
|
|
229
229
|
as={DateTimeTextField}
|
|
230
|
-
|
|
230
|
+
modifyTime={false}
|
|
231
231
|
/>
|
|
232
232
|
<Field
|
|
233
233
|
name="password"
|
|
@@ -5,6 +5,7 @@ import { OptionalObjectSchema } from 'yup/lib/object';
|
|
|
5
5
|
import { Data } from '../../types/data';
|
|
6
6
|
import { BulkEditContext } from '../Explorer/BulkEdit/BulkEditContext';
|
|
7
7
|
import { QuickEditContext } from '../Explorer/QuickEdit/QuickEditContext';
|
|
8
|
+
import { PageHeaderJsActionProps } from '../PageHeader/PageHeaderAction';
|
|
8
9
|
import { StationMessage } from '../models';
|
|
9
10
|
import {
|
|
10
11
|
FormActionData,
|
|
@@ -39,6 +40,16 @@ export interface FormStationProps<
|
|
|
39
40
|
actionsWidth?: string;
|
|
40
41
|
/** If set to true, the save header action is shown. (default: true) */
|
|
41
42
|
showSaveHeaderAction?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Optional configuration for the save header action button.
|
|
45
|
+
* Allows customizing the label and icon.
|
|
46
|
+
*/
|
|
47
|
+
saveHeaderActionConfig?: Pick<PageHeaderJsActionProps, 'label' | 'icon'>;
|
|
48
|
+
/**
|
|
49
|
+
* If set, this will override the default notification message shown
|
|
50
|
+
* after a successful save.
|
|
51
|
+
*/
|
|
52
|
+
saveNotificationMessage?: string;
|
|
42
53
|
/**
|
|
43
54
|
* An object containing the initial data of the form.
|
|
44
55
|
*/
|
|
@@ -91,6 +102,8 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
91
102
|
className = '',
|
|
92
103
|
setTabTitle = true,
|
|
93
104
|
showSaveHeaderAction = true,
|
|
105
|
+
saveHeaderActionConfig,
|
|
106
|
+
saveNotificationMessage,
|
|
94
107
|
}: PropsWithChildren<
|
|
95
108
|
FormStationProps<TValues, TSubmitResponse>
|
|
96
109
|
>): JSX.Element => {
|
|
@@ -103,7 +116,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
103
116
|
setStationError,
|
|
104
117
|
isFormSubmitting,
|
|
105
118
|
lastSubmittedResponse,
|
|
106
|
-
} = useDataProvider(initialData, saveData);
|
|
119
|
+
} = useDataProvider(initialData, saveData, saveNotificationMessage);
|
|
107
120
|
|
|
108
121
|
const { setValidationError, validationWatcher } = useValidationError(
|
|
109
122
|
stationError,
|
|
@@ -137,6 +150,8 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
137
150
|
className={classes.header}
|
|
138
151
|
setTabTitle={setTabTitle}
|
|
139
152
|
showSaveHeaderAction={showSaveHeaderAction}
|
|
153
|
+
saveHeaderActionConfig={saveHeaderActionConfig}
|
|
154
|
+
setValidationError={setValidationError}
|
|
140
155
|
/>
|
|
141
156
|
{!bulkEditContext && (
|
|
142
157
|
<SaveOnNavigate
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PageHeaderActionType,
|
|
11
11
|
PageHeaderProps,
|
|
12
12
|
} from '../../PageHeader';
|
|
13
|
+
import { PageHeaderJsActionProps } from '../../PageHeader/PageHeaderAction';
|
|
13
14
|
import { useTitle } from '../helpers/useTitle';
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -22,6 +23,8 @@ export const FormStationHeader: React.FC<
|
|
|
22
23
|
cancelNavigationUrl?: string;
|
|
23
24
|
setTabTitle?: boolean;
|
|
24
25
|
showSaveHeaderAction?: boolean;
|
|
26
|
+
saveHeaderActionConfig?: Pick<PageHeaderJsActionProps, 'label' | 'icon'>;
|
|
27
|
+
setValidationError: () => void;
|
|
25
28
|
}
|
|
26
29
|
> = ({
|
|
27
30
|
titleProperty,
|
|
@@ -31,8 +34,14 @@ export const FormStationHeader: React.FC<
|
|
|
31
34
|
className,
|
|
32
35
|
setTabTitle,
|
|
33
36
|
showSaveHeaderAction,
|
|
37
|
+
saveHeaderActionConfig = {
|
|
38
|
+
label: 'Save',
|
|
39
|
+
icon: IconName.Save,
|
|
40
|
+
},
|
|
41
|
+
setValidationError,
|
|
34
42
|
}) => {
|
|
35
|
-
const { dirty, submitForm, resetForm } =
|
|
43
|
+
const { dirty, submitForm, resetForm, isValid } =
|
|
44
|
+
useFormikContext<FormikValues>();
|
|
36
45
|
const quickEditContext = useContext(QuickEditContext);
|
|
37
46
|
const bulkEditContext = useContext(BulkEditContext);
|
|
38
47
|
|
|
@@ -56,14 +65,18 @@ export const FormStationHeader: React.FC<
|
|
|
56
65
|
|
|
57
66
|
if (showSaveHeaderAction) {
|
|
58
67
|
actionItems.push({
|
|
59
|
-
label:
|
|
60
|
-
icon:
|
|
68
|
+
label: saveHeaderActionConfig.label,
|
|
69
|
+
icon: saveHeaderActionConfig.icon,
|
|
61
70
|
kind: 'action',
|
|
62
71
|
actionType: PageHeaderActionType.Context,
|
|
63
72
|
onClick: async () => {
|
|
64
73
|
if (quickEditContext?.isQuickEditMode) {
|
|
65
74
|
quickEditContext.refresh();
|
|
66
75
|
} else if (bulkEditContext?.isBulkEditMode) {
|
|
76
|
+
if (!isValid) {
|
|
77
|
+
setValidationError();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
67
80
|
await submitForm();
|
|
68
81
|
history.replace(history.location.pathname);
|
|
69
82
|
} else {
|
|
@@ -114,8 +127,12 @@ export const FormStationHeader: React.FC<
|
|
|
114
127
|
cancelNavigationUrl,
|
|
115
128
|
dirty,
|
|
116
129
|
history,
|
|
130
|
+
isValid,
|
|
117
131
|
quickEditContext,
|
|
118
132
|
resetForm,
|
|
133
|
+
saveHeaderActionConfig.icon,
|
|
134
|
+
saveHeaderActionConfig.label,
|
|
135
|
+
setValidationError,
|
|
119
136
|
showSaveHeaderAction,
|
|
120
137
|
submitForm,
|
|
121
138
|
]);
|
|
@@ -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 {
|
|
@@ -10,6 +10,22 @@ export interface IconsProps {
|
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const AxiIcon: React.FC<{ className?: string }> = ({ className }) => (
|
|
14
|
+
<svg
|
|
15
|
+
className={clsx(classes.icons, className)}
|
|
16
|
+
version="1.1"
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
viewBox="0 0 40 40"
|
|
19
|
+
>
|
|
20
|
+
<path
|
|
21
|
+
vectorEffect="non-scaling-stroke"
|
|
22
|
+
fill="none"
|
|
23
|
+
strokeWidth="2"
|
|
24
|
+
d="M24.3,36h-8.5c-5,0-9.1-4.1-9.1-9.1v-5.3c0-5,4.1-9.1,9.1-9.1h8.5c5,0,9.1,4.1,9.1,9.1v5.3c0,5-4.1,9.1-9.1,9.1ZM6.8,20.4h-.9c-1.3,0-2.3,1.1-2.3,2.3v3c0,1.3,1.1,2.3,2.3,2.3h.9M33.3,28.2h.9c1.3,0,2.3-1.1,2.3-2.3v-3c0-1.3-1.1-2.3-2.3-2.3h-.9M14.1,20.4c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5,2.5-1.1,2.5-2.5-1.1-2.5-2.5-2.5ZM19.7,4c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5,2.5-1.1,2.5-2.5-1.1-2.5-2.5-2.5ZM25.9,20.4c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5,2.5-1.1,2.5-2.5-1.1-2.5-2.5-2.5ZM19.7,9v3.5M16.1,30.2h7.8"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
|
|
13
29
|
const ArchiveIcon: React.FC<{ className?: string }> = ({ className }) => (
|
|
14
30
|
<svg
|
|
15
31
|
className={clsx(classes.icons, className)}
|
|
@@ -924,6 +940,7 @@ const XIcon: React.FC<{ className?: string }> = ({ className }) => (
|
|
|
924
940
|
*/
|
|
925
941
|
export const Icons: React.FC<IconsProps> = ({ icon, className }) => {
|
|
926
942
|
const icons: { [key in IconName]: JSX.Element } = {
|
|
943
|
+
[IconName.Axi]: <AxiIcon className={className} />,
|
|
927
944
|
[IconName.Archive]: <ArchiveIcon className={className} />,
|
|
928
945
|
[IconName.Audio]: <AudioIcon className={className} />,
|
|
929
946
|
[IconName.DescriptiveAudio]: <DescriptiveAudioIcon className={className} />,
|
|
@@ -25,6 +25,13 @@ const headerActions: PageHeaderActionItemProps[] = [
|
|
|
25
25
|
];
|
|
26
26
|
|
|
27
27
|
const bulkHeaderActions: PageHeaderActionProps[] = [
|
|
28
|
+
{
|
|
29
|
+
label: 'Bulk Edit',
|
|
30
|
+
actionType: PageHeaderActionType.Context,
|
|
31
|
+
icon: IconName.BulkEdit,
|
|
32
|
+
tag: 'BETA',
|
|
33
|
+
onClick: action('onActionSelected'),
|
|
34
|
+
},
|
|
28
35
|
{
|
|
29
36
|
label: 'Bulk Delete',
|
|
30
37
|
actionType: PageHeaderActionType.Context,
|
|
@@ -67,6 +74,7 @@ const bulkHeaderActions: PageHeaderActionProps[] = [
|
|
|
67
74
|
label: 'Bulk Action 4',
|
|
68
75
|
confirmationMode: 'Simple',
|
|
69
76
|
onClick: action('onActionSelected'),
|
|
77
|
+
tag: 'BETA',
|
|
70
78
|
},
|
|
71
79
|
];
|
|
72
80
|
|
|
@@ -163,4 +163,32 @@
|
|
|
163
163
|
|
|
164
164
|
color: var(--actions-disabled-fg-color, $actions-disabled-fg-color);
|
|
165
165
|
}
|
|
166
|
+
|
|
167
|
+
.tag {
|
|
168
|
+
position: absolute;
|
|
169
|
+
transform: rotate(45deg) translateX(53px) translateY(-40px);
|
|
170
|
+
transform-origin: center;
|
|
171
|
+
top: 0;
|
|
172
|
+
left: 0;
|
|
173
|
+
display: grid;
|
|
174
|
+
background-color: var(
|
|
175
|
+
--page-header-action-tag-background-color,
|
|
176
|
+
$page-header-action-tag-background-color
|
|
177
|
+
);
|
|
178
|
+
font-size: var(
|
|
179
|
+
--page-header-action-tag-font-size,
|
|
180
|
+
$page-header-action-tag-font-size
|
|
181
|
+
);
|
|
182
|
+
font-weight: bold;
|
|
183
|
+
width: 75px;
|
|
184
|
+
height: 14px;
|
|
185
|
+
align-items: center;
|
|
186
|
+
text-align: center;
|
|
187
|
+
z-index: 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
&:has(.tag) {
|
|
191
|
+
position: relative;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
}
|
|
166
194
|
}
|
|
@@ -645,16 +645,6 @@ describe('PageHeaderAction', () => {
|
|
|
645
645
|
const link = wrapper.find('Link');
|
|
646
646
|
expect(link.prop('target')).toBeUndefined();
|
|
647
647
|
});
|
|
648
|
-
|
|
649
|
-
it('renders with disabled class if disabled prop is true', () => {
|
|
650
|
-
const wrapper = mount(
|
|
651
|
-
<Router>
|
|
652
|
-
<PageHeaderAction {...defaultProps} path="/test" disabled />
|
|
653
|
-
</Router>,
|
|
654
|
-
);
|
|
655
|
-
const container = wrapper.find('.container').hostNodes();
|
|
656
|
-
expect(container.hasClass('disabled')).toBe(true);
|
|
657
|
-
});
|
|
658
648
|
});
|
|
659
649
|
});
|
|
660
650
|
|
|
@@ -69,6 +69,7 @@ const PageHeaderJSAction: React.FC<PageHeaderJsActionProps> = ({
|
|
|
69
69
|
disabled,
|
|
70
70
|
className,
|
|
71
71
|
onClick = noop,
|
|
72
|
+
tag,
|
|
72
73
|
}) => {
|
|
73
74
|
const [confirmation, setConfirmation] = useState<boolean>(false);
|
|
74
75
|
const [referenceElement, setReferenceElement] = useState<HTMLElement>();
|
|
@@ -156,10 +157,10 @@ const PageHeaderJSAction: React.FC<PageHeaderJsActionProps> = ({
|
|
|
156
157
|
<span data-test-id="label">{label}</span>
|
|
157
158
|
)}
|
|
158
159
|
</div>
|
|
160
|
+
{tag && <div className={classes.tag}>{tag}</div>}
|
|
159
161
|
</button>
|
|
160
162
|
{confirmation && confirmationMode === 'Advanced' && (
|
|
161
163
|
<ConfirmDialog
|
|
162
|
-
className={classes.confirmDialog}
|
|
163
164
|
referenceElement={referenceElement}
|
|
164
165
|
title={title}
|
|
165
166
|
cancelButtonText={cancelButtonText}
|
|
@@ -185,12 +186,12 @@ const PageHeaderJSAction: React.FC<PageHeaderJsActionProps> = ({
|
|
|
185
186
|
*/
|
|
186
187
|
const PageHeaderNavigationAction: React.FC<PageHeaderNavigationActionProps> = ({
|
|
187
188
|
className,
|
|
188
|
-
disabled,
|
|
189
189
|
icon,
|
|
190
190
|
imgAlt,
|
|
191
191
|
label,
|
|
192
192
|
openInNewTab = false,
|
|
193
193
|
path,
|
|
194
|
+
tag,
|
|
194
195
|
}) => {
|
|
195
196
|
const headerIcon = icon ? icon : openInNewTab ? IconName.External : undefined;
|
|
196
197
|
|
|
@@ -199,7 +200,6 @@ const PageHeaderNavigationAction: React.FC<PageHeaderNavigationActionProps> = ({
|
|
|
199
200
|
className={clsx(
|
|
200
201
|
classes.container,
|
|
201
202
|
'page-header-action-container',
|
|
202
|
-
{ [classes.disabled]: disabled },
|
|
203
203
|
className,
|
|
204
204
|
)}
|
|
205
205
|
data-test-id="action"
|
|
@@ -216,6 +216,7 @@ const PageHeaderNavigationAction: React.FC<PageHeaderNavigationActionProps> = ({
|
|
|
216
216
|
<div className={classes.label}>
|
|
217
217
|
<span data-test-id="label">{label}</span>
|
|
218
218
|
</div>
|
|
219
|
+
{tag && <div className={classes.tag}>{tag}</div>}
|
|
219
220
|
</Link>
|
|
220
221
|
);
|
|
221
222
|
};
|