@axinom/mosaic-ui 0.61.0-rc.2 → 0.62.0-rc.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/components/Accordion/Accordion.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts +22 -0
- package/dist/components/Explorer/BulkEdit/BulkEdit.model.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/BulkEditContext.d.ts +7 -0
- package/dist/components/Explorer/BulkEdit/BulkEditContext.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +10 -0
- package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts +4 -0
- package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/index.d.ts +6 -0
- package/dist/components/Explorer/BulkEdit/index.d.ts.map +1 -0
- package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts +16 -0
- package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -0
- package/dist/components/Explorer/Explorer.d.ts +4 -1
- package/dist/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.model.d.ts +13 -0
- package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
- package/dist/components/Explorer/helpers/useActions.d.ts +3 -1
- package/dist/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/components/Explorer/helpers/useSubtitle.d.ts +13 -0
- package/dist/components/Explorer/helpers/useSubtitle.d.ts.map +1 -0
- package/dist/components/Explorer/index.d.ts +1 -0
- package/dist/components/Explorer/index.d.ts.map +1 -1
- package/dist/components/FieldSelection/FieldSelection.d.ts +7 -0
- package/dist/components/FieldSelection/FieldSelection.d.ts.map +1 -0
- package/dist/components/FieldSelection/index.d.ts +2 -0
- package/dist/components/FieldSelection/index.d.ts.map +1 -0
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
- package/dist/components/Icons/Icons.d.ts.map +1 -1
- package/dist/components/Icons/Icons.models.d.ts +47 -46
- package/dist/components/Icons/Icons.models.d.ts.map +1 -1
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/helpers/testing.d.ts +3 -1
- package/dist/helpers/testing.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 +6 -2
- package/src/components/Accordion/Accordion.tsx +13 -11
- package/src/components/Explorer/BulkEdit/BulkEdit.model.ts +21 -0
- package/src/components/Explorer/BulkEdit/BulkEditContext.tsx +11 -0
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +162 -0
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +45 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.spec.tsx +141 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.tsx +90 -0
- package/src/components/Explorer/BulkEdit/index.ts +8 -0
- package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +132 -0
- package/src/components/Explorer/Explorer.model.ts +14 -0
- package/src/components/Explorer/Explorer.stories.tsx +82 -0
- package/src/components/Explorer/Explorer.tsx +41 -57
- package/src/components/Explorer/helpers/useActions.ts +21 -5
- package/src/components/Explorer/helpers/useFilters.spec.tsx +140 -0
- package/src/components/Explorer/helpers/useStationMessage.spec.tsx +91 -0
- package/src/components/Explorer/helpers/useSubtitle.spec.tsx +115 -0
- package/src/components/Explorer/helpers/useSubtitle.tsx +52 -0
- package/src/components/Explorer/index.ts +10 -0
- package/src/components/FieldSelection/FieldSelection.scss +18 -0
- package/src/components/FieldSelection/FieldSelection.spec.tsx +62 -0
- package/src/components/FieldSelection/FieldSelection.stories.tsx +30 -0
- package/src/components/FieldSelection/FieldSelection.tsx +154 -0
- package/src/components/FieldSelection/index.ts +1 -0
- package/src/components/FormStation/FormStation.tsx +8 -4
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +22 -3
- package/src/components/Icons/Icons.models.ts +1 -0
- package/src/components/Icons/Icons.tsx +17 -0
- package/src/components/List/List.tsx +11 -0
- package/src/components/index.ts +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-hooks';
|
|
2
|
+
import { ItemSelectEventArgs, ListSelectMode } from '../../List/List.model';
|
|
3
|
+
import { ResultCounts } from './useDataProvider';
|
|
4
|
+
import { useSubtitle } from './useSubtitle';
|
|
5
|
+
|
|
6
|
+
describe('useSubtitle', () => {
|
|
7
|
+
it('should return "Showing 1 Element" when there is 1 total and 1 filtered result', () => {
|
|
8
|
+
const mode = ListSelectMode.Single;
|
|
9
|
+
const itemSelection: ItemSelectEventArgs<never> = {
|
|
10
|
+
mode: 'SINGLE_ITEMS',
|
|
11
|
+
items: [],
|
|
12
|
+
};
|
|
13
|
+
const results: ResultCounts = { total: 1, filtered: 1 };
|
|
14
|
+
|
|
15
|
+
const { result } = renderHook(() =>
|
|
16
|
+
useSubtitle(mode, itemSelection, results),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(result.current.resultsTitle).toBe('Showing 1 Element');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return "Showing 0 of 1 Element" when there is 1 total and 0 filtered result', () => {
|
|
23
|
+
const mode = ListSelectMode.Single;
|
|
24
|
+
const itemSelection: ItemSelectEventArgs<never> = {
|
|
25
|
+
mode: 'SINGLE_ITEMS',
|
|
26
|
+
items: [],
|
|
27
|
+
};
|
|
28
|
+
const results: ResultCounts = { total: 1, filtered: 0 };
|
|
29
|
+
|
|
30
|
+
const { result } = renderHook(() =>
|
|
31
|
+
useSubtitle(mode, itemSelection, results),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(result.current.resultsTitle).toBe('Showing 0 of 1 Element');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return "Showing all of X Elements" when filtered results equal total results', () => {
|
|
38
|
+
const mode = ListSelectMode.Single;
|
|
39
|
+
const itemSelection: ItemSelectEventArgs<never> = {
|
|
40
|
+
mode: 'SINGLE_ITEMS',
|
|
41
|
+
items: [],
|
|
42
|
+
};
|
|
43
|
+
const results: ResultCounts = { total: 5, filtered: 5 };
|
|
44
|
+
|
|
45
|
+
const { result } = renderHook(() =>
|
|
46
|
+
useSubtitle(mode, itemSelection, results),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(result.current.resultsTitle).toBe('Showing all of 5 Elements');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return "Showing X Elements" when filtered results are undefined', () => {
|
|
53
|
+
const mode = ListSelectMode.Single;
|
|
54
|
+
const itemSelection: ItemSelectEventArgs<never> = {
|
|
55
|
+
mode: 'SINGLE_ITEMS',
|
|
56
|
+
items: [],
|
|
57
|
+
};
|
|
58
|
+
const results: ResultCounts = { total: 5, filtered: undefined };
|
|
59
|
+
|
|
60
|
+
const { result } = renderHook(() =>
|
|
61
|
+
useSubtitle(mode, itemSelection, results),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(result.current.resultsTitle).toBe('Showing 5 Elements');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return "Showing X of Y Elements" when filtered results are less than total results', () => {
|
|
68
|
+
const mode = ListSelectMode.Single;
|
|
69
|
+
const itemSelection: ItemSelectEventArgs<never> = {
|
|
70
|
+
mode: 'SINGLE_ITEMS',
|
|
71
|
+
items: [],
|
|
72
|
+
};
|
|
73
|
+
const results: ResultCounts = { total: 10, filtered: 5 };
|
|
74
|
+
|
|
75
|
+
const { result } = renderHook(() =>
|
|
76
|
+
useSubtitle(mode, itemSelection, results),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(result.current.resultsTitle).toBe('Showing 5 of 10 Elements');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should append selected items count when mode is Multi and selection mode is SINGLE_ITEMS', () => {
|
|
83
|
+
const mode = ListSelectMode.Multi;
|
|
84
|
+
const itemSelection: ItemSelectEventArgs<{ id: number }> = {
|
|
85
|
+
mode: 'SINGLE_ITEMS',
|
|
86
|
+
items: [{ id: 1 }, { id: 2 }],
|
|
87
|
+
};
|
|
88
|
+
const results: ResultCounts = { total: 10, filtered: 5 };
|
|
89
|
+
|
|
90
|
+
const { result } = renderHook(() =>
|
|
91
|
+
useSubtitle(mode, itemSelection, results),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.current.resultsTitle).toBe(
|
|
95
|
+
'Showing 5 of 10 Elements, Selected: 2',
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should append filtered results count as selected items when mode is Multi and selection mode is SELECT_ALL', () => {
|
|
100
|
+
const mode = ListSelectMode.Multi;
|
|
101
|
+
const itemSelection: ItemSelectEventArgs<never> = {
|
|
102
|
+
mode: 'SELECT_ALL',
|
|
103
|
+
items: [],
|
|
104
|
+
};
|
|
105
|
+
const results: ResultCounts = { total: 10, filtered: 5 };
|
|
106
|
+
|
|
107
|
+
const { result } = renderHook(() =>
|
|
108
|
+
useSubtitle(mode, itemSelection, results),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(result.current.resultsTitle).toBe(
|
|
112
|
+
'Showing 5 of 10 Elements, Selected: 5',
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Data } from '../../../types';
|
|
2
|
+
import { ItemSelectEventArgs, ListSelectMode } from '../../List/List.model';
|
|
3
|
+
import { ResultCounts } from './useDataProvider';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets PageHeader subtitle
|
|
7
|
+
* @param mode current ListSelectMode
|
|
8
|
+
* @param itemSelection current item selection
|
|
9
|
+
* @param results total results
|
|
10
|
+
*/
|
|
11
|
+
export function useSubtitle<T extends Data>(
|
|
12
|
+
mode: ListSelectMode,
|
|
13
|
+
itemSelection: ItemSelectEventArgs<T>,
|
|
14
|
+
results?: ResultCounts,
|
|
15
|
+
): {
|
|
16
|
+
readonly resultsTitle: string;
|
|
17
|
+
} {
|
|
18
|
+
let resultsTitle = ''; // default to an empty string while results is being fetched
|
|
19
|
+
|
|
20
|
+
if (results !== undefined) {
|
|
21
|
+
switch (true) {
|
|
22
|
+
case results.total === 1 && results.filtered === 1:
|
|
23
|
+
resultsTitle = `Showing 1 Element`;
|
|
24
|
+
break;
|
|
25
|
+
case results.total === 1 && results.filtered === 0:
|
|
26
|
+
resultsTitle = `Showing 0 of 1 Element`;
|
|
27
|
+
break;
|
|
28
|
+
case results.filtered === results.total:
|
|
29
|
+
resultsTitle = `Showing all of ${results.total} Elements`;
|
|
30
|
+
break;
|
|
31
|
+
case results.filtered === undefined:
|
|
32
|
+
resultsTitle = `Showing ${results.total} Elements`;
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
resultsTitle = `Showing ${results.filtered} of ${results.total} Elements`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Append Selected items if list selection is in Multi mode
|
|
39
|
+
if (mode === ListSelectMode.Multi) {
|
|
40
|
+
if (itemSelection.mode === 'SINGLE_ITEMS') {
|
|
41
|
+
resultsTitle = `${resultsTitle}, Selected: ${
|
|
42
|
+
itemSelection.items?.length ?? 0
|
|
43
|
+
}`;
|
|
44
|
+
} else {
|
|
45
|
+
// Show filtered results as selected results if 'SELECT_ALL' is active
|
|
46
|
+
resultsTitle = `${resultsTitle}, Selected: ${results.filtered}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { resultsTitle } as const;
|
|
52
|
+
}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
BulkEditConfig,
|
|
3
|
+
BulkEditContext,
|
|
4
|
+
BulkEditContextType,
|
|
5
|
+
BulkEditFieldConfig,
|
|
6
|
+
BulkEditFieldConfigMap,
|
|
7
|
+
BulkEditFormFieldsConfigConverter,
|
|
8
|
+
defaultComponentMap,
|
|
9
|
+
generateBulkEditMutation,
|
|
10
|
+
} from './BulkEdit';
|
|
1
11
|
export { Explorer, ExplorerProps } from './Explorer';
|
|
2
12
|
export {
|
|
3
13
|
ExplorerBulkAction,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@import '../../styles/common.scss';
|
|
2
|
+
|
|
3
|
+
.header {
|
|
4
|
+
display: grid;
|
|
5
|
+
grid-template-columns: 1fr 50px;
|
|
6
|
+
gap: 10px;
|
|
7
|
+
align-items: center;
|
|
8
|
+
|
|
9
|
+
div:has(> input) {
|
|
10
|
+
padding: 2px;
|
|
11
|
+
max-width: none !important;
|
|
12
|
+
width: 100% !important;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.content {
|
|
17
|
+
padding: 10px;
|
|
18
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { configure, fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { FieldSelection } from './FieldSelection';
|
|
5
|
+
|
|
6
|
+
configure({ testIdAttribute: 'data-test-id' });
|
|
7
|
+
|
|
8
|
+
const MockField: React.FC<{ name: string; label: string }> = ({
|
|
9
|
+
name,
|
|
10
|
+
label,
|
|
11
|
+
}) => <div data-test-id={`field-${name}`}>{label}</div>;
|
|
12
|
+
|
|
13
|
+
describe('FieldSelection', () => {
|
|
14
|
+
it('should render without crashing', () => {
|
|
15
|
+
render(
|
|
16
|
+
<FieldSelection>
|
|
17
|
+
<MockField name="field1" label="Field 1" />
|
|
18
|
+
<MockField name="field2" label="Field 2" />
|
|
19
|
+
</FieldSelection>,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(screen.getByPlaceholderText('Select Field')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should add a field to the selected fields when the add button is clicked', () => {
|
|
26
|
+
render(
|
|
27
|
+
<FieldSelection>
|
|
28
|
+
<MockField name="field1" label="Field 1" />
|
|
29
|
+
<MockField name="field2" label="Field 2" />
|
|
30
|
+
</FieldSelection>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
fireEvent.change(screen.getByPlaceholderText('Select Field'), {
|
|
34
|
+
target: { value: 'field1' },
|
|
35
|
+
});
|
|
36
|
+
fireEvent.click(screen.getByText('Field 1'));
|
|
37
|
+
fireEvent.click(screen.getByTestId('add-field-button'));
|
|
38
|
+
|
|
39
|
+
expect(screen.getByTestId('field-field1')).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should remove a field from the selected fields when the remove button is clicked', () => {
|
|
43
|
+
render(
|
|
44
|
+
<FieldSelection>
|
|
45
|
+
<MockField name="field1" label="Field 1" />
|
|
46
|
+
<MockField name="field2" label="Field 2" />
|
|
47
|
+
</FieldSelection>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
fireEvent.change(screen.getByPlaceholderText('Select Field'), {
|
|
51
|
+
target: { value: 'field1' },
|
|
52
|
+
});
|
|
53
|
+
fireEvent.click(screen.getByText('Field 1'));
|
|
54
|
+
fireEvent.click(screen.getByTestId('add-field-button'));
|
|
55
|
+
|
|
56
|
+
expect(screen.getByTestId('field-field1')).toBeInTheDocument();
|
|
57
|
+
|
|
58
|
+
fireEvent.click(screen.getByTestId('remove-field-button'));
|
|
59
|
+
|
|
60
|
+
expect(screen.queryByTestId('field-field1')).not.toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Field, Formik } from 'formik';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { noop } from '../../helpers/utils';
|
|
5
|
+
import { CustomTagsField, SingleLineTextField } from '../FormElements';
|
|
6
|
+
import { FieldSelection } from './FieldSelection';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof FieldSelection> = {
|
|
9
|
+
title: 'Other Components/FieldSelection',
|
|
10
|
+
component: FieldSelection,
|
|
11
|
+
};
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const Default: StoryObj<typeof FieldSelection> = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<Formik initialValues={{}} onSubmit={noop}>
|
|
17
|
+
<>
|
|
18
|
+
<FieldSelection>
|
|
19
|
+
<Field label="Title" name={'title'} as={SingleLineTextField} />
|
|
20
|
+
<Field label="Tags ( Add )" name={'tagsAdd'} as={CustomTagsField} />
|
|
21
|
+
<Field
|
|
22
|
+
label="Tags ( Remove )"
|
|
23
|
+
name={'tagsRemove'}
|
|
24
|
+
as={CustomTagsField}
|
|
25
|
+
/>
|
|
26
|
+
</FieldSelection>
|
|
27
|
+
</>
|
|
28
|
+
</Formik>
|
|
29
|
+
),
|
|
30
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useCallback, useEffect } from 'react';
|
|
2
|
+
import { Accordion, AccordionItem } from '../Accordion';
|
|
3
|
+
import { Button, ButtonContext } from '../Buttons';
|
|
4
|
+
import { Select } from '../FormElements';
|
|
5
|
+
import { SelectOption } from '../FormElements/Select/Select';
|
|
6
|
+
import { IconName } from '../Icons';
|
|
7
|
+
import classes from './FieldSelection.scss';
|
|
8
|
+
|
|
9
|
+
interface FieldSelectionProps {
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FieldDefinition {
|
|
14
|
+
index: number;
|
|
15
|
+
value: string;
|
|
16
|
+
label: string;
|
|
17
|
+
child: JSX.Element;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const FieldSelection: React.FC<FieldSelectionProps> = ({
|
|
21
|
+
className,
|
|
22
|
+
children,
|
|
23
|
+
}) => {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const fields: FieldDefinition[] = [];
|
|
26
|
+
React.Children.forEach(children, (child, index) => {
|
|
27
|
+
if (
|
|
28
|
+
React.isValidElement<{ label: string; name: string }>(child) &&
|
|
29
|
+
child.props.name
|
|
30
|
+
) {
|
|
31
|
+
fields.push({
|
|
32
|
+
index,
|
|
33
|
+
value: child.props.name,
|
|
34
|
+
label: child.props.label,
|
|
35
|
+
child: React.cloneElement<{ label: string }>(child, {
|
|
36
|
+
label: 'Value',
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
setAvailableFields(fields);
|
|
43
|
+
}, [children]);
|
|
44
|
+
|
|
45
|
+
const [availableFields, setAvailableFields] = React.useState<
|
|
46
|
+
FieldDefinition[]
|
|
47
|
+
>([]);
|
|
48
|
+
|
|
49
|
+
const [selectedFields, setSelectedFields] = React.useState<FieldDefinition[]>(
|
|
50
|
+
[],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Accordion
|
|
55
|
+
className={className}
|
|
56
|
+
expandedByDefault={true}
|
|
57
|
+
header={
|
|
58
|
+
<FieldSelectionHeader
|
|
59
|
+
fields={availableFields}
|
|
60
|
+
onAddField={(value) => {
|
|
61
|
+
const newField = availableFields.find(
|
|
62
|
+
(field) => field.value === value,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (newField) {
|
|
66
|
+
setAvailableFields((currentFields) =>
|
|
67
|
+
currentFields.filter((field) => field.value !== value),
|
|
68
|
+
);
|
|
69
|
+
setSelectedFields((currentFields) => [
|
|
70
|
+
...currentFields,
|
|
71
|
+
newField,
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
{selectedFields.map((field, i) => (
|
|
79
|
+
<AccordionItem
|
|
80
|
+
key={i}
|
|
81
|
+
header={
|
|
82
|
+
<FieldSelectionItemHeader
|
|
83
|
+
label={field.label as string}
|
|
84
|
+
onRemoveField={() => {
|
|
85
|
+
setSelectedFields((currentFields) =>
|
|
86
|
+
currentFields.filter((f) => f.value !== field.value),
|
|
87
|
+
);
|
|
88
|
+
setAvailableFields((currentFields) =>
|
|
89
|
+
[...currentFields, field].sort((a, b) => a.index - b.index),
|
|
90
|
+
);
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
}
|
|
94
|
+
>
|
|
95
|
+
<div className={classes.content}>{field.child}</div>
|
|
96
|
+
</AccordionItem>
|
|
97
|
+
))}
|
|
98
|
+
</Accordion>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const FieldSelectionHeader: React.FC<{
|
|
103
|
+
fields: SelectOption[];
|
|
104
|
+
onAddField: (value: string) => void;
|
|
105
|
+
}> = ({ fields, onAddField }) => {
|
|
106
|
+
const [value, setValue] = React.useState<string>();
|
|
107
|
+
|
|
108
|
+
const handleButtonClicked = useCallback(() => {
|
|
109
|
+
if (value) {
|
|
110
|
+
onAddField(value);
|
|
111
|
+
setValue(undefined);
|
|
112
|
+
}
|
|
113
|
+
}, [onAddField, value]);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className={classes.header}>
|
|
117
|
+
<Select
|
|
118
|
+
label="Field"
|
|
119
|
+
name="field"
|
|
120
|
+
placeholder="Select Field"
|
|
121
|
+
options={fields}
|
|
122
|
+
inlineMode={true}
|
|
123
|
+
onChange={(e) => setValue(e.currentTarget.value)}
|
|
124
|
+
value={value}
|
|
125
|
+
/>
|
|
126
|
+
<Button
|
|
127
|
+
icon={IconName.Plus}
|
|
128
|
+
buttonContext={value ? ButtonContext.Active : ButtonContext.Icon}
|
|
129
|
+
onButtonClicked={handleButtonClicked}
|
|
130
|
+
dataTestId="add-field-button"
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const FieldSelectionItemHeader: React.FC<{
|
|
137
|
+
label: string;
|
|
138
|
+
onRemoveField: () => void;
|
|
139
|
+
}> = ({ label, onRemoveField }) => {
|
|
140
|
+
return (
|
|
141
|
+
<div className={classes.header}>
|
|
142
|
+
{label}
|
|
143
|
+
<Button
|
|
144
|
+
icon={IconName.X}
|
|
145
|
+
buttonContext={ButtonContext.Icon}
|
|
146
|
+
onButtonClicked={(e) => {
|
|
147
|
+
e.stopPropagation();
|
|
148
|
+
onRemoveField();
|
|
149
|
+
}}
|
|
150
|
+
dataTestId="remove-field-button"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FieldSelection } from './FieldSelection';
|
|
@@ -3,6 +3,7 @@ import { Formik, FormikValues } from 'formik';
|
|
|
3
3
|
import React, { PropsWithChildren, useContext } from 'react';
|
|
4
4
|
import { OptionalObjectSchema } from 'yup/lib/object';
|
|
5
5
|
import { Data } from '../../types/data';
|
|
6
|
+
import { BulkEditContext } from '../Explorer/BulkEdit/BulkEditContext';
|
|
6
7
|
import { QuickEditContext } from '../Explorer/QuickEdit/QuickEditContext';
|
|
7
8
|
import { StationMessage } from '../models';
|
|
8
9
|
import {
|
|
@@ -94,6 +95,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
94
95
|
FormStationProps<TValues, TSubmitResponse>
|
|
95
96
|
>): JSX.Element => {
|
|
96
97
|
const quickEditContext = useContext(QuickEditContext);
|
|
98
|
+
const bulkEditContext = useContext(BulkEditContext);
|
|
97
99
|
|
|
98
100
|
const {
|
|
99
101
|
onSubmit,
|
|
@@ -136,10 +138,12 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
136
138
|
setTabTitle={setTabTitle}
|
|
137
139
|
showSaveHeaderAction={showSaveHeaderAction}
|
|
138
140
|
/>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
{!bulkEditContext && (
|
|
142
|
+
<SaveOnNavigate
|
|
143
|
+
isSubmitting={isFormSubmitting}
|
|
144
|
+
onNavigationCancelled={setValidationError}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
143
147
|
{quickEditContext && (
|
|
144
148
|
<SaveOnDemand
|
|
145
149
|
isSubmitting={isFormSubmitting}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FormikValues, useFormikContext } from 'formik';
|
|
2
2
|
import React, { useContext, useMemo } from 'react';
|
|
3
3
|
import { useHistory } from 'react-router-dom';
|
|
4
|
-
import {
|
|
4
|
+
import { BulkEditContext } from '../../Explorer/BulkEdit/BulkEditContext';
|
|
5
|
+
import { QuickEditContext } from '../../Explorer/QuickEdit/QuickEditContext';
|
|
5
6
|
import { IconName } from '../../Icons';
|
|
6
7
|
import {
|
|
7
8
|
PageHeader,
|
|
@@ -31,8 +32,9 @@ export const FormStationHeader: React.FC<
|
|
|
31
32
|
setTabTitle,
|
|
32
33
|
showSaveHeaderAction,
|
|
33
34
|
}) => {
|
|
34
|
-
const { dirty, resetForm } = useFormikContext<FormikValues>();
|
|
35
|
+
const { dirty, submitForm, resetForm } = useFormikContext<FormikValues>();
|
|
35
36
|
const quickEditContext = useContext(QuickEditContext);
|
|
37
|
+
const bulkEditContext = useContext(BulkEditContext);
|
|
36
38
|
|
|
37
39
|
const history = useHistory();
|
|
38
40
|
|
|
@@ -58,9 +60,12 @@ export const FormStationHeader: React.FC<
|
|
|
58
60
|
icon: IconName.Save,
|
|
59
61
|
kind: 'action',
|
|
60
62
|
actionType: PageHeaderActionType.Context,
|
|
61
|
-
onClick: () => {
|
|
63
|
+
onClick: async () => {
|
|
62
64
|
if (quickEditContext?.isQuickEditMode) {
|
|
63
65
|
quickEditContext.refresh();
|
|
66
|
+
} else if (bulkEditContext?.isBulkEditMode) {
|
|
67
|
+
await submitForm();
|
|
68
|
+
history.replace(history.location.pathname);
|
|
64
69
|
} else {
|
|
65
70
|
history.replace(history.location.pathname);
|
|
66
71
|
}
|
|
@@ -78,6 +83,18 @@ export const FormStationHeader: React.FC<
|
|
|
78
83
|
});
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
if (bulkEditContext?.isBulkEditMode) {
|
|
87
|
+
actionItems.push({
|
|
88
|
+
label: 'Cancel',
|
|
89
|
+
icon: IconName.X,
|
|
90
|
+
kind: 'action',
|
|
91
|
+
onClick: () => {
|
|
92
|
+
resetForm();
|
|
93
|
+
bulkEditContext.cancel();
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
81
98
|
if (cancelNavigationUrl) {
|
|
82
99
|
actionItems.push({
|
|
83
100
|
label: 'Cancel',
|
|
@@ -93,12 +110,14 @@ export const FormStationHeader: React.FC<
|
|
|
93
110
|
|
|
94
111
|
return actionItems;
|
|
95
112
|
}, [
|
|
113
|
+
bulkEditContext,
|
|
96
114
|
cancelNavigationUrl,
|
|
97
115
|
dirty,
|
|
98
116
|
history,
|
|
99
117
|
quickEditContext,
|
|
100
118
|
resetForm,
|
|
101
119
|
showSaveHeaderAction,
|
|
120
|
+
submitForm,
|
|
102
121
|
]);
|
|
103
122
|
|
|
104
123
|
return (
|
|
@@ -129,6 +129,22 @@ const BulkIcon: React.FC<{ className?: string }> = ({ className }) => (
|
|
|
129
129
|
</svg>
|
|
130
130
|
);
|
|
131
131
|
|
|
132
|
+
const BulkEditIcon: React.FC<{ className?: string }> = ({ className }) => (
|
|
133
|
+
<svg
|
|
134
|
+
className={clsx(classes.icons, className)}
|
|
135
|
+
version="1.1"
|
|
136
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
137
|
+
viewBox="0 0 40 40"
|
|
138
|
+
>
|
|
139
|
+
<path
|
|
140
|
+
vectorEffect="non-scaling-stroke"
|
|
141
|
+
fill="none"
|
|
142
|
+
strokeWidth="2"
|
|
143
|
+
d="M32.7,18.5l4.4,4.4-10.5,10.5-4.4-4.4,10.5-10.5ZM32.2,12.9v-7.7H10M8.5,16.5h12.9M8.5,21h12.9M8.5,25.5h7M27.2,18.5v-8.6H2.9v24.7h14.4M22.3,29l-1.6,5.8,6-1.4"
|
|
144
|
+
/>
|
|
145
|
+
</svg>
|
|
146
|
+
);
|
|
147
|
+
|
|
132
148
|
const CalendarIcon: React.FC<{ className?: string }> = ({ className }) => (
|
|
133
149
|
<svg
|
|
134
150
|
className={clsx(classes.icons, className)}
|
|
@@ -915,6 +931,7 @@ export const Icons: React.FC<IconsProps> = ({ icon, className }) => {
|
|
|
915
931
|
[IconName.BackwardTen]: <BackwardTen className={className} />,
|
|
916
932
|
[IconName.Block]: <BlockIcon className={className} />,
|
|
917
933
|
[IconName.Bulk]: <BulkIcon className={className} />,
|
|
934
|
+
[IconName.BulkEdit]: <BulkEditIcon className={className} />,
|
|
918
935
|
[IconName.Calendar]: <CalendarIcon className={className} />,
|
|
919
936
|
[IconName.Checkmark]: <CheckmarkIcon className={className} />,
|
|
920
937
|
[IconName.ChevronDown]: <ChevronDownIcon className={className} />,
|
|
@@ -279,6 +279,17 @@ const ListRenderer = <T extends Data>(
|
|
|
279
279
|
},
|
|
280
280
|
}));
|
|
281
281
|
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (selectionMode !== ListSelectMode.Multi) {
|
|
284
|
+
setListItems((prevState) =>
|
|
285
|
+
prevState.map((i) => ({
|
|
286
|
+
selected: false,
|
|
287
|
+
data: i.data,
|
|
288
|
+
})),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}, [selectionMode]);
|
|
292
|
+
|
|
282
293
|
return (
|
|
283
294
|
<div
|
|
284
295
|
className={clsx(classes.wrapper, 'list-wrapper', className)}
|
package/src/components/index.ts
CHANGED