@dhis2-ui/select 10.17.0 → 10.19.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/build/cjs/simple-single-select/__stories__/WithMenuMaxWidth.js +34 -0
- package/build/cjs/simple-single-select/__stories__/WithMenuMinWidth.js +37 -0
- package/build/cjs/simple-single-select/menu/menu.js +17 -8
- package/build/cjs/simple-single-select/simple-single-select.js +19 -9
- package/build/cjs/simple-single-select/simple-single-select.prod.stories.js +14 -0
- package/build/cjs/simple-single-select/simple-single-select.test.js +62 -0
- package/build/cjs/simple-single-select-field/simple-single-select-field.js +4 -0
- package/build/cjs/simple-single-select-field/simple-single-select-field.prod.stories.js +53 -2
- package/build/cjs/simple-single-select-field/simple-single-select-field.test.js +4 -0
- package/build/cjs/single-select-option/single-select-option.js +4 -4
- package/build/es/simple-single-select/__stories__/WithMenuMaxWidth.js +26 -0
- package/build/es/simple-single-select/__stories__/WithMenuMinWidth.js +29 -0
- package/build/es/simple-single-select/menu/menu.js +17 -8
- package/build/es/simple-single-select/simple-single-select.js +19 -9
- package/build/es/simple-single-select/simple-single-select.prod.stories.js +2 -0
- package/build/es/simple-single-select/simple-single-select.test.js +62 -0
- package/build/es/simple-single-select-field/simple-single-select-field.js +4 -0
- package/build/es/simple-single-select-field/simple-single-select-field.prod.stories.js +49 -0
- package/build/es/simple-single-select-field/simple-single-select-field.test.js +4 -0
- package/build/es/single-select-option/single-select-option.js +4 -4
- package/package.json +15 -15
- package/src/simple-single-select/__stories__/WithMenuMaxWidth.js +24 -0
- package/src/simple-single-select/__stories__/WithMenuMinWidth.js +25 -0
- package/src/simple-single-select/menu/menu.js +26 -8
- package/src/simple-single-select/simple-single-select.js +19 -6
- package/src/simple-single-select/simple-single-select.prod.stories.js +2 -0
- package/src/simple-single-select/simple-single-select.test.js +68 -0
- package/src/simple-single-select-field/simple-single-select-field.js +6 -0
- package/src/simple-single-select-field/simple-single-select-field.prod.stories.js +49 -0
- package/src/simple-single-select-field/simple-single-select-field.test.js +4 -0
- package/src/single-select-option/single-select-option.js +5 -3
- package/types/index.d.ts +45 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
1
2
|
import '@testing-library/jest-dom';
|
|
2
3
|
import { render, fireEvent, screen } from '@testing-library/react';
|
|
3
4
|
import React, { useState } from 'react';
|
|
@@ -142,6 +143,67 @@ describe('<SimpleSingleSelect />', () => {
|
|
|
142
143
|
const menu = listbox.parentNode.parentNode.parentNode;
|
|
143
144
|
expect(menu.style.maxHeight).toBe('100px');
|
|
144
145
|
});
|
|
146
|
+
describe('dropdown menu width', () => {
|
|
147
|
+
let offsetWidth;
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
// The menu's width is derived from the select's measured width,
|
|
150
|
+
// which is always 0 in jsdom
|
|
151
|
+
offsetWidth = jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(120);
|
|
152
|
+
});
|
|
153
|
+
afterEach(() => {
|
|
154
|
+
offsetWidth.mockRestore();
|
|
155
|
+
});
|
|
156
|
+
const renderAndOpen = props => {
|
|
157
|
+
render(/*#__PURE__*/React.createElement(SimpleSingleSelect, _extends({
|
|
158
|
+
name: "simple",
|
|
159
|
+
selected: {
|
|
160
|
+
value: 'foo',
|
|
161
|
+
label: 'Foo'
|
|
162
|
+
},
|
|
163
|
+
onChange: () => null,
|
|
164
|
+
options: [{
|
|
165
|
+
value: 'foo',
|
|
166
|
+
label: 'Foo'
|
|
167
|
+
}]
|
|
168
|
+
}, props)));
|
|
169
|
+
fireEvent.click(screen.getByRole('combobox'));
|
|
170
|
+
const listbox = screen.getByRole('listbox');
|
|
171
|
+
return listbox.parentNode.parentNode.parentNode;
|
|
172
|
+
};
|
|
173
|
+
it('should match the width of the select by default', () => {
|
|
174
|
+
const menu = renderAndOpen();
|
|
175
|
+
expect(menu.style.width).toBe('120px');
|
|
176
|
+
});
|
|
177
|
+
it('should not be narrower than menuMinWidth or the select', () => {
|
|
178
|
+
const menu = renderAndOpen({
|
|
179
|
+
menuMinWidth: '240px'
|
|
180
|
+
});
|
|
181
|
+
expect(menu.style.minWidth).toBe('max(120px, 240px)');
|
|
182
|
+
});
|
|
183
|
+
it('should not be wider than menuMaxWidth', () => {
|
|
184
|
+
const menu = renderAndOpen({
|
|
185
|
+
menuMaxWidth: '200px'
|
|
186
|
+
});
|
|
187
|
+
expect(menu.style.maxWidth).toBe('200px');
|
|
188
|
+
expect(menu.style.minWidth).toBe('120px');
|
|
189
|
+
});
|
|
190
|
+
it('should re-measure the select when the menu is reopened', () => {
|
|
191
|
+
renderAndOpen({
|
|
192
|
+
menuMinWidth: '240px'
|
|
193
|
+
});
|
|
194
|
+
const comboBox = screen.getByRole('combobox');
|
|
195
|
+
|
|
196
|
+
// close the menu
|
|
197
|
+
fireEvent.click(comboBox);
|
|
198
|
+
|
|
199
|
+
// widen the select while the menu is closed, then reopen it
|
|
200
|
+
offsetWidth.mockReturnValue(300);
|
|
201
|
+
fireEvent.click(comboBox);
|
|
202
|
+
const listbox = screen.getByRole('listbox');
|
|
203
|
+
const menu = listbox.parentNode.parentNode.parentNode;
|
|
204
|
+
expect(menu.style.minWidth).toBe('max(300px, 240px)');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
145
207
|
it('should accept a placeholder', () => {
|
|
146
208
|
render(/*#__PURE__*/React.createElement(SimpleSingleSelect, {
|
|
147
209
|
name: "simple",
|
|
@@ -101,6 +101,10 @@ SimpleSingleSelectField.propTypes = {
|
|
|
101
101
|
menuLoadingText: PropTypes.string,
|
|
102
102
|
/** Allows to modify the max height of the menu **/
|
|
103
103
|
menuMaxHeight: PropTypes.string,
|
|
104
|
+
/** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
|
|
105
|
+
menuMaxWidth: PropTypes.string,
|
|
106
|
+
/** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
|
|
107
|
+
menuMinWidth: PropTypes.string,
|
|
104
108
|
/** String that will be displayed when the select is being filtered but the options array is empty **/
|
|
105
109
|
noMatchText: requiredIf(props => props.filterable, PropTypes.string),
|
|
106
110
|
/** Allows to override what's rendered inside the `button[role="option"]`.
|
|
@@ -42,6 +42,19 @@ const options = [{
|
|
|
42
42
|
value: '10',
|
|
43
43
|
label: 'ten'
|
|
44
44
|
}];
|
|
45
|
+
const longOptions = [{
|
|
46
|
+
value: '1',
|
|
47
|
+
label: 'option one'
|
|
48
|
+
}, {
|
|
49
|
+
value: '2',
|
|
50
|
+
label: 'option two'
|
|
51
|
+
}, {
|
|
52
|
+
value: '3',
|
|
53
|
+
label: 'option three'
|
|
54
|
+
}, {
|
|
55
|
+
value: '4',
|
|
56
|
+
label: 'A longer option that exceeds the minimum'
|
|
57
|
+
}];
|
|
45
58
|
export default {
|
|
46
59
|
title: 'SimpleSingleSelectField',
|
|
47
60
|
component: SimpleSingleSelectField,
|
|
@@ -169,4 +182,40 @@ export const InputWidth = () => {
|
|
|
169
182
|
onChange: nextValue => setValue(nextValue),
|
|
170
183
|
options: options
|
|
171
184
|
}));
|
|
185
|
+
};
|
|
186
|
+
export const WithMenuMinWidth = () => {
|
|
187
|
+
var _longOptions$find;
|
|
188
|
+
const [value, setValue] = useState('');
|
|
189
|
+
const valueLabel = value ? (_longOptions$find = longOptions.find(option => option.value === value)) === null || _longOptions$find === void 0 ? void 0 : _longOptions$find.label : '';
|
|
190
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
191
|
+
style: {
|
|
192
|
+
width: 120
|
|
193
|
+
}
|
|
194
|
+
}, /*#__PURE__*/React.createElement(SimpleSingleSelectField, {
|
|
195
|
+
name: "simple",
|
|
196
|
+
label: "This is the label",
|
|
197
|
+
value: value,
|
|
198
|
+
valueLabel: valueLabel,
|
|
199
|
+
onChange: nextValue => setValue(nextValue),
|
|
200
|
+
options: longOptions,
|
|
201
|
+
menuMinWidth: "240px"
|
|
202
|
+
}));
|
|
203
|
+
};
|
|
204
|
+
export const WithMenuMaxWidth = () => {
|
|
205
|
+
var _longOptions$find2;
|
|
206
|
+
const [value, setValue] = useState('');
|
|
207
|
+
const valueLabel = value ? (_longOptions$find2 = longOptions.find(option => option.value === value)) === null || _longOptions$find2 === void 0 ? void 0 : _longOptions$find2.label : '';
|
|
208
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
209
|
+
style: {
|
|
210
|
+
width: 120
|
|
211
|
+
}
|
|
212
|
+
}, /*#__PURE__*/React.createElement(SimpleSingleSelectField, {
|
|
213
|
+
name: "simple",
|
|
214
|
+
label: "This is the label",
|
|
215
|
+
value: value,
|
|
216
|
+
valueLabel: valueLabel,
|
|
217
|
+
onChange: nextValue => setValue(nextValue),
|
|
218
|
+
options: longOptions,
|
|
219
|
+
menuMaxWidth: "200px"
|
|
220
|
+
}));
|
|
172
221
|
};
|
|
@@ -49,6 +49,8 @@ describe('<SimpleSingleSelectField />', () => {
|
|
|
49
49
|
loading: false,
|
|
50
50
|
menuLoadingText: "",
|
|
51
51
|
menuMaxHeight: "",
|
|
52
|
+
menuMaxWidth: "400px",
|
|
53
|
+
menuMinWidth: "240px",
|
|
52
54
|
noMatchText: "",
|
|
53
55
|
optionUpdateStrategy: "off",
|
|
54
56
|
placeholder: "",
|
|
@@ -86,6 +88,8 @@ describe('<SimpleSingleSelectField />', () => {
|
|
|
86
88
|
expect(SimpleSingleSelect.mock.calls[0][0].loading).toBe(false);
|
|
87
89
|
expect(SimpleSingleSelect.mock.calls[0][0].menuLoadingText).toBe('');
|
|
88
90
|
expect(SimpleSingleSelect.mock.calls[0][0].menuMaxHeight).toBe('');
|
|
91
|
+
expect(SimpleSingleSelect.mock.calls[0][0].menuMaxWidth).toBe('400px');
|
|
92
|
+
expect(SimpleSingleSelect.mock.calls[0][0].menuMinWidth).toBe('240px');
|
|
89
93
|
expect(SimpleSingleSelect.mock.calls[0][0].noMatchText).toBe('');
|
|
90
94
|
expect(SimpleSingleSelect.mock.calls[0][0].optionUpdateStrategy).toBe('off');
|
|
91
95
|
expect(SimpleSingleSelect.mock.calls[0][0].placeholder).toBe('');
|
|
@@ -16,17 +16,17 @@ const SingleSelectOption = ({
|
|
|
16
16
|
"data-test": dataTest,
|
|
17
17
|
"data-value": value,
|
|
18
18
|
"data-label": label,
|
|
19
|
-
className: _JSXStyle.dynamic([["
|
|
19
|
+
className: _JSXStyle.dynamic([["556453451", [colors.grey900, spacers.dp8, spacers.dp12, colors.grey200, colors.teal050, colors.grey500, active ? `url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuNjMyMDkgNy44MzIxNEMzLjI3MjA5IDcuNDcyMTQgMi43MjAwOSA3LjQ3MjE0IDIuMzYwMDkgNy44MzIxNEMyLjAwMDA5IDguMTkyMTQgMi4wMDAwOSA4Ljc0NDE0IDIuMzYwMDkgOS4xMDQxNEw1LjYyNDA5IDEyLjM2ODFDNS45ODQwOSAxMi43MjgxIDYuNTM2MDkgMTIuNzI4MSA2Ljg5NjA5IDEyLjM2ODFMMTQuNjQ4MSA0LjY0MDE0QzE1LjAwODEgNC4yODAxNCAxNS4wMDgxIDMuNzI4MTQgMTQuNjQ4MSAzLjM2ODE0QzE0LjI4ODEgMy4wMDgxNCAxMy43MzYxIDMuMDA4MTQgMTMuMzUyMSAzLjM2ODE0TDYuMjcyMDkgMTAuNDcyMUwzLjYzMjA5IDcuODMyMTRaIiBmaWxsPSIjMDA2OTVDIi8+Cjwvc3ZnPgo=')` : 'none']]]) + " " + (cx(className, {
|
|
20
20
|
disabled,
|
|
21
21
|
active
|
|
22
22
|
}) || "")
|
|
23
23
|
}, /*#__PURE__*/React.createElement("span", {
|
|
24
24
|
"aria-hidden": "true",
|
|
25
|
-
className: _JSXStyle.dynamic([["
|
|
25
|
+
className: _JSXStyle.dynamic([["556453451", [colors.grey900, spacers.dp8, spacers.dp12, colors.grey200, colors.teal050, colors.grey500, active ? `url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuNjMyMDkgNy44MzIxNEMzLjI3MjA5IDcuNDcyMTQgMi43MjAwOSA3LjQ3MjE0IDIuMzYwMDkgNy44MzIxNEMyLjAwMDA5IDguMTkyMTQgMi4wMDAwOSA4Ljc0NDE0IDIuMzYwMDkgOS4xMDQxNEw1LjYyNDA5IDEyLjM2ODFDNS45ODQwOSAxMi43MjgxIDYuNTM2MDkgMTIuNzI4MSA2Ljg5NjA5IDEyLjM2ODFMMTQuNjQ4MSA0LjY0MDE0QzE1LjAwODEgNC4yODAxNCAxNS4wMDgxIDMuNzI4MTQgMTQuNjQ4MSAzLjM2ODE0QzE0LjI4ODEgMy4wMDgxNCAxMy43MzYxIDMuMDA4MTQgMTMuMzUyMSAzLjM2ODE0TDYuMjcyMDkgMTAuNDcyMUwzLjYzMjA5IDcuODMyMTRaIiBmaWxsPSIjMDA2OTVDIi8+Cjwvc3ZnPgo=')` : 'none']]]) + " " + "checkmark"
|
|
26
26
|
}), /*#__PURE__*/React.createElement("span", {
|
|
27
|
-
className: _JSXStyle.dynamic([["
|
|
27
|
+
className: _JSXStyle.dynamic([["556453451", [colors.grey900, spacers.dp8, spacers.dp12, colors.grey200, colors.teal050, colors.grey500, active ? `url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuNjMyMDkgNy44MzIxNEMzLjI3MjA5IDcuNDcyMTQgMi43MjAwOSA3LjQ3MjE0IDIuMzYwMDkgNy44MzIxNEMyLjAwMDA5IDguMTkyMTQgMi4wMDAwOSA4Ljc0NDE0IDIuMzYwMDkgOS4xMDQxNEw1LjYyNDA5IDEyLjM2ODFDNS45ODQwOSAxMi43MjgxIDYuNTM2MDkgMTIuNzI4MSA2Ljg5NjA5IDEyLjM2ODFMMTQuNjQ4MSA0LjY0MDE0QzE1LjAwODEgNC4yODAxNCAxNS4wMDgxIDMuNzI4MTQgMTQuNjQ4MSAzLjM2ODE0QzE0LjI4ODEgMy4wMDgxNCAxMy43MzYxIDMuMDA4MTQgMTMuMzUyMSAzLjM2ODE0TDYuMjcyMDkgMTAuNDcyMUwzLjYzMjA5IDcuODMyMTRaIiBmaWxsPSIjMDA2OTVDIi8+Cjwvc3ZnPgo=')` : 'none']]]) + " " + "content"
|
|
28
28
|
}, label), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
29
|
-
id: "
|
|
29
|
+
id: "556453451",
|
|
30
30
|
dynamic: [colors.grey900, spacers.dp8, spacers.dp12, colors.grey200, colors.teal050, colors.grey500, active ? `url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuNjMyMDkgNy44MzIxNEMzLjI3MjA5IDcuNDcyMTQgMi43MjAwOSA3LjQ3MjE0IDIuMzYwMDkgNy44MzIxNEMyLjAwMDA5IDguMTkyMTQgMi4wMDAwOSA4Ljc0NDE0IDIuMzYwMDkgOS4xMDQxNEw1LjYyNDA5IDEyLjM2ODFDNS45ODQwOSAxMi43MjgxIDYuNTM2MDkgMTIuNzI4MSA2Ljg5NjA5IDEyLjM2ODFMMTQuNjQ4MSA0LjY0MDE0QzE1LjAwODEgNC4yODAxNCAxNS4wMDgxIDMuNzI4MTQgMTQuNjQ4MSAzLjM2ODE0QzE0LjI4ODEgMy4wMDgxNCAxMy43MzYxIDMuMDA4MTQgMTMuMzUyMSAzLjM2ODE0TDYuMjcyMDkgMTAuNDcyMUwzLjYzMjA5IDcuODMyMTRaIiBmaWxsPSIjMDA2OTVDIi8+Cjwvc3ZnPgo=')` : 'none']
|
|
31
31
|
}, [`div.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;font-size:14px;-webkit-text-decoration:none;text-decoration:none;color:${colors.grey900};padding-block:${spacers.dp8};padding-inline:6px ${spacers.dp12};}`, `div.__jsx-style-dynamic-selector:hover{background-color:${colors.grey200};}`, `div.__jsx-style-dynamic-selector:active,div.active.__jsx-style-dynamic-selector{background-color:${colors.teal050};cursor:auto;}`, `div.disabled.__jsx-style-dynamic-selector{color:${colors.grey500};cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}`, "div.disabled.__jsx-style-dynamic-selector:hover{background-color:initial;}", `div.__jsx-style-dynamic-selector .checkmark.__jsx-style-dynamic-selector{width:16px;height:16px;margin-inline-end:6px;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;background-size:contain;background-repeat:no-repeat;background-image:${active ? `url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuNjMyMDkgNy44MzIxNEMzLjI3MjA5IDcuNDcyMTQgMi43MjAwOSA3LjQ3MjE0IDIuMzYwMDkgNy44MzIxNEMyLjAwMDA5IDguMTkyMTQgMi4wMDAwOSA4Ljc0NDE0IDIuMzYwMDkgOS4xMDQxNEw1LjYyNDA5IDEyLjM2ODFDNS45ODQwOSAxMi43MjgxIDYuNTM2MDkgMTIuNzI4MSA2Ljg5NjA5IDEyLjM2ODFMMTQuNjQ4MSA0LjY0MDE0QzE1LjAwODEgNC4yODAxNCAxNS4wMDgxIDMuNzI4MTQgMTQuNjQ4MSAzLjM2ODE0QzE0LjI4ODEgMy4wMDgxNCAxMy43MzYxIDMuMDA4MTQgMTMuMzUyMSAzLjM2ODE0TDYuMjcyMDkgMTAuNDcyMUwzLjYzMjA5IDcuODMyMTRaIiBmaWxsPSIjMDA2OTVDIi8+Cjwvc3ZnPgo=')` : 'none'};}`]));
|
|
32
32
|
SingleSelectOption.propTypes = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/select",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.19.0",
|
|
4
4
|
"description": "UI Select",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,21 +33,21 @@
|
|
|
33
33
|
"styled-jsx": "^4"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@dhis2-ui/box": "10.
|
|
37
|
-
"@dhis2-ui/button": "10.
|
|
38
|
-
"@dhis2-ui/card": "10.
|
|
39
|
-
"@dhis2-ui/checkbox": "10.
|
|
40
|
-
"@dhis2-ui/chip": "10.
|
|
41
|
-
"@dhis2-ui/field": "10.
|
|
42
|
-
"@dhis2-ui/input": "10.
|
|
43
|
-
"@dhis2-ui/layer": "10.
|
|
44
|
-
"@dhis2-ui/loader": "10.
|
|
45
|
-
"@dhis2-ui/popper": "10.
|
|
46
|
-
"@dhis2-ui/status-icon": "10.
|
|
47
|
-
"@dhis2-ui/tooltip": "10.
|
|
36
|
+
"@dhis2-ui/box": "10.19.0",
|
|
37
|
+
"@dhis2-ui/button": "10.19.0",
|
|
38
|
+
"@dhis2-ui/card": "10.19.0",
|
|
39
|
+
"@dhis2-ui/checkbox": "10.19.0",
|
|
40
|
+
"@dhis2-ui/chip": "10.19.0",
|
|
41
|
+
"@dhis2-ui/field": "10.19.0",
|
|
42
|
+
"@dhis2-ui/input": "10.19.0",
|
|
43
|
+
"@dhis2-ui/layer": "10.19.0",
|
|
44
|
+
"@dhis2-ui/loader": "10.19.0",
|
|
45
|
+
"@dhis2-ui/popper": "10.19.0",
|
|
46
|
+
"@dhis2-ui/status-icon": "10.19.0",
|
|
47
|
+
"@dhis2-ui/tooltip": "10.19.0",
|
|
48
48
|
"@dhis2/prop-types": "^3.1.2",
|
|
49
|
-
"@dhis2/ui-constants": "10.
|
|
50
|
-
"@dhis2/ui-icons": "10.
|
|
49
|
+
"@dhis2/ui-constants": "10.19.0",
|
|
50
|
+
"@dhis2/ui-icons": "10.19.0",
|
|
51
51
|
"classnames": "^2.3.1",
|
|
52
52
|
"prop-types": "^15.7.2"
|
|
53
53
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { SimpleSingleSelect } from '../simple-single-select.js'
|
|
3
|
+
|
|
4
|
+
const options = [
|
|
5
|
+
{ value: '1', label: 'option one' },
|
|
6
|
+
{ value: '2', label: 'option two' },
|
|
7
|
+
{ value: '3', label: 'A much longer option label that gets clamped' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export const WithMenuMaxWidth = () => {
|
|
11
|
+
const [selected, setSelected] = useState(null)
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ width: 120 }}>
|
|
15
|
+
<SimpleSingleSelect
|
|
16
|
+
name="simple"
|
|
17
|
+
selected={selected}
|
|
18
|
+
onChange={setSelected}
|
|
19
|
+
options={options}
|
|
20
|
+
menuMaxWidth="200px"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { SimpleSingleSelect } from '../simple-single-select.js'
|
|
3
|
+
|
|
4
|
+
const options = [
|
|
5
|
+
{ value: '1', label: 'option one' },
|
|
6
|
+
{ value: '2', label: 'option two' },
|
|
7
|
+
{ value: '3', label: 'option three' },
|
|
8
|
+
{ value: '4', label: 'A longer option that exceeds the minimum' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export const WithMenuMinWidth = () => {
|
|
12
|
+
const [selected, setSelected] = useState(null)
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div style={{ width: 120 }}>
|
|
16
|
+
<SimpleSingleSelect
|
|
17
|
+
name="simple"
|
|
18
|
+
selected={selected}
|
|
19
|
+
onChange={setSelected}
|
|
20
|
+
options={options}
|
|
21
|
+
menuMinWidth="240px"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -26,6 +26,8 @@ export function Menu({
|
|
|
26
26
|
loading,
|
|
27
27
|
loadingText,
|
|
28
28
|
maxHeight,
|
|
29
|
+
maxWidth,
|
|
30
|
+
minWidth,
|
|
29
31
|
noMatchText,
|
|
30
32
|
optionUpdateStrategy,
|
|
31
33
|
selectRef,
|
|
@@ -34,18 +36,16 @@ export function Menu({
|
|
|
34
36
|
onClose,
|
|
35
37
|
onEndReached,
|
|
36
38
|
}) {
|
|
37
|
-
const [
|
|
39
|
+
const [selectWidth, setSelectWidth] = useState()
|
|
38
40
|
const dataTestPrefix = `${dataTest}-menu`
|
|
39
41
|
|
|
42
|
+
// Re-measuring whenever `hidden` changes keeps the width current when the
|
|
43
|
+
// select's container has been resized since the menu was last opened
|
|
40
44
|
useEffect(() => {
|
|
41
45
|
if (selectRef) {
|
|
42
|
-
|
|
43
|
-
callback() // We want to know the width as soon as the
|
|
44
|
-
|
|
45
|
-
selectRef.addEventListener('resize', callback)
|
|
46
|
-
return () => selectRef.removeEventListener('resize', callback)
|
|
46
|
+
setSelectWidth(`${selectRef.offsetWidth}px`)
|
|
47
47
|
}
|
|
48
|
-
}, [selectRef])
|
|
48
|
+
}, [selectRef, hidden])
|
|
49
49
|
|
|
50
50
|
if (hidden) {
|
|
51
51
|
return null
|
|
@@ -60,6 +60,14 @@ export function Menu({
|
|
|
60
60
|
|
|
61
61
|
const isEmpty = !options.length && !filterValue
|
|
62
62
|
|
|
63
|
+
const flexible = Boolean(minWidth || maxWidth)
|
|
64
|
+
// We never want the menu narrower than the select, so a maxWidth below
|
|
65
|
+
// the select's width intentionally has no effect
|
|
66
|
+
const flexibleMinWidth =
|
|
67
|
+
minWidth && selectWidth
|
|
68
|
+
? `max(${selectWidth}, ${minWidth})`
|
|
69
|
+
: minWidth || selectWidth
|
|
70
|
+
|
|
63
71
|
return (
|
|
64
72
|
<Layer onBackdropClick={onClose} transparent>
|
|
65
73
|
<Popper
|
|
@@ -67,7 +75,15 @@ export function Menu({
|
|
|
67
75
|
placement="bottom-start"
|
|
68
76
|
observeReferenceResize
|
|
69
77
|
>
|
|
70
|
-
<div
|
|
78
|
+
<div
|
|
79
|
+
className="menu"
|
|
80
|
+
style={{
|
|
81
|
+
width: flexible ? 'fit-content' : selectWidth,
|
|
82
|
+
minWidth: flexible ? flexibleMinWidth : undefined,
|
|
83
|
+
maxWidth,
|
|
84
|
+
maxHeight,
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
71
87
|
{isEmpty && <Empty>{empty}</Empty>}
|
|
72
88
|
|
|
73
89
|
{hasNoFilterMatch && <NoMatch>{noMatchText}</NoMatch>}
|
|
@@ -160,6 +176,8 @@ Menu.propTypes = {
|
|
|
160
176
|
loading: PropTypes.bool,
|
|
161
177
|
loadingText: PropTypes.string,
|
|
162
178
|
maxHeight: PropTypes.string,
|
|
179
|
+
maxWidth: PropTypes.string,
|
|
180
|
+
minWidth: PropTypes.string,
|
|
163
181
|
noMatchText: PropTypes.string,
|
|
164
182
|
optionComponent: PropTypes.elementType,
|
|
165
183
|
optionUpdateStrategy: PropTypes.oneOf(['off', 'polite', 'assertive']),
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
useHandleKeyPressOnFilterInput,
|
|
13
13
|
} from './use-handle-key-press/index.js'
|
|
14
14
|
|
|
15
|
+
const defaultSelected = { label: '', value: '' }
|
|
16
|
+
const noop = () => undefined
|
|
17
|
+
|
|
15
18
|
function useFocussedOptionIndex({ filterable, filterValue, options }) {
|
|
16
19
|
const [defaultFocussedOptionIndex, setDefaultFocussedOptionIndex] =
|
|
17
20
|
useState(0)
|
|
@@ -75,19 +78,21 @@ export function SimpleSingleSelect({
|
|
|
75
78
|
loading = false,
|
|
76
79
|
menuLoadingText: _menuLoadingText = '',
|
|
77
80
|
menuMaxHeight = '288px',
|
|
81
|
+
menuMaxWidth,
|
|
82
|
+
menuMinWidth,
|
|
78
83
|
noMatchText: _noMatchText = '',
|
|
79
84
|
optionUpdateStrategy = 'polite',
|
|
80
85
|
placeholder = '',
|
|
81
86
|
prefix = '',
|
|
82
|
-
selected =
|
|
87
|
+
selected = defaultSelected,
|
|
83
88
|
tabIndex = '0',
|
|
84
89
|
valid = false,
|
|
85
90
|
warning = false,
|
|
86
|
-
onBlur =
|
|
87
|
-
onClear =
|
|
88
|
-
onEndReached =
|
|
89
|
-
onFilterChange =
|
|
90
|
-
onFocus =
|
|
91
|
+
onBlur = noop,
|
|
92
|
+
onClear = noop,
|
|
93
|
+
onEndReached = noop,
|
|
94
|
+
onFilterChange = noop,
|
|
95
|
+
onFocus = noop,
|
|
91
96
|
}) {
|
|
92
97
|
// We can't translate with default props as the translation function would
|
|
93
98
|
// be called before we get any translations from the consuming app/lib
|
|
@@ -261,6 +266,8 @@ export function SimpleSingleSelect({
|
|
|
261
266
|
loading={loading}
|
|
262
267
|
loadingText={menuLoadingText}
|
|
263
268
|
maxHeight={menuMaxHeight}
|
|
269
|
+
maxWidth={menuMaxWidth}
|
|
270
|
+
minWidth={menuMinWidth}
|
|
264
271
|
noMatchText={noMatchText}
|
|
265
272
|
optionUpdateStrategy={optionUpdateStrategy}
|
|
266
273
|
options={options}
|
|
@@ -342,6 +349,12 @@ SimpleSingleSelect.propTypes = {
|
|
|
342
349
|
/** Allows to modify the max height of the menu **/
|
|
343
350
|
menuMaxHeight: PropTypes.string,
|
|
344
351
|
|
|
352
|
+
/** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
|
|
353
|
+
menuMaxWidth: PropTypes.string,
|
|
354
|
+
|
|
355
|
+
/** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
|
|
356
|
+
menuMinWidth: PropTypes.string,
|
|
357
|
+
|
|
345
358
|
/** String that will be displayed when the select is being filtered but the options array is empty **/
|
|
346
359
|
noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
|
|
347
360
|
|
|
@@ -22,6 +22,8 @@ export { WithOptionsAndLoadingText } from './__stories__/WithOptionsAndLoadingTe
|
|
|
22
22
|
export { WithoutOptionsAndLoading } from './__stories__/WithoutOptionsAndLoading.js'
|
|
23
23
|
export { WithManyOptions } from './__stories__/WithManyOptions.js'
|
|
24
24
|
export { WithCustomLowMaxHeight } from './__stories__/WithCustomLowMaxHeight.js'
|
|
25
|
+
export { WithMenuMinWidth } from './__stories__/WithMenuMinWidth.js'
|
|
26
|
+
export { WithMenuMaxWidth } from './__stories__/WithMenuMaxWidth.js'
|
|
25
27
|
export { WithOptionsAndDisabled } from './__stories__/WithOptionsAndDisabled.js'
|
|
26
28
|
export { WithSelectionAndDisabled } from './__stories__/WithSelectionAndDisabled.js'
|
|
27
29
|
export { WithPrefix } from './__stories__/WithPrefix.js'
|
|
@@ -150,6 +150,74 @@ describe('<SimpleSingleSelect />', () => {
|
|
|
150
150
|
expect(menu.style.maxHeight).toBe('100px')
|
|
151
151
|
})
|
|
152
152
|
|
|
153
|
+
describe('dropdown menu width', () => {
|
|
154
|
+
let offsetWidth
|
|
155
|
+
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
// The menu's width is derived from the select's measured width,
|
|
158
|
+
// which is always 0 in jsdom
|
|
159
|
+
offsetWidth = jest
|
|
160
|
+
.spyOn(HTMLElement.prototype, 'offsetWidth', 'get')
|
|
161
|
+
.mockReturnValue(120)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
afterEach(() => {
|
|
165
|
+
offsetWidth.mockRestore()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const renderAndOpen = (props) => {
|
|
169
|
+
render(
|
|
170
|
+
<SimpleSingleSelect
|
|
171
|
+
name="simple"
|
|
172
|
+
selected={{ value: 'foo', label: 'Foo' }}
|
|
173
|
+
onChange={() => null}
|
|
174
|
+
options={[{ value: 'foo', label: 'Foo' }]}
|
|
175
|
+
{...props}
|
|
176
|
+
/>
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
fireEvent.click(screen.getByRole('combobox'))
|
|
180
|
+
|
|
181
|
+
const listbox = screen.getByRole('listbox')
|
|
182
|
+
return listbox.parentNode.parentNode.parentNode
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
it('should match the width of the select by default', () => {
|
|
186
|
+
const menu = renderAndOpen()
|
|
187
|
+
|
|
188
|
+
expect(menu.style.width).toBe('120px')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should not be narrower than menuMinWidth or the select', () => {
|
|
192
|
+
const menu = renderAndOpen({ menuMinWidth: '240px' })
|
|
193
|
+
|
|
194
|
+
expect(menu.style.minWidth).toBe('max(120px, 240px)')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should not be wider than menuMaxWidth', () => {
|
|
198
|
+
const menu = renderAndOpen({ menuMaxWidth: '200px' })
|
|
199
|
+
|
|
200
|
+
expect(menu.style.maxWidth).toBe('200px')
|
|
201
|
+
expect(menu.style.minWidth).toBe('120px')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should re-measure the select when the menu is reopened', () => {
|
|
205
|
+
renderAndOpen({ menuMinWidth: '240px' })
|
|
206
|
+
const comboBox = screen.getByRole('combobox')
|
|
207
|
+
|
|
208
|
+
// close the menu
|
|
209
|
+
fireEvent.click(comboBox)
|
|
210
|
+
|
|
211
|
+
// widen the select while the menu is closed, then reopen it
|
|
212
|
+
offsetWidth.mockReturnValue(300)
|
|
213
|
+
fireEvent.click(comboBox)
|
|
214
|
+
|
|
215
|
+
const listbox = screen.getByRole('listbox')
|
|
216
|
+
const menu = listbox.parentNode.parentNode.parentNode
|
|
217
|
+
expect(menu.style.minWidth).toBe('max(300px, 240px)')
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
153
221
|
it('should accept a placeholder', () => {
|
|
154
222
|
render(
|
|
155
223
|
<SimpleSingleSelect
|
|
@@ -137,6 +137,12 @@ SimpleSingleSelectField.propTypes = {
|
|
|
137
137
|
/** Allows to modify the max height of the menu **/
|
|
138
138
|
menuMaxHeight: PropTypes.string,
|
|
139
139
|
|
|
140
|
+
/** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
|
|
141
|
+
menuMaxWidth: PropTypes.string,
|
|
142
|
+
|
|
143
|
+
/** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
|
|
144
|
+
menuMinWidth: PropTypes.string,
|
|
145
|
+
|
|
140
146
|
/** String that will be displayed when the select is being filtered but the options array is empty **/
|
|
141
147
|
noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
|
|
142
148
|
|
|
@@ -26,6 +26,13 @@ const options = [
|
|
|
26
26
|
{ value: '10', label: 'ten' },
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
+
const longOptions = [
|
|
30
|
+
{ value: '1', label: 'option one' },
|
|
31
|
+
{ value: '2', label: 'option two' },
|
|
32
|
+
{ value: '3', label: 'option three' },
|
|
33
|
+
{ value: '4', label: 'A longer option that exceeds the minimum' },
|
|
34
|
+
]
|
|
35
|
+
|
|
29
36
|
export default {
|
|
30
37
|
title: 'SimpleSingleSelectField',
|
|
31
38
|
component: SimpleSingleSelectField,
|
|
@@ -186,3 +193,45 @@ export const InputWidth = () => {
|
|
|
186
193
|
</div>
|
|
187
194
|
)
|
|
188
195
|
}
|
|
196
|
+
|
|
197
|
+
export const WithMenuMinWidth = () => {
|
|
198
|
+
const [value, setValue] = useState('')
|
|
199
|
+
const valueLabel = value
|
|
200
|
+
? longOptions.find((option) => option.value === value)?.label
|
|
201
|
+
: ''
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div style={{ width: 120 }}>
|
|
205
|
+
<SimpleSingleSelectField
|
|
206
|
+
name="simple"
|
|
207
|
+
label="This is the label"
|
|
208
|
+
value={value}
|
|
209
|
+
valueLabel={valueLabel}
|
|
210
|
+
onChange={(nextValue) => setValue(nextValue)}
|
|
211
|
+
options={longOptions}
|
|
212
|
+
menuMinWidth="240px"
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const WithMenuMaxWidth = () => {
|
|
219
|
+
const [value, setValue] = useState('')
|
|
220
|
+
const valueLabel = value
|
|
221
|
+
? longOptions.find((option) => option.value === value)?.label
|
|
222
|
+
: ''
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div style={{ width: 120 }}>
|
|
226
|
+
<SimpleSingleSelectField
|
|
227
|
+
name="simple"
|
|
228
|
+
label="This is the label"
|
|
229
|
+
value={value}
|
|
230
|
+
valueLabel={valueLabel}
|
|
231
|
+
onChange={(nextValue) => setValue(nextValue)}
|
|
232
|
+
options={longOptions}
|
|
233
|
+
menuMaxWidth="200px"
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
@@ -52,6 +52,8 @@ describe('<SimpleSingleSelectField />', () => {
|
|
|
52
52
|
loading={false}
|
|
53
53
|
menuLoadingText=""
|
|
54
54
|
menuMaxHeight=""
|
|
55
|
+
menuMaxWidth="400px"
|
|
56
|
+
menuMinWidth="240px"
|
|
55
57
|
noMatchText=""
|
|
56
58
|
optionUpdateStrategy="off"
|
|
57
59
|
placeholder=""
|
|
@@ -91,6 +93,8 @@ describe('<SimpleSingleSelectField />', () => {
|
|
|
91
93
|
expect(SimpleSingleSelect.mock.calls[0][0].loading).toBe(false)
|
|
92
94
|
expect(SimpleSingleSelect.mock.calls[0][0].menuLoadingText).toBe('')
|
|
93
95
|
expect(SimpleSingleSelect.mock.calls[0][0].menuMaxHeight).toBe('')
|
|
96
|
+
expect(SimpleSingleSelect.mock.calls[0][0].menuMaxWidth).toBe('400px')
|
|
97
|
+
expect(SimpleSingleSelect.mock.calls[0][0].menuMinWidth).toBe('240px')
|
|
94
98
|
expect(SimpleSingleSelect.mock.calls[0][0].noMatchText).toBe('')
|
|
95
99
|
expect(SimpleSingleSelect.mock.calls[0][0].optionUpdateStrategy).toBe(
|
|
96
100
|
'off'
|
|
@@ -63,9 +63,11 @@ const SingleSelectOption = ({
|
|
|
63
63
|
flex-shrink: 0;
|
|
64
64
|
background-size: contain;
|
|
65
65
|
background-repeat: no-repeat;
|
|
66
|
-
background-image: ${
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
background-image: ${
|
|
67
|
+
active
|
|
68
|
+
? `url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuNjMyMDkgNy44MzIxNEMzLjI3MjA5IDcuNDcyMTQgMi43MjAwOSA3LjQ3MjE0IDIuMzYwMDkgNy44MzIxNEMyLjAwMDA5IDguMTkyMTQgMi4wMDAwOSA4Ljc0NDE0IDIuMzYwMDkgOS4xMDQxNEw1LjYyNDA5IDEyLjM2ODFDNS45ODQwOSAxMi43MjgxIDYuNTM2MDkgMTIuNzI4MSA2Ljg5NjA5IDEyLjM2ODFMMTQuNjQ4MSA0LjY0MDE0QzE1LjAwODEgNC4yODAxNCAxNS4wMDgxIDMuNzI4MTQgMTQuNjQ4MSAzLjM2ODE0QzE0LjI4ODEgMy4wMDgxNCAxMy43MzYxIDMuMDA4MTQgMTMuMzUyMSAzLjM2ODE0TDYuMjcyMDkgMTAuNDcyMUwzLjYzMjA5IDcuODMyMTRaIiBmaWxsPSIjMDA2OTVDIi8+Cjwvc3ZnPgo=')`
|
|
69
|
+
: 'none'
|
|
70
|
+
};
|
|
69
71
|
}
|
|
70
72
|
`}</style>
|
|
71
73
|
</div>
|