Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class MultiSelectField extends React.Component {
helpText,
validationText,
maxHeight,
menuMinWidth,
menuMaxWidth,
inputMaxHeight,
inputWidth,
children,
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
<MultiSelectOption key="1" value="1" label="option one" />,
<MultiSelectOption key="2" value="2" label="option two" />,
<MultiSelectOption
key="3"
value="3"
label="A much longer option label"
/>,
],
}

export const WithMenuMinWidth = Template.bind({})
WithMenuMinWidth.args = {
inputWidth: '120px',
menuMinWidth: '240px',
children: [
<MultiSelectOption key="1" value="1" label="option one" />,
<MultiSelectOption key="2" value="2" label="option two" />,
<MultiSelectOption key="3" value="3" label="option three" />,
<MultiSelectOption
key="4"
value="4"
label="A longer option that exceeds the minimum"
/>,
],
}
8 changes: 8 additions & 0 deletions components/select/src/multi-select/multi-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const MultiSelect = ({
selected = staticArr,
tabIndex,
maxHeight,
menuMinWidth,
menuMaxWidth,
inputMaxHeight,
onChange,
onFocus,
Expand Down Expand Up @@ -72,6 +74,8 @@ const MultiSelect = ({
menu={menu}
tabIndex={tabIndex}
maxHeight={maxHeight}
menuMinWidth={menuMinWidth}
menuMaxWidth={menuMaxWidth}
onChange={onChange}
onFocus={onFocus}
onKeyDown={onKeyDown}
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions components/select/src/multi-select/multi-select.prod.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,29 @@ export const RTL = (args) => {
)
}
RTL.args = { selected: ['1', '2'], prefix: 'RTL text' }

export const WithMenuMaxWidth = (args) => (
<div style={{ width: 120 }}>
<MultiSelect {...args}>
<MultiSelectOption value="1" label="option one" />
<MultiSelectOption value="2" label="option two" />
<MultiSelectOption value="3" label="A much longer option label" />
</MultiSelect>
</div>
)
WithMenuMaxWidth.args = { menuMaxWidth: '200px' }

export const WithMenuMinWidth = (args) => (
<div style={{ width: 120 }}>
<MultiSelect {...args}>
<MultiSelectOption value="1" label="option one" />
<MultiSelectOption value="2" label="option two" />
<MultiSelectOption value="3" label="option three" />
<MultiSelectOption
value="4"
label="A longer option that exceeds the minimum"
/>
</MultiSelect>
</div>
)
WithMenuMinWidth.args = { menuMinWidth: '240px' }
22 changes: 19 additions & 3 deletions components/select/src/select/menu-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Layer onBackdropClick={onClick} transparent>
<Popper
Expand All @@ -24,7 +36,9 @@ const MenuWrapper = ({

<style jsx>{`
div {
width: ${menuWidth};
width: ${width};
min-width: ${minWidth};
max-width: ${maxWidth};
height: auto;
max-height: ${maxHeight};
overflow: auto;
Expand All @@ -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,
}

Expand Down
30 changes: 18 additions & 12 deletions components/select/src/select/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DOWN_KEY = 40
export class Select extends Component {
state = {
open: false,
menuWidth: 'auto',
inputWidth: 'auto',
}

static defaultProps = {
Expand All @@ -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)
}

Expand All @@ -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 = () => {
Expand All @@ -81,7 +81,7 @@ export class Select extends Component {
handleOpen = () => {
this.setState({
open: true,
menuWidth: this.getMenuWidth(),
inputWidth: this.getInputWidth(),
})
}

Expand Down Expand Up @@ -154,14 +154,16 @@ export class Select extends Component {
}

render() {
const { open, menuWidth } = this.state
const { open, inputWidth } = this.state
const {
children,
className,
selected,
onChange,
tabIndex,
maxHeight,
menuMinWidth,
menuMaxWidth,
error,
warning,
valid,
Expand Down Expand Up @@ -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}
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class SingleSelectField extends React.Component {
helpText,
validationText,
maxHeight,
menuMinWidth,
menuMaxWidth,
inputMaxHeight,
inputWidth,
children,
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
<SingleSelectOption key="1" value="1" label="option one" />,
<SingleSelectOption key="2" value="2" label="option two" />,
<SingleSelectOption
key="3"
value="3"
label="A much longer option label"
/>,
],
}

export const WithMenuMinWidth = Template.bind({})
WithMenuMinWidth.args = {
inputWidth: '120px',
menuMinWidth: '240px',
children: [
<SingleSelectOption key="1" value="1" label="option one" />,
<SingleSelectOption key="2" value="2" label="option two" />,
<SingleSelectOption key="3" value="3" label="option three" />,
<SingleSelectOption
key="4"
value="4"
label="A longer option that exceeds the minimum"
/>,
],
}
8 changes: 8 additions & 0 deletions components/select/src/single-select/single-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const SingleSelect = ({
selected = '',
tabIndex,
maxHeight,
menuMinWidth,
menuMaxWidth,
inputMaxHeight,
onChange,
onFocus,
Expand Down Expand Up @@ -68,6 +70,8 @@ const SingleSelect = ({
menu={menu}
tabIndex={tabIndex}
maxHeight={maxHeight}
menuMinWidth={menuMinWidth}
menuMaxWidth={menuMaxWidth}
onChange={onChange}
onFocus={onFocus}
onKeyDown={onKeyDown}
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,29 @@ export const ShiftedIntoView = (args) => (
`}</style>
</>
)

export const WithMenuMaxWidth = (args) => (
<div style={{ width: 120 }}>
<SingleSelect {...args}>
<SingleSelectOption value="1" label="option one" />
<SingleSelectOption value="2" label="option two" />
<SingleSelectOption value="3" label="A much longer option label" />
</SingleSelect>
</div>
)
WithMenuMaxWidth.args = { menuMaxWidth: '200px' }

export const WithMenuMinWidth = (args) => (
<div style={{ width: 120 }}>
<SingleSelect {...args}>
<SingleSelectOption value="1" label="option one" />
<SingleSelectOption value="2" label="option two" />
<SingleSelectOption value="3" label="option three" />
<SingleSelectOption
value="4"
label="A longer option that exceeds the minimum"
/>
</SingleSelect>
</div>
)
WithMenuMinWidth.args = { menuMinWidth: '240px' }
Loading