@dhis2-ui/select 10.16.5 → 10.17.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 (31) 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/single-select/single-select.js +8 -0
  8. package/build/cjs/single-select/single-select.prod.stories.js +41 -2
  9. package/build/cjs/single-select-field/single-select-field.js +8 -0
  10. package/build/cjs/single-select-field/single-select-field.prod.stories.js +42 -2
  11. package/build/es/multi-select/multi-select.js +8 -0
  12. package/build/es/multi-select/multi-select.prod.stories.js +37 -0
  13. package/build/es/multi-select-field/multi-select-field.js +8 -0
  14. package/build/es/multi-select-field/multi-select-field.prod.stories.js +41 -1
  15. package/build/es/select/menu-wrapper.js +17 -6
  16. package/build/es/select/select.js +18 -12
  17. package/build/es/single-select/single-select.js +8 -0
  18. package/build/es/single-select/single-select.prod.stories.js +38 -1
  19. package/build/es/single-select-field/single-select-field.js +8 -0
  20. package/build/es/single-select-field/single-select-field.prod.stories.js +41 -1
  21. package/package.json +15 -15
  22. package/src/multi-select/multi-select.js +8 -0
  23. package/src/multi-select/multi-select.prod.stories.js +26 -0
  24. package/src/multi-select-field/multi-select-field.js +8 -0
  25. package/src/multi-select-field/multi-select-field.prod.stories.js +31 -0
  26. package/src/select/menu-wrapper.js +19 -3
  27. package/src/select/select.js +18 -12
  28. package/src/single-select/single-select.js +8 -0
  29. package/src/single-select/single-select.prod.stories.js +26 -0
  30. package/src/single-select-field/single-select-field.js +8 -0
  31. package/src/single-select-field/single-select-field.prod.stories.js +31 -0
@@ -7,11 +7,20 @@ import React from 'react';
7
7
  const MenuWrapper = ({
8
8
  children,
9
9
  dataTest,
10
+ inputWidth,
10
11
  maxHeight = '280px',
11
- menuWidth,
12
+ menuMaxWidth,
13
+ menuMinWidth,
12
14
  onClick,
13
15
  selectRef
14
16
  }) => {
17
+ // menuMinWidth or menuMaxWidth enables flexible sizing (fit-content), with
18
+ // min-width = max(input, menuMinWidth). Without them, width matches the input.
19
+ const flexible = menuMinWidth || menuMaxWidth;
20
+ const width = flexible ? 'fit-content' : inputWidth;
21
+ const flexibleMinWidth = menuMinWidth ? `max(${inputWidth}, ${menuMinWidth})` : inputWidth;
22
+ const minWidth = flexible ? flexibleMinWidth : 'auto';
23
+ const maxWidth = menuMaxWidth || 'none';
15
24
  return /*#__PURE__*/React.createElement(Layer, {
16
25
  onBackdropClick: onClick,
17
26
  transparent: true
@@ -21,18 +30,20 @@ const MenuWrapper = ({
21
30
  observeReferenceResize: true
22
31
  }, /*#__PURE__*/React.createElement("div", {
23
32
  "data-test": `${dataTest}-menuwrapper`,
24
- className: _JSXStyle.dynamic([["1871652546", [menuWidth, maxHeight, colors.white, colors.grey200, elevations.popover]]])
33
+ className: _JSXStyle.dynamic([["3752211706", [width, minWidth, maxWidth, maxHeight, colors.white, colors.grey200, elevations.popover]]])
25
34
  }, children, /*#__PURE__*/React.createElement(_JSXStyle, {
26
- id: "1871652546",
27
- dynamic: [menuWidth, maxHeight, colors.white, colors.grey200, elevations.popover]
28
- }, [`div.__jsx-style-dynamic-selector{width:${menuWidth};height:auto;max-height:${maxHeight};overflow:auto;background:${colors.white};border:1px solid ${colors.grey200};border-radius:3px;box-shadow:${elevations.popover};}`]))));
35
+ id: "3752211706",
36
+ dynamic: [width, minWidth, maxWidth, maxHeight, colors.white, colors.grey200, elevations.popover]
37
+ }, [`div.__jsx-style-dynamic-selector{width:${width};min-width:${minWidth};max-width:${maxWidth};height:auto;max-height:${maxHeight};overflow:auto;background:${colors.white};border:1px solid ${colors.grey200};border-radius:3px;box-shadow:${elevations.popover};}`]))));
29
38
  };
30
39
  MenuWrapper.propTypes = {
31
40
  dataTest: PropTypes.string.isRequired,
32
- menuWidth: PropTypes.string.isRequired,
41
+ inputWidth: PropTypes.string.isRequired,
33
42
  selectRef: PropTypes.object.isRequired,
34
43
  children: PropTypes.node,
35
44
  maxHeight: PropTypes.string,
45
+ menuMaxWidth: PropTypes.string,
46
+ menuMinWidth: PropTypes.string,
36
47
  onClick: PropTypes.func
37
48
  };
38
49
  export { MenuWrapper };
@@ -19,7 +19,7 @@ export class Select extends Component {
19
19
  super(...args);
20
20
  _defineProperty(this, "state", {
21
21
  open: false,
22
- menuWidth: 'auto'
22
+ inputWidth: 'auto'
23
23
  });
24
24
  _defineProperty(this, "selectRef", /*#__PURE__*/React.createRef());
25
25
  _defineProperty(this, "inputRef", /*#__PURE__*/React.createRef());
@@ -33,10 +33,10 @@ export class Select extends Component {
33
33
  * See: https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web
34
34
  */
35
35
  _defineProperty(this, "onResize", debounce(() => {
36
- const menuWidth = this.getMenuWidth();
37
- if (this.state.menuWidth !== menuWidth) {
36
+ const inputWidth = this.getInputWidth();
37
+ if (this.state.inputWidth !== inputWidth) {
38
38
  this.setState({
39
- menuWidth
39
+ inputWidth
40
40
  });
41
41
  }
42
42
  }, 50));
@@ -59,7 +59,7 @@ export class Select extends Component {
59
59
  _defineProperty(this, "handleOpen", () => {
60
60
  this.setState({
61
61
  open: true,
62
- menuWidth: this.getMenuWidth()
62
+ inputWidth: this.getInputWidth()
63
63
  });
64
64
  });
65
65
  _defineProperty(this, "handleClose", () => {
@@ -138,29 +138,29 @@ export class Select extends Component {
138
138
  this.inputRef.current.focus();
139
139
  }
140
140
  this.setState({
141
- menuWidth: this.getMenuWidth()
141
+ inputWidth: this.getInputWidth()
142
142
  });
143
143
  window.addEventListener('resize', this.onResize);
144
144
  }
145
145
  componentWillUnmount() {
146
146
  window.removeEventListener('resize', this.onResize);
147
147
  }
148
- getMenuWidth() {
148
+ getInputWidth() {
149
149
  const {
150
150
  offsetWidth
151
151
  } = this.inputRef.current;
152
152
  const {
153
- menuWidth
153
+ inputWidth
154
154
  } = this.state;
155
- if (offsetWidth && `${offsetWidth}px` !== menuWidth) {
155
+ if (offsetWidth && `${offsetWidth}px` !== inputWidth) {
156
156
  return `${offsetWidth}px`;
157
157
  }
158
- return menuWidth;
158
+ return inputWidth;
159
159
  }
160
160
  render() {
161
161
  const {
162
162
  open,
163
- menuWidth
163
+ inputWidth
164
164
  } = this.state;
165
165
  const {
166
166
  children,
@@ -169,6 +169,8 @@ export class Select extends Component {
169
169
  onChange,
170
170
  tabIndex,
171
171
  maxHeight,
172
+ menuMinWidth,
173
+ menuMaxWidth,
172
174
  error,
173
175
  warning,
174
176
  valid,
@@ -215,7 +217,9 @@ export class Select extends Component {
215
217
  onClick: this.onOutsideClick,
216
218
  maxHeight: maxHeight,
217
219
  selectRef: this.selectRef,
218
- menuWidth: menuWidth,
220
+ inputWidth: inputWidth,
221
+ menuMinWidth: menuMinWidth,
222
+ menuMaxWidth: menuMaxWidth,
219
223
  dataTest: `${dataTest}-menu`
220
224
  }, menu));
221
225
  }
@@ -235,6 +239,8 @@ Select.propTypes = {
235
239
  error: sharedPropTypes.statusPropType,
236
240
  initialFocus: PropTypes.bool,
237
241
  maxHeight: PropTypes.string,
242
+ menuMaxWidth: PropTypes.string,
243
+ menuMinWidth: PropTypes.string,
238
244
  tabIndex: PropTypes.string,
239
245
  valid: sharedPropTypes.statusPropType,
240
246
  warning: sharedPropTypes.statusPropType,
@@ -13,6 +13,8 @@ const SingleSelect = ({
13
13
  selected = '',
14
14
  tabIndex,
15
15
  maxHeight,
16
+ menuMinWidth,
17
+ menuMaxWidth,
16
18
  inputMaxHeight,
17
19
  onChange,
18
20
  onFocus,
@@ -66,6 +68,8 @@ const SingleSelect = ({
66
68
  menu: menu,
67
69
  tabIndex: tabIndex,
68
70
  maxHeight: maxHeight,
71
+ menuMinWidth: menuMinWidth,
72
+ menuMaxWidth: menuMaxWidth,
69
73
  onChange: onChange,
70
74
  onFocus: onFocus,
71
75
  onKeyDown: onKeyDown,
@@ -110,6 +114,10 @@ SingleSelect.propTypes = {
110
114
  loading: PropTypes.bool,
111
115
  loadingText: PropTypes.string,
112
116
  maxHeight: PropTypes.string,
117
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
118
+ menuMaxWidth: PropTypes.string,
119
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
120
+ menuMinWidth: PropTypes.string,
113
121
  /** Text to show when filter returns no results. Required if `filterable` prop is true */
114
122
  noMatchText: requiredIf(props => props.filterable, PropTypes.string),
115
123
  placeholder: PropTypes.string,
@@ -462,4 +462,41 @@ export const ShiftedIntoView = args => /*#__PURE__*/React.createElement(React.Fr
462
462
  label: "option twelve"
463
463
  })), /*#__PURE__*/React.createElement(_JSXStyle, {
464
464
  id: "1828862170"
465
- }, ["html,body,#root{position:relative;height:300px !important;max-height:300px;}", "#root{padding-top:130px !important;}"]));
465
+ }, ["html,body,#root{position:relative;height:300px !important;max-height:300px;}", "#root{padding-top:130px !important;}"]));
466
+ export const WithMenuMaxWidth = args => /*#__PURE__*/React.createElement("div", {
467
+ style: {
468
+ width: 120
469
+ }
470
+ }, /*#__PURE__*/React.createElement(SingleSelect, args, /*#__PURE__*/React.createElement(SingleSelectOption, {
471
+ value: "1",
472
+ label: "option one"
473
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
474
+ value: "2",
475
+ label: "option two"
476
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
477
+ value: "3",
478
+ label: "A much longer option label"
479
+ })));
480
+ WithMenuMaxWidth.args = {
481
+ menuMaxWidth: '200px'
482
+ };
483
+ export const WithMenuMinWidth = args => /*#__PURE__*/React.createElement("div", {
484
+ style: {
485
+ width: 120
486
+ }
487
+ }, /*#__PURE__*/React.createElement(SingleSelect, args, /*#__PURE__*/React.createElement(SingleSelectOption, {
488
+ value: "1",
489
+ label: "option one"
490
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
491
+ value: "2",
492
+ label: "option two"
493
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
494
+ value: "3",
495
+ label: "option three"
496
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
497
+ value: "4",
498
+ label: "A longer option that exceeds the minimum"
499
+ })));
500
+ WithMenuMinWidth.args = {
501
+ menuMinWidth: '240px'
502
+ };
@@ -36,6 +36,8 @@ class SingleSelectField extends React.Component {
36
36
  helpText,
37
37
  validationText,
38
38
  maxHeight,
39
+ menuMinWidth,
40
+ menuMaxWidth,
39
41
  inputMaxHeight,
40
42
  inputWidth,
41
43
  children,
@@ -70,6 +72,8 @@ class SingleSelectField extends React.Component {
70
72
  selected: selected,
71
73
  tabIndex: tabIndex,
72
74
  maxHeight: maxHeight,
75
+ menuMinWidth: menuMinWidth,
76
+ menuMaxWidth: menuMaxWidth,
73
77
  inputMaxHeight: inputMaxHeight,
74
78
  onChange: onChange,
75
79
  onFocus: onFocus,
@@ -140,6 +144,10 @@ SingleSelectField.propTypes = {
140
144
  loadingText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
141
145
  /** Constrains height of the SingleSelect */
142
146
  maxHeight: PropTypes.string,
147
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
148
+ menuMaxWidth: PropTypes.string,
149
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
150
+ menuMinWidth: PropTypes.string,
143
151
  /** Text to display when there are no filter results */
144
152
  noMatchText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
145
153
  /** Placeholder text when the SingleSelect is empty */
@@ -173,4 +173,44 @@ DefaultLoadingText.args = {
173
173
  loading: true,
174
174
  ...DefaultEmpty.args
175
175
  };
176
- DefaultLoadingText.storyName = 'Default: loadingText';
176
+ DefaultLoadingText.storyName = 'Default: loadingText';
177
+ export const WithMenuMaxWidth = Template.bind({});
178
+ WithMenuMaxWidth.args = {
179
+ inputWidth: '120px',
180
+ menuMaxWidth: '200px',
181
+ children: [/*#__PURE__*/React.createElement(SingleSelectOption, {
182
+ key: "1",
183
+ value: "1",
184
+ label: "option one"
185
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
186
+ key: "2",
187
+ value: "2",
188
+ label: "option two"
189
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
190
+ key: "3",
191
+ value: "3",
192
+ label: "A much longer option label"
193
+ })]
194
+ };
195
+ export const WithMenuMinWidth = Template.bind({});
196
+ WithMenuMinWidth.args = {
197
+ inputWidth: '120px',
198
+ menuMinWidth: '240px',
199
+ children: [/*#__PURE__*/React.createElement(SingleSelectOption, {
200
+ key: "1",
201
+ value: "1",
202
+ label: "option one"
203
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
204
+ key: "2",
205
+ value: "2",
206
+ label: "option two"
207
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
208
+ key: "3",
209
+ value: "3",
210
+ label: "option three"
211
+ }), /*#__PURE__*/React.createElement(SingleSelectOption, {
212
+ key: "4",
213
+ value: "4",
214
+ label: "A longer option that exceeds the minimum"
215
+ })]
216
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2-ui/select",
3
- "version": "10.16.5",
3
+ "version": "10.17.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.16.5",
37
- "@dhis2-ui/button": "10.16.5",
38
- "@dhis2-ui/card": "10.16.5",
39
- "@dhis2-ui/checkbox": "10.16.5",
40
- "@dhis2-ui/chip": "10.16.5",
41
- "@dhis2-ui/field": "10.16.5",
42
- "@dhis2-ui/input": "10.16.5",
43
- "@dhis2-ui/layer": "10.16.5",
44
- "@dhis2-ui/loader": "10.16.5",
45
- "@dhis2-ui/popper": "10.16.5",
46
- "@dhis2-ui/status-icon": "10.16.5",
47
- "@dhis2-ui/tooltip": "10.16.5",
36
+ "@dhis2-ui/box": "10.17.0",
37
+ "@dhis2-ui/button": "10.17.0",
38
+ "@dhis2-ui/card": "10.17.0",
39
+ "@dhis2-ui/checkbox": "10.17.0",
40
+ "@dhis2-ui/chip": "10.17.0",
41
+ "@dhis2-ui/field": "10.17.0",
42
+ "@dhis2-ui/input": "10.17.0",
43
+ "@dhis2-ui/layer": "10.17.0",
44
+ "@dhis2-ui/loader": "10.17.0",
45
+ "@dhis2-ui/popper": "10.17.0",
46
+ "@dhis2-ui/status-icon": "10.17.0",
47
+ "@dhis2-ui/tooltip": "10.17.0",
48
48
  "@dhis2/prop-types": "^3.1.2",
49
- "@dhis2/ui-constants": "10.16.5",
50
- "@dhis2/ui-icons": "10.16.5",
49
+ "@dhis2/ui-constants": "10.17.0",
50
+ "@dhis2/ui-icons": "10.17.0",
51
51
  "classnames": "^2.3.1",
52
52
  "prop-types": "^15.7.2"
53
53
  },
@@ -15,6 +15,8 @@ const MultiSelect = ({
15
15
  selected = staticArr,
16
16
  tabIndex,
17
17
  maxHeight,
18
+ menuMinWidth,
19
+ menuMaxWidth,
18
20
  inputMaxHeight,
19
21
  onChange,
20
22
  onFocus,
@@ -72,6 +74,8 @@ const MultiSelect = ({
72
74
  menu={menu}
73
75
  tabIndex={tabIndex}
74
76
  maxHeight={maxHeight}
77
+ menuMinWidth={menuMinWidth}
78
+ menuMaxWidth={menuMaxWidth}
75
79
  onChange={onChange}
76
80
  onFocus={onFocus}
77
81
  onKeyDown={onKeyDown}
@@ -131,6 +135,10 @@ MultiSelect.propTypes = {
131
135
  loading: PropTypes.bool,
132
136
  loadingText: PropTypes.string,
133
137
  maxHeight: PropTypes.string,
138
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
139
+ menuMaxWidth: PropTypes.string,
140
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
141
+ menuMinWidth: PropTypes.string,
134
142
  /** Required if `filterable` prop is `true` */
135
143
  noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
136
144
  placeholder: PropTypes.string,
@@ -406,3 +406,29 @@ export const RTL = (args) => {
406
406
  )
407
407
  }
408
408
  RTL.args = { selected: ['1', '2'], prefix: 'RTL text' }
409
+
410
+ export const WithMenuMaxWidth = (args) => (
411
+ <div style={{ width: 120 }}>
412
+ <MultiSelect {...args}>
413
+ <MultiSelectOption value="1" label="option one" />
414
+ <MultiSelectOption value="2" label="option two" />
415
+ <MultiSelectOption value="3" label="A much longer option label" />
416
+ </MultiSelect>
417
+ </div>
418
+ )
419
+ WithMenuMaxWidth.args = { menuMaxWidth: '200px' }
420
+
421
+ export const WithMenuMinWidth = (args) => (
422
+ <div style={{ width: 120 }}>
423
+ <MultiSelect {...args}>
424
+ <MultiSelectOption value="1" label="option one" />
425
+ <MultiSelectOption value="2" label="option two" />
426
+ <MultiSelectOption value="3" label="option three" />
427
+ <MultiSelectOption
428
+ value="4"
429
+ label="A longer option that exceeds the minimum"
430
+ />
431
+ </MultiSelect>
432
+ </div>
433
+ )
434
+ WithMenuMinWidth.args = { menuMinWidth: '240px' }
@@ -46,6 +46,8 @@ class MultiSelectField extends React.Component {
46
46
  helpText,
47
47
  validationText,
48
48
  maxHeight,
49
+ menuMinWidth,
50
+ menuMaxWidth,
49
51
  inputMaxHeight,
50
52
  inputWidth,
51
53
  children,
@@ -81,6 +83,8 @@ class MultiSelectField extends React.Component {
81
83
  selected={selected}
82
84
  tabIndex={tabIndex}
83
85
  maxHeight={maxHeight}
86
+ menuMinWidth={menuMinWidth}
87
+ menuMaxWidth={menuMaxWidth}
84
88
  inputMaxHeight={inputMaxHeight}
85
89
  onChange={onChange}
86
90
  onFocus={onFocus}
@@ -148,6 +152,10 @@ MultiSelectField.propTypes = {
148
152
  loadingText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
149
153
  /** Constrains height of the MultiSelect */
150
154
  maxHeight: PropTypes.string,
155
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
156
+ menuMaxWidth: PropTypes.string,
157
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
158
+ menuMinWidth: PropTypes.string,
151
159
  /** Text to display when there are no filter results */
152
160
  noMatchText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
153
161
  /** Placeholder text when the MultiSelect is empty */
@@ -135,3 +135,34 @@ DefaultFilterPlaceholderAndNoMatchText.storyName =
135
135
  export const DefaultLoadingText = Template.bind({})
136
136
  DefaultLoadingText.args = { loading: true, ...DefaultEmpty.args }
137
137
  DefaultLoadingText.storyName = 'Default: loadingText'
138
+
139
+ export const WithMenuMaxWidth = Template.bind({})
140
+ WithMenuMaxWidth.args = {
141
+ inputWidth: '120px',
142
+ menuMaxWidth: '200px',
143
+ children: [
144
+ <MultiSelectOption key="1" value="1" label="option one" />,
145
+ <MultiSelectOption key="2" value="2" label="option two" />,
146
+ <MultiSelectOption
147
+ key="3"
148
+ value="3"
149
+ label="A much longer option label"
150
+ />,
151
+ ],
152
+ }
153
+
154
+ export const WithMenuMinWidth = Template.bind({})
155
+ WithMenuMinWidth.args = {
156
+ inputWidth: '120px',
157
+ menuMinWidth: '240px',
158
+ children: [
159
+ <MultiSelectOption key="1" value="1" label="option one" />,
160
+ <MultiSelectOption key="2" value="2" label="option two" />,
161
+ <MultiSelectOption key="3" value="3" label="option three" />,
162
+ <MultiSelectOption
163
+ key="4"
164
+ value="4"
165
+ label="A longer option that exceeds the minimum"
166
+ />,
167
+ ],
168
+ }
@@ -7,11 +7,23 @@ import React from 'react'
7
7
  const MenuWrapper = ({
8
8
  children,
9
9
  dataTest,
10
+ inputWidth,
10
11
  maxHeight = '280px',
11
- menuWidth,
12
+ menuMaxWidth,
13
+ menuMinWidth,
12
14
  onClick,
13
15
  selectRef,
14
16
  }) => {
17
+ // menuMinWidth or menuMaxWidth enables flexible sizing (fit-content), with
18
+ // min-width = max(input, menuMinWidth). Without them, width matches the input.
19
+ const flexible = menuMinWidth || menuMaxWidth
20
+ const width = flexible ? 'fit-content' : inputWidth
21
+ const flexibleMinWidth = menuMinWidth
22
+ ? `max(${inputWidth}, ${menuMinWidth})`
23
+ : inputWidth
24
+ const minWidth = flexible ? flexibleMinWidth : 'auto'
25
+ const maxWidth = menuMaxWidth || 'none'
26
+
15
27
  return (
16
28
  <Layer onBackdropClick={onClick} transparent>
17
29
  <Popper
@@ -24,7 +36,9 @@ const MenuWrapper = ({
24
36
 
25
37
  <style jsx>{`
26
38
  div {
27
- width: ${menuWidth};
39
+ width: ${width};
40
+ min-width: ${minWidth};
41
+ max-width: ${maxWidth};
28
42
  height: auto;
29
43
  max-height: ${maxHeight};
30
44
  overflow: auto;
@@ -42,10 +56,12 @@ const MenuWrapper = ({
42
56
 
43
57
  MenuWrapper.propTypes = {
44
58
  dataTest: PropTypes.string.isRequired,
45
- menuWidth: PropTypes.string.isRequired,
59
+ inputWidth: PropTypes.string.isRequired,
46
60
  selectRef: PropTypes.object.isRequired,
47
61
  children: PropTypes.node,
48
62
  maxHeight: PropTypes.string,
63
+ menuMaxWidth: PropTypes.string,
64
+ menuMinWidth: PropTypes.string,
49
65
  onClick: PropTypes.func,
50
66
  }
51
67
 
@@ -15,7 +15,7 @@ const DOWN_KEY = 40
15
15
  export class Select extends Component {
16
16
  state = {
17
17
  open: false,
18
- menuWidth: 'auto',
18
+ inputWidth: 'auto',
19
19
  }
20
20
 
21
21
  static defaultProps = {
@@ -30,7 +30,7 @@ export class Select extends Component {
30
30
  this.inputRef.current.focus()
31
31
  }
32
32
 
33
- this.setState({ menuWidth: this.getMenuWidth() })
33
+ this.setState({ inputWidth: this.getInputWidth() })
34
34
  window.addEventListener('resize', this.onResize)
35
35
  }
36
36
 
@@ -48,21 +48,21 @@ export class Select extends Component {
48
48
  * See: https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web
49
49
  */
50
50
  onResize = debounce(() => {
51
- const menuWidth = this.getMenuWidth()
51
+ const inputWidth = this.getInputWidth()
52
52
 
53
- if (this.state.menuWidth !== menuWidth) {
54
- this.setState({ menuWidth })
53
+ if (this.state.inputWidth !== inputWidth) {
54
+ this.setState({ inputWidth })
55
55
  }
56
56
  }, 50)
57
57
 
58
- getMenuWidth() {
58
+ getInputWidth() {
59
59
  const { offsetWidth } = this.inputRef.current
60
- const { menuWidth } = this.state
60
+ const { inputWidth } = this.state
61
61
 
62
- if (offsetWidth && `${offsetWidth}px` !== menuWidth) {
62
+ if (offsetWidth && `${offsetWidth}px` !== inputWidth) {
63
63
  return `${offsetWidth}px`
64
64
  }
65
- return menuWidth
65
+ return inputWidth
66
66
  }
67
67
 
68
68
  handleFocusInput = () => {
@@ -82,7 +82,7 @@ export class Select extends Component {
82
82
  handleOpen = () => {
83
83
  this.setState({
84
84
  open: true,
85
- menuWidth: this.getMenuWidth(),
85
+ inputWidth: this.getInputWidth(),
86
86
  })
87
87
  }
88
88
 
@@ -159,7 +159,7 @@ export class Select extends Component {
159
159
  }
160
160
 
161
161
  render() {
162
- const { open, menuWidth } = this.state
162
+ const { open, inputWidth } = this.state
163
163
  const {
164
164
  children,
165
165
  className,
@@ -167,6 +167,8 @@ export class Select extends Component {
167
167
  onChange,
168
168
  tabIndex,
169
169
  maxHeight,
170
+ menuMinWidth,
171
+ menuMaxWidth,
170
172
  error,
171
173
  warning,
172
174
  valid,
@@ -220,7 +222,9 @@ export class Select extends Component {
220
222
  onClick={this.onOutsideClick}
221
223
  maxHeight={maxHeight}
222
224
  selectRef={this.selectRef}
223
- menuWidth={menuWidth}
225
+ inputWidth={inputWidth}
226
+ menuMinWidth={menuMinWidth}
227
+ menuMaxWidth={menuMaxWidth}
224
228
  dataTest={`${dataTest}-menu`}
225
229
  >
226
230
  {menu}
@@ -246,6 +250,8 @@ Select.propTypes = {
246
250
  error: sharedPropTypes.statusPropType,
247
251
  initialFocus: PropTypes.bool,
248
252
  maxHeight: PropTypes.string,
253
+ menuMaxWidth: PropTypes.string,
254
+ menuMinWidth: PropTypes.string,
249
255
  tabIndex: PropTypes.string,
250
256
  valid: sharedPropTypes.statusPropType,
251
257
  warning: sharedPropTypes.statusPropType,
@@ -13,6 +13,8 @@ const SingleSelect = ({
13
13
  selected = '',
14
14
  tabIndex,
15
15
  maxHeight,
16
+ menuMinWidth,
17
+ menuMaxWidth,
16
18
  inputMaxHeight,
17
19
  onChange,
18
20
  onFocus,
@@ -68,6 +70,8 @@ const SingleSelect = ({
68
70
  menu={menu}
69
71
  tabIndex={tabIndex}
70
72
  maxHeight={maxHeight}
73
+ menuMinWidth={menuMinWidth}
74
+ menuMaxWidth={menuMaxWidth}
71
75
  onChange={onChange}
72
76
  onFocus={onFocus}
73
77
  onKeyDown={onKeyDown}
@@ -127,6 +131,10 @@ SingleSelect.propTypes = {
127
131
  loading: PropTypes.bool,
128
132
  loadingText: PropTypes.string,
129
133
  maxHeight: PropTypes.string,
134
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
135
+ menuMaxWidth: PropTypes.string,
136
+ /** See [dropdown menu width](https://developers.dhis2.org/docs/ui/components/select#dropdown-menu-width) */
137
+ menuMinWidth: PropTypes.string,
130
138
  /** Text to show when filter returns no results. Required if `filterable` prop is true */
131
139
  noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
132
140
  placeholder: PropTypes.string,
@@ -356,3 +356,29 @@ export const ShiftedIntoView = (args) => (
356
356
  `}</style>
357
357
  </>
358
358
  )
359
+
360
+ export const WithMenuMaxWidth = (args) => (
361
+ <div style={{ width: 120 }}>
362
+ <SingleSelect {...args}>
363
+ <SingleSelectOption value="1" label="option one" />
364
+ <SingleSelectOption value="2" label="option two" />
365
+ <SingleSelectOption value="3" label="A much longer option label" />
366
+ </SingleSelect>
367
+ </div>
368
+ )
369
+ WithMenuMaxWidth.args = { menuMaxWidth: '200px' }
370
+
371
+ export const WithMenuMinWidth = (args) => (
372
+ <div style={{ width: 120 }}>
373
+ <SingleSelect {...args}>
374
+ <SingleSelectOption value="1" label="option one" />
375
+ <SingleSelectOption value="2" label="option two" />
376
+ <SingleSelectOption value="3" label="option three" />
377
+ <SingleSelectOption
378
+ value="4"
379
+ label="A longer option that exceeds the minimum"
380
+ />
381
+ </SingleSelect>
382
+ </div>
383
+ )
384
+ WithMenuMinWidth.args = { menuMinWidth: '240px' }