@dhis2-ui/select 10.16.5 → 10.18.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.
Files changed (62) hide show
  1. package/build/cjs/multi-select/multi-select.js +8 -0
  2. package/build/cjs/multi-select/multi-select.prod.stories.js +40 -1
  3. package/build/cjs/multi-select-field/multi-select-field.js +8 -0
  4. package/build/cjs/multi-select-field/multi-select-field.prod.stories.js +42 -2
  5. package/build/cjs/select/menu-wrapper.js +17 -6
  6. package/build/cjs/select/select.js +18 -12
  7. package/build/cjs/simple-single-select/__stories__/WithMenuMaxWidth.js +34 -0
  8. package/build/cjs/simple-single-select/__stories__/WithMenuMinWidth.js +37 -0
  9. package/build/cjs/simple-single-select/menu/menu.js +17 -8
  10. package/build/cjs/simple-single-select/simple-single-select.js +19 -9
  11. package/build/cjs/simple-single-select/simple-single-select.prod.stories.js +14 -0
  12. package/build/cjs/simple-single-select/simple-single-select.test.js +62 -0
  13. package/build/cjs/simple-single-select-field/simple-single-select-field.js +4 -0
  14. package/build/cjs/simple-single-select-field/simple-single-select-field.prod.stories.js +53 -2
  15. package/build/cjs/simple-single-select-field/simple-single-select-field.test.js +4 -0
  16. package/build/cjs/single-select/single-select.js +8 -0
  17. package/build/cjs/single-select/single-select.prod.stories.js +41 -2
  18. package/build/cjs/single-select-field/single-select-field.js +8 -0
  19. package/build/cjs/single-select-field/single-select-field.prod.stories.js +42 -2
  20. package/build/cjs/single-select-option/single-select-option.js +4 -4
  21. package/build/es/multi-select/multi-select.js +8 -0
  22. package/build/es/multi-select/multi-select.prod.stories.js +37 -0
  23. package/build/es/multi-select-field/multi-select-field.js +8 -0
  24. package/build/es/multi-select-field/multi-select-field.prod.stories.js +41 -1
  25. package/build/es/select/menu-wrapper.js +17 -6
  26. package/build/es/select/select.js +18 -12
  27. package/build/es/simple-single-select/__stories__/WithMenuMaxWidth.js +26 -0
  28. package/build/es/simple-single-select/__stories__/WithMenuMinWidth.js +29 -0
  29. package/build/es/simple-single-select/menu/menu.js +17 -8
  30. package/build/es/simple-single-select/simple-single-select.js +19 -9
  31. package/build/es/simple-single-select/simple-single-select.prod.stories.js +2 -0
  32. package/build/es/simple-single-select/simple-single-select.test.js +62 -0
  33. package/build/es/simple-single-select-field/simple-single-select-field.js +4 -0
  34. package/build/es/simple-single-select-field/simple-single-select-field.prod.stories.js +49 -0
  35. package/build/es/simple-single-select-field/simple-single-select-field.test.js +4 -0
  36. package/build/es/single-select/single-select.js +8 -0
  37. package/build/es/single-select/single-select.prod.stories.js +38 -1
  38. package/build/es/single-select-field/single-select-field.js +8 -0
  39. package/build/es/single-select-field/single-select-field.prod.stories.js +41 -1
  40. package/build/es/single-select-option/single-select-option.js +4 -4
  41. package/package.json +15 -15
  42. package/src/multi-select/multi-select.js +8 -0
  43. package/src/multi-select/multi-select.prod.stories.js +26 -0
  44. package/src/multi-select-field/multi-select-field.js +8 -0
  45. package/src/multi-select-field/multi-select-field.prod.stories.js +31 -0
  46. package/src/select/menu-wrapper.js +19 -3
  47. package/src/select/select.js +18 -12
  48. package/src/simple-single-select/__stories__/WithMenuMaxWidth.js +24 -0
  49. package/src/simple-single-select/__stories__/WithMenuMinWidth.js +25 -0
  50. package/src/simple-single-select/menu/menu.js +26 -8
  51. package/src/simple-single-select/simple-single-select.js +19 -6
  52. package/src/simple-single-select/simple-single-select.prod.stories.js +2 -0
  53. package/src/simple-single-select/simple-single-select.test.js +68 -0
  54. package/src/simple-single-select-field/simple-single-select-field.js +6 -0
  55. package/src/simple-single-select-field/simple-single-select-field.prod.stories.js +49 -0
  56. package/src/simple-single-select-field/simple-single-select-field.test.js +4 -0
  57. package/src/single-select/single-select.js +8 -0
  58. package/src/single-select/single-select.prod.stories.js +26 -0
  59. package/src/single-select-field/single-select-field.js +8 -0
  60. package/src/single-select-field/single-select-field.prod.stories.js +31 -0
  61. package/src/single-select-option/single-select-option.js +5 -3
  62. package/types/index.d.ts +45 -1
@@ -22,6 +22,8 @@ const MultiSelect = ({
22
22
  selected = staticArr,
23
23
  tabIndex,
24
24
  maxHeight,
25
+ menuMinWidth,
26
+ menuMaxWidth,
25
27
  inputMaxHeight,
26
28
  onChange,
27
29
  onFocus,
@@ -77,6 +79,8 @@ const MultiSelect = ({
77
79
  menu: menu,
78
80
  tabIndex: tabIndex,
79
81
  maxHeight: maxHeight,
82
+ menuMinWidth: menuMinWidth,
83
+ menuMaxWidth: menuMaxWidth,
80
84
  onChange: onChange,
81
85
  onFocus: onFocus,
82
86
  onKeyDown: onKeyDown,
@@ -122,6 +126,10 @@ MultiSelect.propTypes = {
122
126
  loading: _propTypes2.default.bool,
123
127
  loadingText: _propTypes2.default.string,
124
128
  maxHeight: _propTypes2.default.string,
129
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
130
+ menuMaxWidth: _propTypes2.default.string,
131
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
132
+ menuMinWidth: _propTypes2.default.string,
125
133
  /** Required if `filterable` prop is `true` */
126
134
  noMatchText: (0, _propTypes.requiredIf)(props => props.filterable, _propTypes2.default.string),
127
135
  placeholder: _propTypes2.default.string,
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports.WithPrefixAndSelection = exports.WithPrefix = exports.WithPlaceholderAndSelection = exports.WithPlaceholder = exports.WithOptionsLoadingAndLoadingText = exports.WithOptionsAndOnChange = exports.WithOptionsAndMultipleSelections = exports.WithOptionsAndLoading = exports.WithOptionsAndDisabled = exports.WithOptionsAndASelection = exports.WithOptionsASelectionAndDisabled = exports.WithOnFocus = exports.WithOnBlur = exports.WithMultipleSelectionsWrapped = exports.WithMoreThanThreeOptionsAndA100PxMaxHeight = exports.WithMoreThanTenOptions = exports.WithInvalidOptions = exports.WithInvalidFilterableOptions = exports.WithInitialFocus = exports.WithFilterField = exports.WithDisabledOptionAndOnChange = exports.WithCustomOptionsAndOnChange = exports.WithClearButtonSelectionAndOnChange = exports.ShiftedIntoView = exports.RTL = exports.FlippedPosition = exports.EmptyWithEmptyText = exports.EmptyWithEmptyComponent = exports.Empty = exports.DenseWithSelections = exports.Dense = exports.DefaultPosition = exports.CollapseSelectionAfterThreshold = void 0;
6
+ exports.default = exports.WithPrefixAndSelection = exports.WithPrefix = exports.WithPlaceholderAndSelection = exports.WithPlaceholder = exports.WithOptionsLoadingAndLoadingText = exports.WithOptionsAndOnChange = exports.WithOptionsAndMultipleSelections = exports.WithOptionsAndLoading = exports.WithOptionsAndDisabled = exports.WithOptionsAndASelection = exports.WithOptionsASelectionAndDisabled = exports.WithOnFocus = exports.WithOnBlur = exports.WithMultipleSelectionsWrapped = exports.WithMoreThanThreeOptionsAndA100PxMaxHeight = exports.WithMoreThanTenOptions = exports.WithMenuMinWidth = exports.WithMenuMaxWidth = exports.WithInvalidOptions = exports.WithInvalidFilterableOptions = exports.WithInitialFocus = exports.WithFilterField = exports.WithDisabledOptionAndOnChange = exports.WithCustomOptionsAndOnChange = exports.WithClearButtonSelectionAndOnChange = exports.ShiftedIntoView = exports.RTL = exports.FlippedPosition = exports.EmptyWithEmptyText = exports.EmptyWithEmptyComponent = exports.Empty = exports.DenseWithSelections = exports.Dense = exports.DefaultPosition = exports.CollapseSelectionAfterThreshold = void 0;
7
7
  var _style = _interopRequireDefault(require("styled-jsx/style"));
8
8
  var _uiConstants = require("@dhis2/ui-constants");
9
9
  var _propTypes = _interopRequireDefault(require("prop-types"));
@@ -561,4 +561,43 @@ exports.RTL = RTL;
561
561
  RTL.args = {
562
562
  selected: ['1', '2'],
563
563
  prefix: 'RTL text'
564
+ };
565
+ const WithMenuMaxWidth = args => /*#__PURE__*/_react.default.createElement("div", {
566
+ style: {
567
+ width: 120
568
+ }
569
+ }, /*#__PURE__*/_react.default.createElement(_index2.MultiSelect, args, /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
570
+ value: "1",
571
+ label: "option one"
572
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
573
+ value: "2",
574
+ label: "option two"
575
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
576
+ value: "3",
577
+ label: "A much longer option label"
578
+ })));
579
+ exports.WithMenuMaxWidth = WithMenuMaxWidth;
580
+ WithMenuMaxWidth.args = {
581
+ menuMaxWidth: '200px'
582
+ };
583
+ const WithMenuMinWidth = args => /*#__PURE__*/_react.default.createElement("div", {
584
+ style: {
585
+ width: 120
586
+ }
587
+ }, /*#__PURE__*/_react.default.createElement(_index2.MultiSelect, args, /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
588
+ value: "1",
589
+ label: "option one"
590
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
591
+ value: "2",
592
+ label: "option two"
593
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
594
+ value: "3",
595
+ label: "option three"
596
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
597
+ value: "4",
598
+ label: "A longer option that exceeds the minimum"
599
+ })));
600
+ exports.WithMenuMinWidth = WithMenuMinWidth;
601
+ WithMenuMinWidth.args = {
602
+ menuMinWidth: '240px'
564
603
  };
@@ -42,6 +42,8 @@ class MultiSelectField extends _react.default.Component {
42
42
  helpText,
43
43
  validationText,
44
44
  maxHeight,
45
+ menuMinWidth,
46
+ menuMaxWidth,
45
47
  inputMaxHeight,
46
48
  inputWidth,
47
49
  children,
@@ -76,6 +78,8 @@ class MultiSelectField extends _react.default.Component {
76
78
  selected: selected,
77
79
  tabIndex: tabIndex,
78
80
  maxHeight: maxHeight,
81
+ menuMinWidth: menuMinWidth,
82
+ menuMaxWidth: menuMaxWidth,
79
83
  inputMaxHeight: inputMaxHeight,
80
84
  onChange: onChange,
81
85
  onFocus: onFocus,
@@ -147,6 +151,10 @@ MultiSelectField.propTypes = {
147
151
  loadingText: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func]),
148
152
  /** Constrains height of the MultiSelect */
149
153
  maxHeight: _propTypes.default.string,
154
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
155
+ menuMaxWidth: _propTypes.default.string,
156
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
157
+ menuMinWidth: _propTypes.default.string,
150
158
  /** Text to display when there are no filter results */
151
159
  noMatchText: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func]),
152
160
  /** Placeholder text when the MultiSelect is empty */
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports.WithHelpText = exports.StatusWarning = exports.StatusValid = exports.StatusError = exports.Required = exports.InputWidth = exports.DefaultLoadingText = exports.DefaultFilterPlaceholderAndNoMatchText = exports.DefaultEmpty = exports.DefaultClearText = exports.Default = void 0;
6
+ exports.default = exports.WithMenuMinWidth = exports.WithMenuMaxWidth = exports.WithHelpText = exports.StatusWarning = exports.StatusValid = exports.StatusError = exports.Required = exports.InputWidth = exports.DefaultLoadingText = exports.DefaultFilterPlaceholderAndNoMatchText = exports.DefaultEmpty = exports.DefaultClearText = exports.Default = void 0;
7
7
  var _uiConstants = require("@dhis2/ui-constants");
8
8
  var _react = _interopRequireDefault(require("react"));
9
9
  var _index = require("../index.js");
@@ -174,4 +174,44 @@ DefaultLoadingText.args = {
174
174
  loading: true,
175
175
  ...DefaultEmpty.args
176
176
  };
177
- DefaultLoadingText.storyName = 'Default: loadingText';
177
+ DefaultLoadingText.storyName = 'Default: loadingText';
178
+ const WithMenuMaxWidth = exports.WithMenuMaxWidth = Template.bind({});
179
+ WithMenuMaxWidth.args = {
180
+ inputWidth: '120px',
181
+ menuMaxWidth: '200px',
182
+ children: [/*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
183
+ key: "1",
184
+ value: "1",
185
+ label: "option one"
186
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
187
+ key: "2",
188
+ value: "2",
189
+ label: "option two"
190
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
191
+ key: "3",
192
+ value: "3",
193
+ label: "A much longer option label"
194
+ })]
195
+ };
196
+ const WithMenuMinWidth = exports.WithMenuMinWidth = Template.bind({});
197
+ WithMenuMinWidth.args = {
198
+ inputWidth: '120px',
199
+ menuMinWidth: '240px',
200
+ children: [/*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
201
+ key: "1",
202
+ value: "1",
203
+ label: "option one"
204
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
205
+ key: "2",
206
+ value: "2",
207
+ label: "option two"
208
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
209
+ key: "3",
210
+ value: "3",
211
+ label: "option three"
212
+ }), /*#__PURE__*/_react.default.createElement(_index.MultiSelectOption, {
213
+ key: "4",
214
+ value: "4",
215
+ label: "A longer option that exceeds the minimum"
216
+ })]
217
+ };
@@ -14,11 +14,20 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
14
14
  const MenuWrapper = ({
15
15
  children,
16
16
  dataTest,
17
+ inputWidth,
17
18
  maxHeight = '280px',
18
- menuWidth,
19
+ menuMaxWidth,
20
+ menuMinWidth,
19
21
  onClick,
20
22
  selectRef
21
23
  }) => {
24
+ // menuMinWidth or menuMaxWidth enables flexible sizing (fit-content), with
25
+ // min-width = max(input, menuMinWidth). Without them, width matches the input.
26
+ const flexible = menuMinWidth || menuMaxWidth;
27
+ const width = flexible ? 'fit-content' : inputWidth;
28
+ const flexibleMinWidth = menuMinWidth ? `max(${inputWidth}, ${menuMinWidth})` : inputWidth;
29
+ const minWidth = flexible ? flexibleMinWidth : 'auto';
30
+ const maxWidth = menuMaxWidth || 'none';
22
31
  return /*#__PURE__*/_react.default.createElement(_layer.Layer, {
23
32
  onBackdropClick: onClick,
24
33
  transparent: true
@@ -28,18 +37,20 @@ const MenuWrapper = ({
28
37
  observeReferenceResize: true
29
38
  }, /*#__PURE__*/_react.default.createElement("div", {
30
39
  "data-test": `${dataTest}-menuwrapper`,
31
- className: _style.default.dynamic([["1871652546", [menuWidth, maxHeight, _uiConstants.colors.white, _uiConstants.colors.grey200, _uiConstants.elevations.popover]]])
40
+ className: _style.default.dynamic([["3752211706", [width, minWidth, maxWidth, maxHeight, _uiConstants.colors.white, _uiConstants.colors.grey200, _uiConstants.elevations.popover]]])
32
41
  }, children, /*#__PURE__*/_react.default.createElement(_style.default, {
33
- id: "1871652546",
34
- dynamic: [menuWidth, maxHeight, _uiConstants.colors.white, _uiConstants.colors.grey200, _uiConstants.elevations.popover]
35
- }, [`div.__jsx-style-dynamic-selector{width:${menuWidth};height:auto;max-height:${maxHeight};overflow:auto;background:${_uiConstants.colors.white};border:1px solid ${_uiConstants.colors.grey200};border-radius:3px;box-shadow:${_uiConstants.elevations.popover};}`]))));
42
+ id: "3752211706",
43
+ dynamic: [width, minWidth, maxWidth, maxHeight, _uiConstants.colors.white, _uiConstants.colors.grey200, _uiConstants.elevations.popover]
44
+ }, [`div.__jsx-style-dynamic-selector{width:${width};min-width:${minWidth};max-width:${maxWidth};height:auto;max-height:${maxHeight};overflow:auto;background:${_uiConstants.colors.white};border:1px solid ${_uiConstants.colors.grey200};border-radius:3px;box-shadow:${_uiConstants.elevations.popover};}`]))));
36
45
  };
37
46
  exports.MenuWrapper = MenuWrapper;
38
47
  MenuWrapper.propTypes = {
39
48
  dataTest: _propTypes.default.string.isRequired,
40
- menuWidth: _propTypes.default.string.isRequired,
49
+ inputWidth: _propTypes.default.string.isRequired,
41
50
  selectRef: _propTypes.default.object.isRequired,
42
51
  children: _propTypes.default.node,
43
52
  maxHeight: _propTypes.default.string,
53
+ menuMaxWidth: _propTypes.default.string,
54
+ menuMinWidth: _propTypes.default.string,
44
55
  onClick: _propTypes.default.func
45
56
  };
@@ -26,7 +26,7 @@ class Select extends _react.Component {
26
26
  super(...args);
27
27
  _defineProperty(this, "state", {
28
28
  open: false,
29
- menuWidth: 'auto'
29
+ inputWidth: 'auto'
30
30
  });
31
31
  _defineProperty(this, "selectRef", /*#__PURE__*/_react.default.createRef());
32
32
  _defineProperty(this, "inputRef", /*#__PURE__*/_react.default.createRef());
@@ -40,10 +40,10 @@ class Select extends _react.Component {
40
40
  * See: https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web
41
41
  */
42
42
  _defineProperty(this, "onResize", (0, _debounce.debounce)(() => {
43
- const menuWidth = this.getMenuWidth();
44
- if (this.state.menuWidth !== menuWidth) {
43
+ const inputWidth = this.getInputWidth();
44
+ if (this.state.inputWidth !== inputWidth) {
45
45
  this.setState({
46
- menuWidth
46
+ inputWidth
47
47
  });
48
48
  }
49
49
  }, 50));
@@ -66,7 +66,7 @@ class Select extends _react.Component {
66
66
  _defineProperty(this, "handleOpen", () => {
67
67
  this.setState({
68
68
  open: true,
69
- menuWidth: this.getMenuWidth()
69
+ inputWidth: this.getInputWidth()
70
70
  });
71
71
  });
72
72
  _defineProperty(this, "handleClose", () => {
@@ -145,29 +145,29 @@ class Select extends _react.Component {
145
145
  this.inputRef.current.focus();
146
146
  }
147
147
  this.setState({
148
- menuWidth: this.getMenuWidth()
148
+ inputWidth: this.getInputWidth()
149
149
  });
150
150
  window.addEventListener('resize', this.onResize);
151
151
  }
152
152
  componentWillUnmount() {
153
153
  window.removeEventListener('resize', this.onResize);
154
154
  }
155
- getMenuWidth() {
155
+ getInputWidth() {
156
156
  const {
157
157
  offsetWidth
158
158
  } = this.inputRef.current;
159
159
  const {
160
- menuWidth
160
+ inputWidth
161
161
  } = this.state;
162
- if (offsetWidth && `${offsetWidth}px` !== menuWidth) {
162
+ if (offsetWidth && `${offsetWidth}px` !== inputWidth) {
163
163
  return `${offsetWidth}px`;
164
164
  }
165
- return menuWidth;
165
+ return inputWidth;
166
166
  }
167
167
  render() {
168
168
  const {
169
169
  open,
170
- menuWidth
170
+ inputWidth
171
171
  } = this.state;
172
172
  const {
173
173
  children,
@@ -176,6 +176,8 @@ class Select extends _react.Component {
176
176
  onChange,
177
177
  tabIndex,
178
178
  maxHeight,
179
+ menuMinWidth,
180
+ menuMaxWidth,
179
181
  error,
180
182
  warning,
181
183
  valid,
@@ -222,7 +224,9 @@ class Select extends _react.Component {
222
224
  onClick: this.onOutsideClick,
223
225
  maxHeight: maxHeight,
224
226
  selectRef: this.selectRef,
225
- menuWidth: menuWidth,
227
+ inputWidth: inputWidth,
228
+ menuMinWidth: menuMinWidth,
229
+ menuMaxWidth: menuMaxWidth,
226
230
  dataTest: `${dataTest}-menu`
227
231
  }, menu));
228
232
  }
@@ -243,6 +247,8 @@ Select.propTypes = {
243
247
  error: _uiConstants.sharedPropTypes.statusPropType,
244
248
  initialFocus: _propTypes.default.bool,
245
249
  maxHeight: _propTypes.default.string,
250
+ menuMaxWidth: _propTypes.default.string,
251
+ menuMinWidth: _propTypes.default.string,
246
252
  tabIndex: _propTypes.default.string,
247
253
  valid: _uiConstants.sharedPropTypes.statusPropType,
248
254
  warning: _uiConstants.sharedPropTypes.statusPropType,
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.WithMenuMaxWidth = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _simpleSingleSelect = require("../simple-single-select.js");
9
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
10
+ const options = [{
11
+ value: '1',
12
+ label: 'option one'
13
+ }, {
14
+ value: '2',
15
+ label: 'option two'
16
+ }, {
17
+ value: '3',
18
+ label: 'A much longer option label that gets clamped'
19
+ }];
20
+ const WithMenuMaxWidth = () => {
21
+ const [selected, setSelected] = (0, _react.useState)(null);
22
+ return /*#__PURE__*/_react.default.createElement("div", {
23
+ style: {
24
+ width: 120
25
+ }
26
+ }, /*#__PURE__*/_react.default.createElement(_simpleSingleSelect.SimpleSingleSelect, {
27
+ name: "simple",
28
+ selected: selected,
29
+ onChange: setSelected,
30
+ options: options,
31
+ menuMaxWidth: "200px"
32
+ }));
33
+ };
34
+ exports.WithMenuMaxWidth = WithMenuMaxWidth;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.WithMenuMinWidth = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _simpleSingleSelect = require("../simple-single-select.js");
9
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
10
+ const options = [{
11
+ value: '1',
12
+ label: 'option one'
13
+ }, {
14
+ value: '2',
15
+ label: 'option two'
16
+ }, {
17
+ value: '3',
18
+ label: 'option three'
19
+ }, {
20
+ value: '4',
21
+ label: 'A longer option that exceeds the minimum'
22
+ }];
23
+ const WithMenuMinWidth = () => {
24
+ const [selected, setSelected] = (0, _react.useState)(null);
25
+ return /*#__PURE__*/_react.default.createElement("div", {
26
+ style: {
27
+ width: 120
28
+ }
29
+ }, /*#__PURE__*/_react.default.createElement(_simpleSingleSelect.SimpleSingleSelect, {
30
+ name: "simple",
31
+ selected: selected,
32
+ onChange: setSelected,
33
+ options: options,
34
+ menuMinWidth: "240px"
35
+ }));
36
+ };
37
+ exports.WithMenuMinWidth = WithMenuMinWidth;
@@ -34,6 +34,8 @@ function Menu({
34
34
  loading,
35
35
  loadingText,
36
36
  maxHeight,
37
+ maxWidth,
38
+ minWidth,
37
39
  noMatchText,
38
40
  optionUpdateStrategy,
39
41
  selectRef,
@@ -42,17 +44,16 @@ function Menu({
42
44
  onClose,
43
45
  onEndReached
44
46
  }) {
45
- const [menuWidth, setWidth] = (0, _react.useState)('auto');
47
+ const [selectWidth, setSelectWidth] = (0, _react.useState)();
46
48
  const dataTestPrefix = `${dataTest}-menu`;
49
+
50
+ // Re-measuring whenever `hidden` changes keeps the width current when the
51
+ // select's container has been resized since the menu was last opened
47
52
  (0, _react.useEffect)(() => {
48
53
  if (selectRef) {
49
- const callback = () => setWidth(`${selectRef.offsetWidth}px`);
50
- callback(); // We want to know the width as soon as the
51
-
52
- selectRef.addEventListener('resize', callback);
53
- return () => selectRef.removeEventListener('resize', callback);
54
+ setSelectWidth(`${selectRef.offsetWidth}px`);
54
55
  }
55
- }, [selectRef]);
56
+ }, [selectRef, hidden]);
56
57
  if (hidden) {
57
58
  return null;
58
59
  }
@@ -61,6 +62,10 @@ function Menu({
61
62
  // we're in the process of loading options
62
63
  !loading;
63
64
  const isEmpty = !options.length && !filterValue;
65
+ const flexible = Boolean(minWidth || maxWidth);
66
+ // We never want the menu narrower than the select, so a maxWidth below
67
+ // the select's width intentionally has no effect
68
+ const flexibleMinWidth = minWidth && selectWidth ? `max(${selectWidth}, ${minWidth})` : minWidth || selectWidth;
64
69
  return /*#__PURE__*/_react.default.createElement(_layer.Layer, {
65
70
  onBackdropClick: onClose,
66
71
  transparent: true
@@ -70,7 +75,9 @@ function Menu({
70
75
  observeReferenceResize: true
71
76
  }, /*#__PURE__*/_react.default.createElement("div", {
72
77
  style: {
73
- width: menuWidth,
78
+ width: flexible ? 'fit-content' : selectWidth,
79
+ minWidth: flexible ? flexibleMinWidth : undefined,
80
+ maxWidth,
74
81
  maxHeight
75
82
  },
76
83
  className: _style.default.dynamic([["2949029222", [_uiConstants.colors.white, _uiConstants.colors.grey200, _uiConstants.elevations.e300]]]) + " " + "menu"
@@ -121,6 +128,8 @@ Menu.propTypes = {
121
128
  loading: _propTypes.default.bool,
122
129
  loadingText: _propTypes.default.string,
123
130
  maxHeight: _propTypes.default.string,
131
+ maxWidth: _propTypes.default.string,
132
+ minWidth: _propTypes.default.string,
124
133
  noMatchText: _propTypes.default.string,
125
134
  optionComponent: _propTypes.default.elementType,
126
135
  optionUpdateStrategy: _propTypes.default.oneOf(['off', 'polite', 'assertive']),
@@ -16,6 +16,11 @@ var _sharedPropTypes = require("./shared-prop-types.js");
16
16
  var _index4 = require("./use-handle-key-press/index.js");
17
17
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
18
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
+ const defaultSelected = {
20
+ label: '',
21
+ value: ''
22
+ };
23
+ const noop = () => undefined;
19
24
  function useFocussedOptionIndex({
20
25
  filterable,
21
26
  filterValue,
@@ -73,22 +78,21 @@ function SimpleSingleSelect({
73
78
  loading = false,
74
79
  menuLoadingText: _menuLoadingText = '',
75
80
  menuMaxHeight = '288px',
81
+ menuMaxWidth,
82
+ menuMinWidth,
76
83
  noMatchText: _noMatchText = '',
77
84
  optionUpdateStrategy = 'polite',
78
85
  placeholder = '',
79
86
  prefix = '',
80
- selected = {
81
- label: '',
82
- value: ''
83
- },
87
+ selected = defaultSelected,
84
88
  tabIndex = '0',
85
89
  valid = false,
86
90
  warning = false,
87
- onBlur = () => undefined,
88
- onClear = () => undefined,
89
- onEndReached = () => undefined,
90
- onFilterChange = () => undefined,
91
- onFocus = () => undefined
91
+ onBlur = noop,
92
+ onClear = noop,
93
+ onEndReached = noop,
94
+ onFilterChange = noop,
95
+ onFocus = noop
92
96
  }) {
93
97
  // We can't translate with default props as the translation function would
94
98
  // be called before we get any translations from the consuming app/lib
@@ -238,6 +242,8 @@ function SimpleSingleSelect({
238
242
  loading: loading,
239
243
  loadingText: menuLoadingText,
240
244
  maxHeight: menuMaxHeight,
245
+ maxWidth: menuMaxWidth,
246
+ minWidth: menuMinWidth,
241
247
  noMatchText: noMatchText,
242
248
  optionUpdateStrategy: optionUpdateStrategy,
243
249
  options: options,
@@ -295,6 +301,10 @@ SimpleSingleSelect.propTypes = {
295
301
  menuLoadingText: _propTypes2.default.string,
296
302
  /** Allows to modify the max height of the menu **/
297
303
  menuMaxHeight: _propTypes2.default.string,
304
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
305
+ menuMaxWidth: _propTypes2.default.string,
306
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
307
+ menuMinWidth: _propTypes2.default.string,
298
308
  /** String that will be displayed when the select is being filtered but the options array is empty **/
299
309
  noMatchText: (0, _propTypes.requiredIf)(props => props.filterable, _propTypes2.default.string),
300
310
  /** Allows to override what's rendered inside the `button[role="option"]`.
@@ -99,6 +99,18 @@ Object.defineProperty(exports, "WithManyOptions", {
99
99
  return _WithManyOptions.WithManyOptions;
100
100
  }
101
101
  });
102
+ Object.defineProperty(exports, "WithMenuMaxWidth", {
103
+ enumerable: true,
104
+ get: function () {
105
+ return _WithMenuMaxWidth.WithMenuMaxWidth;
106
+ }
107
+ });
108
+ Object.defineProperty(exports, "WithMenuMinWidth", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _WithMenuMinWidth.WithMenuMinWidth;
112
+ }
113
+ });
102
114
  Object.defineProperty(exports, "WithMultipleSelectsAndScrollableParent", {
103
115
  enumerable: true,
104
116
  get: function () {
@@ -226,6 +238,8 @@ var _WithOptionsAndLoadingText = require("./__stories__/WithOptionsAndLoadingTex
226
238
  var _WithoutOptionsAndLoading = require("./__stories__/WithoutOptionsAndLoading.js");
227
239
  var _WithManyOptions = require("./__stories__/WithManyOptions.js");
228
240
  var _WithCustomLowMaxHeight = require("./__stories__/WithCustomLowMaxHeight.js");
241
+ var _WithMenuMinWidth = require("./__stories__/WithMenuMinWidth.js");
242
+ var _WithMenuMaxWidth = require("./__stories__/WithMenuMaxWidth.js");
229
243
  var _WithOptionsAndDisabled = require("./__stories__/WithOptionsAndDisabled.js");
230
244
  var _WithSelectionAndDisabled = require("./__stories__/WithSelectionAndDisabled.js");
231
245
  var _WithPrefix = require("./__stories__/WithPrefix.js");
@@ -5,6 +5,7 @@ var _react = require("@testing-library/react");
5
5
  var _react2 = _interopRequireWildcard(require("react"));
6
6
  var _simpleSingleSelect = require("./simple-single-select.js");
7
7
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
8
+ 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); }
8
9
  describe('<SimpleSingleSelect />', () => {
9
10
  beforeAll(() => {
10
11
  const consoleError = console.error;
@@ -145,6 +146,67 @@ describe('<SimpleSingleSelect />', () => {
145
146
  const menu = listbox.parentNode.parentNode.parentNode;
146
147
  expect(menu.style.maxHeight).toBe('100px');
147
148
  });
149
+ describe('dropdown menu width', () => {
150
+ let offsetWidth;
151
+ beforeEach(() => {
152
+ // The menu's width is derived from the select's measured width,
153
+ // which is always 0 in jsdom
154
+ offsetWidth = jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(120);
155
+ });
156
+ afterEach(() => {
157
+ offsetWidth.mockRestore();
158
+ });
159
+ const renderAndOpen = props => {
160
+ (0, _react.render)(/*#__PURE__*/_react2.default.createElement(_simpleSingleSelect.SimpleSingleSelect, _extends({
161
+ name: "simple",
162
+ selected: {
163
+ value: 'foo',
164
+ label: 'Foo'
165
+ },
166
+ onChange: () => null,
167
+ options: [{
168
+ value: 'foo',
169
+ label: 'Foo'
170
+ }]
171
+ }, props)));
172
+ _react.fireEvent.click(_react.screen.getByRole('combobox'));
173
+ const listbox = _react.screen.getByRole('listbox');
174
+ return listbox.parentNode.parentNode.parentNode;
175
+ };
176
+ it('should match the width of the select by default', () => {
177
+ const menu = renderAndOpen();
178
+ expect(menu.style.width).toBe('120px');
179
+ });
180
+ it('should not be narrower than menuMinWidth or the select', () => {
181
+ const menu = renderAndOpen({
182
+ menuMinWidth: '240px'
183
+ });
184
+ expect(menu.style.minWidth).toBe('max(120px, 240px)');
185
+ });
186
+ it('should not be wider than menuMaxWidth', () => {
187
+ const menu = renderAndOpen({
188
+ menuMaxWidth: '200px'
189
+ });
190
+ expect(menu.style.maxWidth).toBe('200px');
191
+ expect(menu.style.minWidth).toBe('120px');
192
+ });
193
+ it('should re-measure the select when the menu is reopened', () => {
194
+ renderAndOpen({
195
+ menuMinWidth: '240px'
196
+ });
197
+ const comboBox = _react.screen.getByRole('combobox');
198
+
199
+ // close the menu
200
+ _react.fireEvent.click(comboBox);
201
+
202
+ // widen the select while the menu is closed, then reopen it
203
+ offsetWidth.mockReturnValue(300);
204
+ _react.fireEvent.click(comboBox);
205
+ const listbox = _react.screen.getByRole('listbox');
206
+ const menu = listbox.parentNode.parentNode.parentNode;
207
+ expect(menu.style.minWidth).toBe('max(300px, 240px)');
208
+ });
209
+ });
148
210
  it('should accept a placeholder', () => {
149
211
  (0, _react.render)(/*#__PURE__*/_react2.default.createElement(_simpleSingleSelect.SimpleSingleSelect, {
150
212
  name: "simple",
@@ -108,6 +108,10 @@ SimpleSingleSelectField.propTypes = {
108
108
  menuLoadingText: _propTypes2.default.string,
109
109
  /** Allows to modify the max height of the menu **/
110
110
  menuMaxHeight: _propTypes2.default.string,
111
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
112
+ menuMaxWidth: _propTypes2.default.string,
113
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) **/
114
+ menuMinWidth: _propTypes2.default.string,
111
115
  /** String that will be displayed when the select is being filtered but the options array is empty **/
112
116
  noMatchText: (0, _propTypes.requiredIf)(props => props.filterable, _propTypes2.default.string),
113
117
  /** Allows to override what's rendered inside the `button[role="option"]`.