diff --git a/components/select/src/multi-select-field/multi-select-field.js b/components/select/src/multi-select-field/multi-select-field.js
index 55754af71..864a95bb6 100644
--- a/components/select/src/multi-select-field/multi-select-field.js
+++ b/components/select/src/multi-select-field/multi-select-field.js
@@ -46,6 +46,8 @@ class MultiSelectField extends React.Component {
helpText,
validationText,
maxHeight,
+ menuMinWidth,
+ menuMaxWidth,
inputMaxHeight,
inputWidth,
children,
@@ -81,6 +83,8 @@ class MultiSelectField extends React.Component {
selected={selected}
tabIndex={tabIndex}
maxHeight={maxHeight}
+ menuMinWidth={menuMinWidth}
+ menuMaxWidth={menuMaxWidth}
inputMaxHeight={inputMaxHeight}
onChange={onChange}
onFocus={onFocus}
@@ -148,6 +152,10 @@ MultiSelectField.propTypes = {
loadingText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** Constrains height of the MultiSelect */
maxHeight: PropTypes.string,
+ /** Sets a maximum width for the dropdown menu */
+ menuMaxWidth: PropTypes.string,
+ /** Sets a minimum width for the dropdown menu */
+ menuMinWidth: PropTypes.string,
/** Text to display when there are no filter results */
noMatchText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** Placeholder text when the MultiSelect is empty */
diff --git a/components/select/src/multi-select-field/multi-select-field.prod.stories.js b/components/select/src/multi-select-field/multi-select-field.prod.stories.js
index 69869c2d3..b834964bb 100644
--- a/components/select/src/multi-select-field/multi-select-field.prod.stories.js
+++ b/components/select/src/multi-select-field/multi-select-field.prod.stories.js
@@ -135,3 +135,34 @@ DefaultFilterPlaceholderAndNoMatchText.storyName =
export const DefaultLoadingText = Template.bind({})
DefaultLoadingText.args = { loading: true, ...DefaultEmpty.args }
DefaultLoadingText.storyName = 'Default: loadingText'
+
+export const WithMenuMaxWidth = Template.bind({})
+WithMenuMaxWidth.args = {
+ inputWidth: '120px',
+ menuMaxWidth: '200px',
+ children: [
+ ,
+ ,
+ ,
+ ],
+}
+
+export const WithMenuMinWidth = Template.bind({})
+WithMenuMinWidth.args = {
+ inputWidth: '120px',
+ menuMinWidth: '240px',
+ children: [
+ ,
+ ,
+ ,
+ ,
+ ],
+}
diff --git a/components/select/src/multi-select/multi-select.js b/components/select/src/multi-select/multi-select.js
index d4aa9fc42..6a4e3d353 100644
--- a/components/select/src/multi-select/multi-select.js
+++ b/components/select/src/multi-select/multi-select.js
@@ -15,6 +15,8 @@ const MultiSelect = ({
selected = staticArr,
tabIndex,
maxHeight,
+ menuMinWidth,
+ menuMaxWidth,
inputMaxHeight,
onChange,
onFocus,
@@ -72,6 +74,8 @@ const MultiSelect = ({
menu={menu}
tabIndex={tabIndex}
maxHeight={maxHeight}
+ menuMinWidth={menuMinWidth}
+ menuMaxWidth={menuMaxWidth}
onChange={onChange}
onFocus={onFocus}
onKeyDown={onKeyDown}
@@ -131,6 +135,10 @@ MultiSelect.propTypes = {
loading: PropTypes.bool,
loadingText: PropTypes.string,
maxHeight: PropTypes.string,
+ /** Sets a maximum width for the dropdown menu */
+ menuMaxWidth: PropTypes.string,
+ /** Sets a minimum width for the dropdown menu */
+ menuMinWidth: PropTypes.string,
/** Required if `filterable` prop is `true` */
noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
placeholder: PropTypes.string,
diff --git a/components/select/src/multi-select/multi-select.prod.stories.js b/components/select/src/multi-select/multi-select.prod.stories.js
index 7dd7c440e..95749aea4 100644
--- a/components/select/src/multi-select/multi-select.prod.stories.js
+++ b/components/select/src/multi-select/multi-select.prod.stories.js
@@ -406,3 +406,29 @@ export const RTL = (args) => {
)
}
RTL.args = { selected: ['1', '2'], prefix: 'RTL text' }
+
+export const WithMenuMaxWidth = (args) => (
+
+
+
+
+
+
+
+)
+WithMenuMaxWidth.args = { menuMaxWidth: '200px' }
+
+export const WithMenuMinWidth = (args) => (
+
+
+
+
+
+
+
+
+)
+WithMenuMinWidth.args = { menuMinWidth: '240px' }
diff --git a/components/select/src/select/menu-wrapper.js b/components/select/src/select/menu-wrapper.js
index 597b357a0..a5d41f18a 100644
--- a/components/select/src/select/menu-wrapper.js
+++ b/components/select/src/select/menu-wrapper.js
@@ -7,11 +7,23 @@ import React from 'react'
const MenuWrapper = ({
children,
dataTest,
+ inputWidth,
maxHeight = '280px',
- menuWidth,
+ menuMaxWidth,
+ menuMinWidth,
onClick,
selectRef,
}) => {
+ // menuMinWidth or menuMaxWidth enables flexible sizing (fit-content), with
+ // min-width = max(input, menuMinWidth). Without them, width matches the input.
+ const flexible = menuMinWidth || menuMaxWidth
+ const width = flexible ? 'fit-content' : inputWidth
+ const flexibleMinWidth = menuMinWidth
+ ? `max(${inputWidth}, ${menuMinWidth})`
+ : inputWidth
+ const minWidth = flexible ? flexibleMinWidth : 'auto'
+ const maxWidth = menuMaxWidth || 'none'
+
return (
{`
div {
- width: ${menuWidth};
+ width: ${width};
+ min-width: ${minWidth};
+ max-width: ${maxWidth};
height: auto;
max-height: ${maxHeight};
overflow: auto;
@@ -42,10 +56,12 @@ const MenuWrapper = ({
MenuWrapper.propTypes = {
dataTest: PropTypes.string.isRequired,
- menuWidth: PropTypes.string.isRequired,
+ inputWidth: PropTypes.string.isRequired,
selectRef: PropTypes.object.isRequired,
children: PropTypes.node,
maxHeight: PropTypes.string,
+ menuMaxWidth: PropTypes.string,
+ menuMinWidth: PropTypes.string,
onClick: PropTypes.func,
}
diff --git a/components/select/src/select/select.js b/components/select/src/select/select.js
index 1579b24c4..ec94773b7 100644
--- a/components/select/src/select/select.js
+++ b/components/select/src/select/select.js
@@ -14,7 +14,7 @@ const DOWN_KEY = 40
export class Select extends Component {
state = {
open: false,
- menuWidth: 'auto',
+ inputWidth: 'auto',
}
static defaultProps = {
@@ -29,7 +29,7 @@ export class Select extends Component {
this.inputRef.current.focus()
}
- this.setState({ menuWidth: this.getMenuWidth() })
+ this.setState({ inputWidth: this.getInputWidth() })
window.addEventListener('resize', this.onResize)
}
@@ -47,21 +47,21 @@ export class Select extends Component {
* See: https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web
*/
onResize = debounce(() => {
- const menuWidth = this.getMenuWidth()
+ const inputWidth = this.getInputWidth()
- if (this.state.menuWidth !== menuWidth) {
- this.setState({ menuWidth })
+ if (this.state.inputWidth !== inputWidth) {
+ this.setState({ inputWidth })
}
}, 50)
- getMenuWidth() {
+ getInputWidth() {
const { offsetWidth } = this.inputRef.current
- const { menuWidth } = this.state
+ const { inputWidth } = this.state
- if (offsetWidth && `${offsetWidth}px` !== menuWidth) {
+ if (offsetWidth && `${offsetWidth}px` !== inputWidth) {
return `${offsetWidth}px`
}
- return menuWidth
+ return inputWidth
}
handleFocusInput = () => {
@@ -81,7 +81,7 @@ export class Select extends Component {
handleOpen = () => {
this.setState({
open: true,
- menuWidth: this.getMenuWidth(),
+ inputWidth: this.getInputWidth(),
})
}
@@ -154,7 +154,7 @@ export class Select extends Component {
}
render() {
- const { open, menuWidth } = this.state
+ const { open, inputWidth } = this.state
const {
children,
className,
@@ -162,6 +162,8 @@ export class Select extends Component {
onChange,
tabIndex,
maxHeight,
+ menuMinWidth,
+ menuMaxWidth,
error,
warning,
valid,
@@ -215,7 +217,9 @@ export class Select extends Component {
onClick={this.onOutsideClick}
maxHeight={maxHeight}
selectRef={this.selectRef}
- menuWidth={menuWidth}
+ inputWidth={inputWidth}
+ menuMinWidth={menuMinWidth}
+ menuMaxWidth={menuMaxWidth}
dataTest={`${dataTest}-menu`}
>
{menu}
@@ -241,6 +245,8 @@ Select.propTypes = {
error: sharedPropTypes.statusPropType,
initialFocus: PropTypes.bool,
maxHeight: PropTypes.string,
+ menuMaxWidth: PropTypes.string,
+ menuMinWidth: PropTypes.string,
tabIndex: PropTypes.string,
valid: sharedPropTypes.statusPropType,
warning: sharedPropTypes.statusPropType,
diff --git a/components/select/src/single-select-field/single-select-field.js b/components/select/src/single-select-field/single-select-field.js
index f3766e2a9..e77f7bc05 100644
--- a/components/select/src/single-select-field/single-select-field.js
+++ b/components/select/src/single-select-field/single-select-field.js
@@ -45,6 +45,8 @@ class SingleSelectField extends React.Component {
helpText,
validationText,
maxHeight,
+ menuMinWidth,
+ menuMaxWidth,
inputMaxHeight,
inputWidth,
children,
@@ -81,6 +83,8 @@ class SingleSelectField extends React.Component {
selected={selected}
tabIndex={tabIndex}
maxHeight={maxHeight}
+ menuMinWidth={menuMinWidth}
+ menuMaxWidth={menuMaxWidth}
inputMaxHeight={inputMaxHeight}
onChange={onChange}
onFocus={onFocus}
@@ -148,6 +152,10 @@ SingleSelectField.propTypes = {
loadingText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** Constrains height of the SingleSelect */
maxHeight: PropTypes.string,
+ /** Sets a maximum width for the dropdown menu */
+ menuMaxWidth: PropTypes.string,
+ /** Sets a minimum width for the dropdown menu */
+ menuMinWidth: PropTypes.string,
/** Text to display when there are no filter results */
noMatchText: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** Placeholder text when the SingleSelect is empty */
diff --git a/components/select/src/single-select-field/single-select-field.prod.stories.js b/components/select/src/single-select-field/single-select-field.prod.stories.js
index cf20d6dbc..167934510 100644
--- a/components/select/src/single-select-field/single-select-field.prod.stories.js
+++ b/components/select/src/single-select-field/single-select-field.prod.stories.js
@@ -144,3 +144,34 @@ DefaultFilterPlaceholderAndNoMatchText.storyName =
export const DefaultLoadingText = Template.bind({})
DefaultLoadingText.args = { loading: true, ...DefaultEmpty.args }
DefaultLoadingText.storyName = 'Default: loadingText'
+
+export const WithMenuMaxWidth = Template.bind({})
+WithMenuMaxWidth.args = {
+ inputWidth: '120px',
+ menuMaxWidth: '200px',
+ children: [
+ ,
+ ,
+ ,
+ ],
+}
+
+export const WithMenuMinWidth = Template.bind({})
+WithMenuMinWidth.args = {
+ inputWidth: '120px',
+ menuMinWidth: '240px',
+ children: [
+ ,
+ ,
+ ,
+ ,
+ ],
+}
diff --git a/components/select/src/single-select/single-select.js b/components/select/src/single-select/single-select.js
index 9f190e1ac..334acfa94 100644
--- a/components/select/src/single-select/single-select.js
+++ b/components/select/src/single-select/single-select.js
@@ -13,6 +13,8 @@ const SingleSelect = ({
selected = '',
tabIndex,
maxHeight,
+ menuMinWidth,
+ menuMaxWidth,
inputMaxHeight,
onChange,
onFocus,
@@ -68,6 +70,8 @@ const SingleSelect = ({
menu={menu}
tabIndex={tabIndex}
maxHeight={maxHeight}
+ menuMinWidth={menuMinWidth}
+ menuMaxWidth={menuMaxWidth}
onChange={onChange}
onFocus={onFocus}
onKeyDown={onKeyDown}
@@ -127,6 +131,10 @@ SingleSelect.propTypes = {
loading: PropTypes.bool,
loadingText: PropTypes.string,
maxHeight: PropTypes.string,
+ /** Sets a maximum width for the dropdown menu */
+ menuMaxWidth: PropTypes.string,
+ /** Sets a minimum width for the dropdown menu */
+ menuMinWidth: PropTypes.string,
/** Text to show when filter returns no results. Required if `filterable` prop is true */
noMatchText: requiredIf((props) => props.filterable, PropTypes.string),
placeholder: PropTypes.string,
diff --git a/components/select/src/single-select/single-select.prod.stories.js b/components/select/src/single-select/single-select.prod.stories.js
index 6039292be..b7d570de6 100644
--- a/components/select/src/single-select/single-select.prod.stories.js
+++ b/components/select/src/single-select/single-select.prod.stories.js
@@ -356,3 +356,29 @@ export const ShiftedIntoView = (args) => (
`}
>
)
+
+export const WithMenuMaxWidth = (args) => (
+
+
+
+
+
+
+
+)
+WithMenuMaxWidth.args = { menuMaxWidth: '200px' }
+
+export const WithMenuMinWidth = (args) => (
+
+
+
+
+
+
+
+
+)
+WithMenuMinWidth.args = { menuMinWidth: '240px' }