@axinom/mosaic-ui 0.55.0-rc.5 → 0.55.0-rc.6
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/FormElements/Select/Select.d.ts +15 -11
- package/dist/components/FormElements/Select/Select.d.ts.map +1 -1
- package/dist/components/FormElements/Select/SelectField.d.ts.map +1 -1
- package/dist/components/FormElements/Tags/Tags.d.ts +2 -2
- package/dist/components/FormElements/Tags/Tags.d.ts.map +1 -1
- package/dist/components/FormElements/Tags/TagsField.d.ts +1 -1
- package/dist/components/FormElements/Tags/TagsField.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 +4 -3
- package/src/components/DynamicDataList/DynamicListDataEntry/Renderers/createSelectRenderer/createSelectRenderer.spec.tsx +9 -4
- package/src/components/DynamicDataList/DynamicListDataEntry/Renderers/createSelectRenderer/createSelectRenderer.tsx +2 -2
- package/src/components/FormElements/Select/Select.scss +108 -59
- package/src/components/FormElements/Select/Select.spec.tsx +52 -43
- package/src/components/FormElements/Select/Select.stories.tsx +8 -10
- package/src/components/FormElements/Select/Select.tsx +138 -52
- package/src/components/FormElements/Select/SelectField.tsx +1 -0
- package/src/components/FormElements/Tags/Tags.scss +16 -76
- package/src/components/FormElements/Tags/Tags.spec.tsx +69 -80
- package/src/components/FormElements/Tags/Tags.tsx +54 -62
- package/src/components/FormElements/Tags/TagsField.tsx +3 -13
- package/src/styles/variables.scss +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.55.0-rc.
|
|
3
|
+
"version": "0.55.0-rc.6",
|
|
4
4
|
"description": "UI components for building Axinom Mosaic applications",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
"build-storybook": "storybook build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@axinom/mosaic-core": "^0.4.28-rc.
|
|
35
|
+
"@axinom/mosaic-core": "^0.4.28-rc.6",
|
|
36
36
|
"@faker-js/faker": "^7.4.0",
|
|
37
37
|
"@geoffcox/react-splitter": "^2.1.2",
|
|
38
|
+
"@mui/base": "5.0.0-beta.40",
|
|
38
39
|
"@popperjs/core": "^2.11.8",
|
|
39
40
|
"clsx": "^1.1.0",
|
|
40
41
|
"lodash": "^4.17.21",
|
|
@@ -106,5 +107,5 @@
|
|
|
106
107
|
"publishConfig": {
|
|
107
108
|
"access": "public"
|
|
108
109
|
},
|
|
109
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "1f58b4dfda36f87d416466b789e9372c0216a1c4"
|
|
110
111
|
}
|
|
@@ -44,12 +44,17 @@ describe('createSelectRenderer', () => {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
it(`sets 'select' value to current value`, () => {
|
|
47
|
-
const
|
|
48
|
-
const wrapper = mount(
|
|
47
|
+
const mockOptions = [{ value: 'test-value', label: 'Test Value' }];
|
|
48
|
+
const wrapper = mount(
|
|
49
|
+
<RendererWrapper
|
|
50
|
+
currentValue={mockOptions[0].value}
|
|
51
|
+
options={mockOptions}
|
|
52
|
+
/>,
|
|
53
|
+
);
|
|
49
54
|
|
|
50
|
-
const
|
|
55
|
+
const input = wrapper.find('[role="combobox"]');
|
|
51
56
|
|
|
52
|
-
expect(
|
|
57
|
+
expect(input.prop('value')).toBe(mockOptions[0].label);
|
|
53
58
|
});
|
|
54
59
|
|
|
55
60
|
it(`emits 'onValueChange' with the new value when a new option has been selected`, () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { ChangeEvent } from 'react';
|
|
2
2
|
import { DynamicListDataEntryRenderer } from '../../../../DynamicDataList/DynamicDataList.model';
|
|
3
3
|
import { Select } from '../../../../FormElements';
|
|
4
4
|
import { CreateSelectRendererConfig } from '../renderers.model';
|
|
@@ -33,7 +33,7 @@ export const createSelectRenderer = ({
|
|
|
33
33
|
onValueChange(defaultValue);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const onChangeHandler = (e:
|
|
36
|
+
const onChangeHandler = (e: ChangeEvent<HTMLInputElement>): void => {
|
|
37
37
|
onValueChange(transform(e.currentTarget.value)); // emit onChange with transformed value
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -1,79 +1,128 @@
|
|
|
1
1
|
@import '../../../styles/common.scss';
|
|
2
2
|
|
|
3
|
-
@function svg-arrow-glyph($color) {
|
|
4
|
-
@return url('data:image/svg+xml;utf8,<svg stroke="' + $color + '" version="1.1" xmlns="http://www.w3.org/2000/svg" width="40px" height="20px" viewBox="0 0 40 40"><path vector-effect="non-scaling-stroke" fill="none" stroke-width="2" d="M38.5,9.5L20,30.5L1.5,9.5" /></svg>');
|
|
5
|
-
}
|
|
6
|
-
|
|
7
3
|
.container {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
appearance: none;
|
|
12
|
-
cursor: pointer;
|
|
13
|
-
color: var(--input-color, $input-color);
|
|
14
|
-
border: 1px solid var(--input-border-color, $input-border-color);
|
|
15
|
-
width: max-content;
|
|
16
|
-
outline: none;
|
|
17
|
-
font-size: var(--label-font-size, $label-font-size);
|
|
18
|
-
max-width: $select-max-width;
|
|
4
|
+
.inputWrapper {
|
|
5
|
+
position: relative;
|
|
6
|
+
max-width: var(--input-max-width, $input-max-width);
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
background-repeat: no-repeat;
|
|
25
|
-
background-position-y: center;
|
|
26
|
-
background-position-x: 100%;
|
|
8
|
+
.button {
|
|
9
|
+
position: absolute;
|
|
10
|
+
right: 0;
|
|
11
|
+
top: 0;
|
|
27
12
|
|
|
28
|
-
|
|
13
|
+
svg {
|
|
14
|
+
height: 50%;
|
|
15
|
+
* {
|
|
16
|
+
stroke: var(
|
|
17
|
+
--popper-trigger-button-color,
|
|
18
|
+
$popper-trigger-button-color
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
&:disabled {
|
|
24
|
+
svg * {
|
|
25
|
+
stroke: var(--select-arrow-color, $select-disabled-arrow-color);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
input {
|
|
31
|
+
color: var(--input-color, $input-color);
|
|
32
|
+
border: 1px solid var(--input-border-color, $input-border-color);
|
|
33
|
+
padding: 0 48px 0 12px;
|
|
34
|
+
display: inline-block;
|
|
35
|
+
font-size: var(--label-font-size, $label-font-size);
|
|
36
|
+
outline: none;
|
|
37
|
+
height: 50px;
|
|
38
|
+
width: 100%;
|
|
39
|
+
transition: box-shadow 0.15s ease-in-out 0s;
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
&::placeholder {
|
|
42
|
+
color: var(--input-placeholder-color, $input-placeholder-color);
|
|
43
|
+
}
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
&.hasError {
|
|
46
|
+
border: 1px solid
|
|
47
|
+
var(--input-invalid-border-color, $input-invalid-border-color);
|
|
48
|
+
}
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
&:disabled {
|
|
51
|
+
background-color: var(
|
|
52
|
+
--input-disabled-background-color,
|
|
53
|
+
$input-disabled-background-color
|
|
54
|
+
);
|
|
55
|
+
color: var(--input-disabled-font-color, $input-disabled-font-color);
|
|
56
|
+
border-color: var(
|
|
57
|
+
--input-disabled-border-color,
|
|
58
|
+
$input-disabled-border-color
|
|
59
|
+
);
|
|
60
|
+
}
|
|
46
61
|
|
|
47
|
-
|
|
62
|
+
&:hover:enabled,
|
|
63
|
+
&:focus {
|
|
64
|
+
border-color: var(--input-hover-color, $input-hover-color);
|
|
65
|
+
box-shadow: 0 0 0 2px var(--input-hover-color, $input-hover-color);
|
|
48
66
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
&.hasError {
|
|
68
|
+
border-color: var(
|
|
69
|
+
--input-invalid-border-color,
|
|
70
|
+
$input-invalid-border-color
|
|
71
|
+
);
|
|
72
|
+
box-shadow: 0 0 0 2px
|
|
73
|
+
var(--input-invalid-hover-color, $input-invalid-hover-color);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&:disabled {
|
|
77
|
+
border-color: var(
|
|
78
|
+
--input-disabled-border-color,
|
|
79
|
+
$input-disabled-border-color
|
|
80
|
+
);
|
|
81
|
+
box-shadow: none;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
56
84
|
}
|
|
57
85
|
}
|
|
86
|
+
}
|
|
58
87
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
.popperContent {
|
|
89
|
+
ul {
|
|
90
|
+
display: grid;
|
|
91
|
+
row-gap: 1px;
|
|
92
|
+
background-color: var(--popper-background-color, $popper-background-color);
|
|
93
|
+
padding: 0px;
|
|
94
|
+
margin-top: 1px;
|
|
95
|
+
margin-bottom: 1px;
|
|
96
|
+
border: 1px solid var(--popper-border-color, $popper-border-color);
|
|
97
|
+
overflow-y: auto;
|
|
98
|
+
max-height: 509px;
|
|
99
|
+
|
|
100
|
+
li {
|
|
101
|
+
display: grid;
|
|
102
|
+
place-items: center left;
|
|
103
|
+
height: 50px;
|
|
104
|
+
font-size: var(--popper-item-font-size, $popper-item-font-size);
|
|
105
|
+
color: var(--popper-text-color, $popper-text-color);
|
|
106
|
+
background-color: white;
|
|
107
|
+
padding: 0 12px;
|
|
108
|
+
list-style: none;
|
|
109
|
+
cursor: default;
|
|
66
110
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
&:hover {
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
background-color: var(
|
|
114
|
+
--popper-background-selected-color,
|
|
115
|
+
$popper-background-selected-color
|
|
116
|
+
);
|
|
117
|
+
}
|
|
71
118
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
&[aria-selected='true'],
|
|
120
|
+
&[class='Mui-focused Mui-focusVisible'] {
|
|
121
|
+
background-color: var(
|
|
122
|
+
--popper-background-selected-color,
|
|
123
|
+
$popper-background-selected-color
|
|
124
|
+
);
|
|
125
|
+
}
|
|
77
126
|
}
|
|
78
127
|
}
|
|
79
128
|
}
|
|
@@ -1,95 +1,104 @@
|
|
|
1
|
-
import { shallow } from 'enzyme';
|
|
1
|
+
import { mount, shallow } from 'enzyme';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Select } from './Select';
|
|
4
4
|
|
|
5
|
+
const mockLabel = 'mockLabel';
|
|
6
|
+
|
|
7
|
+
const mockOptions = [
|
|
8
|
+
{
|
|
9
|
+
value: '1',
|
|
10
|
+
label: 'One',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
value: '2',
|
|
14
|
+
label: 'Two',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: '3',
|
|
18
|
+
label: 'Three',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
5
22
|
describe('Select', () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.body.innerHTML = '';
|
|
25
|
+
});
|
|
26
|
+
|
|
6
27
|
it('renders the component without crashing', () => {
|
|
7
|
-
const wrapper = shallow(<Select name=
|
|
28
|
+
const wrapper = shallow(<Select name="test-name" options={[]} />);
|
|
8
29
|
|
|
9
30
|
expect(wrapper).toBeTruthy();
|
|
10
31
|
});
|
|
11
32
|
|
|
12
33
|
it('displays a label', () => {
|
|
13
|
-
const
|
|
14
|
-
|
|
34
|
+
const wrapper = shallow(
|
|
35
|
+
<Select name="test-name" label={mockLabel} options={[]} />,
|
|
36
|
+
);
|
|
15
37
|
|
|
16
|
-
const label = wrapper.dive().find('label');
|
|
38
|
+
const label = wrapper.dive().find('[data-test-id="form-field-label"]');
|
|
17
39
|
|
|
18
40
|
expect(label.text()).toBe(mockLabel);
|
|
19
41
|
});
|
|
20
42
|
|
|
21
|
-
it('
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
disabled: true,
|
|
26
|
-
id: 'test-id',
|
|
27
|
-
name: 'test-name',
|
|
28
|
-
onBlur: () => null,
|
|
29
|
-
onChange: () => null,
|
|
30
|
-
onFocus: () => null,
|
|
31
|
-
} as Record<string, unknown>;
|
|
32
|
-
|
|
33
|
-
const wrapper = shallow(<Select name="test-name" {...mockProps} />);
|
|
43
|
+
it('the given value must be filled in input', () => {
|
|
44
|
+
const wrapper = mount(
|
|
45
|
+
<Select name={'test-name'} options={mockOptions} value="2" />,
|
|
46
|
+
);
|
|
34
47
|
|
|
35
|
-
const
|
|
48
|
+
const input = wrapper.find('[role="combobox"]');
|
|
36
49
|
|
|
37
|
-
expect(
|
|
50
|
+
expect(input.prop('value')).toEqual(mockOptions[1].label);
|
|
38
51
|
});
|
|
39
52
|
|
|
40
|
-
it('
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const mockValueUpdated = 'updated-test-value';
|
|
44
|
-
const wrapper = shallow(
|
|
45
|
-
<Select name="test-name" value={mockValue} onChange={spy} />,
|
|
53
|
+
it('the popper must be rendered on input click', () => {
|
|
54
|
+
const wrapper = mount(
|
|
55
|
+
<Select name={'test-name'} options={mockOptions} value="2" />,
|
|
46
56
|
);
|
|
57
|
+
const input = wrapper.find('[role="combobox"]');
|
|
58
|
+
input.simulate('mousedown');
|
|
47
59
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
expect(select.prop('value')).toEqual(mockValue);
|
|
51
|
-
|
|
52
|
-
select.simulate('change', { target: { value: mockValueUpdated } });
|
|
53
|
-
|
|
54
|
-
expect(spy).toHaveBeenCalledTimes(1);
|
|
55
|
-
expect(spy).toHaveBeenCalledWith({ target: { value: mockValueUpdated } });
|
|
60
|
+
const popper = document.querySelector('[role="tooltip"]');
|
|
61
|
+
expect(popper).not.toBeNull();
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
it('raises change, blur, and focus events', () => {
|
|
59
65
|
const changeSpy = jest.fn();
|
|
60
66
|
const blurSpy = jest.fn();
|
|
61
67
|
const focusSpy = jest.fn();
|
|
62
|
-
const wrapper =
|
|
68
|
+
const wrapper = mount(
|
|
63
69
|
<Select
|
|
64
70
|
name={'test-name'}
|
|
65
71
|
onChange={changeSpy}
|
|
66
72
|
onBlur={blurSpy}
|
|
67
73
|
onFocus={focusSpy}
|
|
74
|
+
options={mockOptions}
|
|
68
75
|
/>,
|
|
69
76
|
);
|
|
70
77
|
|
|
71
|
-
const
|
|
78
|
+
const input = wrapper.find('[role="combobox"]');
|
|
79
|
+
input.simulate('mousedown');
|
|
80
|
+
const firstOption = document.querySelector('[role="option"]');
|
|
81
|
+
firstOption?.dispatchEvent(new Event('click', { bubbles: true }));
|
|
72
82
|
|
|
73
|
-
select.simulate('change');
|
|
74
83
|
expect(changeSpy).toHaveBeenCalledTimes(1);
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
input.simulate('blur');
|
|
77
86
|
expect(blurSpy).toHaveBeenCalledTimes(1);
|
|
78
87
|
|
|
79
|
-
|
|
88
|
+
input.simulate('focus');
|
|
80
89
|
expect(focusSpy).toHaveBeenCalledTimes(1);
|
|
81
90
|
});
|
|
82
91
|
|
|
83
92
|
it('applies error styling and renders error message when an error is passed', () => {
|
|
84
93
|
const mockErrorMessage = 'test-error-message';
|
|
85
94
|
const wrapper = shallow(
|
|
86
|
-
<Select name={'test-name'} error={mockErrorMessage} />,
|
|
95
|
+
<Select name={'test-name'} error={mockErrorMessage} options={[]} />,
|
|
87
96
|
);
|
|
88
97
|
|
|
89
|
-
const errorMsg = wrapper.dive().find('
|
|
90
|
-
const
|
|
98
|
+
const errorMsg = wrapper.dive().find('[data-test-id="form-field-error"]');
|
|
99
|
+
const input = wrapper.find('[role="combobox"]');
|
|
91
100
|
|
|
92
101
|
expect(errorMsg.text()).toBe(mockErrorMessage);
|
|
93
|
-
expect(
|
|
102
|
+
expect(input.hasClass('hasError')).toBe(true);
|
|
94
103
|
});
|
|
95
104
|
});
|
|
@@ -6,14 +6,7 @@ import { createGroups } from '../../../helpers/storybook';
|
|
|
6
6
|
import { Select } from './Select';
|
|
7
7
|
|
|
8
8
|
const groups = createGroups({
|
|
9
|
-
Behavior: [
|
|
10
|
-
'autoFocus',
|
|
11
|
-
'disabled',
|
|
12
|
-
'inlineMode',
|
|
13
|
-
'name',
|
|
14
|
-
'id',
|
|
15
|
-
'addEmptyOption',
|
|
16
|
-
],
|
|
9
|
+
Behavior: ['autoFocus', 'disabled', 'inlineMode', 'name', 'id'],
|
|
17
10
|
Content: ['label', 'options', 'error', 'tooltipContent', 'value'],
|
|
18
11
|
Styling: ['className'],
|
|
19
12
|
Events: ['onChange', 'onBlur', 'onFocus'],
|
|
@@ -26,6 +19,7 @@ const meta: Meta<typeof Select> = {
|
|
|
26
19
|
...groups,
|
|
27
20
|
},
|
|
28
21
|
};
|
|
22
|
+
|
|
29
23
|
export default meta;
|
|
30
24
|
|
|
31
25
|
export const Main: StoryObj<typeof Select> = {
|
|
@@ -37,14 +31,18 @@ export const Main: StoryObj<typeof Select> = {
|
|
|
37
31
|
{ value: '2', label: 'Two' },
|
|
38
32
|
{ value: '3', label: 'Three' },
|
|
39
33
|
{ value: '4', label: 'Four' },
|
|
34
|
+
{ value: '5', label: 'Five' },
|
|
35
|
+
{ value: '6', label: 'Six' },
|
|
36
|
+
{ value: '7', label: 'Seven' },
|
|
37
|
+
{ value: '8', label: 'Eight' },
|
|
38
|
+
{ value: '9', label: 'Nine' },
|
|
39
|
+
{ value: '10', label: 'Ten' },
|
|
40
40
|
],
|
|
41
41
|
tooltipContent: faker.lorem.paragraph(2),
|
|
42
|
-
addEmptyOption: false,
|
|
43
42
|
},
|
|
44
43
|
render: (args) =>
|
|
45
44
|
React.createElement(() => {
|
|
46
45
|
const [value, setValue] = useState(args.value);
|
|
47
|
-
|
|
48
46
|
return (
|
|
49
47
|
<Select
|
|
50
48
|
{...args}
|
|
@@ -1,73 +1,159 @@
|
|
|
1
|
+
import { Popper, useAutocomplete, UseAutocompleteProps } from '@mui/base';
|
|
1
2
|
import clsx from 'clsx';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import React, { ChangeEvent } from 'react';
|
|
4
|
+
import { Button, ButtonContext } from '../../Buttons';
|
|
5
|
+
import { IconName } from '../../Icons';
|
|
6
|
+
import { BaseFormControl, BaseInputEvents } from '../Form.models';
|
|
4
7
|
import { FormElementContainer } from '../FormElementContainer';
|
|
5
8
|
import classes from './Select.scss';
|
|
6
9
|
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
export interface SelectOption {
|
|
11
|
+
value: string | number | string[];
|
|
12
|
+
label: string | number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type TrimmedUseAutocompleteProps = Omit<
|
|
16
|
+
UseAutocompleteProps<SelectOption, false, true, false>,
|
|
17
|
+
'value' | 'onChange'
|
|
18
|
+
>;
|
|
19
|
+
export interface SelectProps
|
|
20
|
+
extends BaseFormControl,
|
|
21
|
+
BaseInputEvents,
|
|
22
|
+
TrimmedUseAutocompleteProps {
|
|
23
|
+
/** An array of options */
|
|
24
|
+
options: SelectOption[];
|
|
12
25
|
/** Whether or not the control should start focused (default: false) */
|
|
13
26
|
autoFocus?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
27
|
+
/** Current value the form control has */
|
|
28
|
+
value?: string | string[] | number;
|
|
16
29
|
/** Select placeholder */
|
|
17
30
|
placeholder?: string;
|
|
31
|
+
/** Allows to clear the input field and leave empty */
|
|
32
|
+
addEmptyOption?: boolean;
|
|
18
33
|
}
|
|
19
34
|
|
|
20
|
-
export const Select: React.FC<SelectProps> = ({
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
export const Select: React.FC<SelectProps> = (props) => {
|
|
36
|
+
const {
|
|
37
|
+
name,
|
|
38
|
+
error,
|
|
39
|
+
disabled,
|
|
40
|
+
id,
|
|
41
|
+
label,
|
|
42
|
+
className,
|
|
43
|
+
tooltipContent,
|
|
44
|
+
inlineMode,
|
|
45
|
+
autoFocus,
|
|
46
|
+
value,
|
|
47
|
+
onBlur,
|
|
48
|
+
onFocus,
|
|
49
|
+
onChange,
|
|
50
|
+
placeholder,
|
|
51
|
+
addEmptyOption,
|
|
52
|
+
...autoCompleteProps
|
|
53
|
+
} = props;
|
|
54
|
+
const {
|
|
55
|
+
getRootProps,
|
|
56
|
+
getInputProps,
|
|
57
|
+
getListboxProps,
|
|
58
|
+
getOptionProps,
|
|
59
|
+
getPopupIndicatorProps,
|
|
60
|
+
groupedOptions,
|
|
61
|
+
popupOpen,
|
|
62
|
+
setAnchorEl,
|
|
63
|
+
anchorEl,
|
|
64
|
+
} = useAutocomplete({
|
|
65
|
+
id,
|
|
66
|
+
disabled,
|
|
67
|
+
value:
|
|
68
|
+
autoCompleteProps.options.find((option) => option.value === value) ??
|
|
69
|
+
null,
|
|
70
|
+
onChange: ({ target: _, ...event }, value) => {
|
|
71
|
+
onChange?.({
|
|
72
|
+
...event,
|
|
73
|
+
currentTarget: {
|
|
74
|
+
...event.currentTarget,
|
|
75
|
+
id,
|
|
76
|
+
name,
|
|
77
|
+
value: value?.value ?? '',
|
|
78
|
+
},
|
|
79
|
+
} as ChangeEvent<HTMLInputElement>);
|
|
80
|
+
},
|
|
81
|
+
disableClearable: !addEmptyOption,
|
|
82
|
+
...autoCompleteProps,
|
|
83
|
+
});
|
|
37
84
|
|
|
38
85
|
return (
|
|
39
86
|
<FormElementContainer
|
|
40
|
-
{
|
|
87
|
+
id={id}
|
|
88
|
+
label={label}
|
|
89
|
+
tooltipContent={tooltipContent}
|
|
90
|
+
inlineMode={inlineMode}
|
|
41
91
|
className={clsx(classes.container, 'select-container', className)}
|
|
42
|
-
error={
|
|
92
|
+
error={error}
|
|
43
93
|
dataTestFieldType="Select"
|
|
44
94
|
>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
value={value}
|
|
50
|
-
disabled={disabled}
|
|
51
|
-
autoFocus={autoFocus}
|
|
52
|
-
onChange={onChange}
|
|
53
|
-
onBlur={onBlur}
|
|
54
|
-
onFocus={onFocus}
|
|
95
|
+
<div
|
|
96
|
+
ref={setAnchorEl}
|
|
97
|
+
{...getRootProps()}
|
|
98
|
+
className={clsx(classes.inputWrapper)}
|
|
55
99
|
>
|
|
56
|
-
|
|
57
|
-
(
|
|
58
|
-
|
|
100
|
+
<input
|
|
101
|
+
{...getInputProps()}
|
|
102
|
+
name={name}
|
|
103
|
+
className={clsx({ [classes.hasError]: Boolean(error) })}
|
|
104
|
+
autoFocus={autoFocus}
|
|
105
|
+
onBlur={(event) => {
|
|
106
|
+
getInputProps().onBlur?.(event);
|
|
107
|
+
onBlur?.(event);
|
|
108
|
+
}}
|
|
109
|
+
onFocus={(event) => {
|
|
110
|
+
getInputProps().onFocus?.(event);
|
|
111
|
+
onFocus?.(event);
|
|
112
|
+
}}
|
|
113
|
+
placeholder={placeholder}
|
|
114
|
+
/>
|
|
115
|
+
<Button
|
|
116
|
+
className={clsx(classes.button)}
|
|
117
|
+
buttonContext={ButtonContext.None}
|
|
118
|
+
icon={popupOpen ? IconName.ChevronUp : IconName.ChevronDown}
|
|
119
|
+
onButtonClicked={getPopupIndicatorProps().onClick}
|
|
120
|
+
onBlur={getPopupIndicatorProps().onBlur}
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
<Popper
|
|
125
|
+
open={popupOpen}
|
|
126
|
+
anchorEl={anchorEl}
|
|
127
|
+
style={{
|
|
128
|
+
width: anchorEl ? anchorEl.clientWidth : undefined,
|
|
129
|
+
zIndex: 999,
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<PopperContent>
|
|
133
|
+
{groupedOptions.length > 0 && (
|
|
134
|
+
<ul {...getListboxProps()}>
|
|
135
|
+
{(groupedOptions as typeof autoCompleteProps.options).map(
|
|
136
|
+
(option, index) => (
|
|
137
|
+
<li
|
|
138
|
+
key={String(option.value)}
|
|
139
|
+
{...getOptionProps({ option, index })}
|
|
140
|
+
>
|
|
141
|
+
{option.label}
|
|
142
|
+
</li>
|
|
143
|
+
),
|
|
144
|
+
)}
|
|
145
|
+
</ul>
|
|
59
146
|
)}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{disabled ? '' : placeholder}
|
|
63
|
-
</option>
|
|
64
|
-
)}
|
|
65
|
-
{options.map((option) => (
|
|
66
|
-
<option key={option.value} value={option.value}>
|
|
67
|
-
{option.label}
|
|
68
|
-
</option>
|
|
69
|
-
))}
|
|
70
|
-
</select>
|
|
147
|
+
</PopperContent>
|
|
148
|
+
</Popper>
|
|
71
149
|
</FormElementContainer>
|
|
72
150
|
);
|
|
73
151
|
};
|
|
152
|
+
|
|
153
|
+
const PopperContent: React.FC = ({ children }) => {
|
|
154
|
+
return (
|
|
155
|
+
<div className={clsx(classes.popperContent, 'select-popper-content')}>
|
|
156
|
+
{children}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useFormikError } from '../useFormikError';
|
|
3
3
|
import { Select, SelectProps } from './Select';
|
|
4
|
+
|
|
4
5
|
export const SelectField: React.FC<Omit<SelectProps, 'error'>> = (props) => {
|
|
5
6
|
const error = useFormikError(props.name);
|
|
6
7
|
return <Select {...props} error={error} />;
|