diff --git a/packages/docs-app/src/examples/select-examples/selectExample.tsx b/packages/docs-app/src/examples/select-examples/selectExample.tsx index 293aa55a39..1c4dc41204 100644 --- a/packages/docs-app/src/examples/select-examples/selectExample.tsx +++ b/packages/docs-app/src/examples/select-examples/selectExample.tsx @@ -34,6 +34,7 @@ export interface ISelectExampleState { resetOnSelect: boolean; disableItems: boolean; disabled: boolean; + matchTargetWidth: false; } export class SelectExample extends React.PureComponent<IExampleProps, ISelectExampleState> { @@ -45,6 +46,7 @@ export class SelectExample extends React.PureComponent<IExampleProps, ISelectExa disabled: false, filterable: true, hasInitialContent: false, + matchTargetWidth: false, minimal: false, resetOnClose: false, resetOnQuery: true, @@ -71,6 +73,8 @@ export class SelectExample extends React.PureComponent<IExampleProps, ISelectExa private handleResetOnSelectChange = this.handleSwitchChange("resetOnSelect"); + private handleMatchTargetWidthChange = this.handleSwitchChange("matchTargetWidth"); + public render() { const { allowCreate, disabled, disableItems, minimal, ...flags } = this.state; @@ -124,6 +128,11 @@ export class SelectExample extends React.PureComponent<IExampleProps, ISelectExa checked={this.state.disableItems} onChange={this.handleItemDisabledChange} /> + <Switch + label="Match target width" + checked={this.state.matchTargetWidth} + onChange={this.handleMatchTargetWidthChange} + /> <Switch label="Allow creating new items" checked={this.state.allowCreate} diff --git a/packages/select/src/common/classes.ts b/packages/select/src/common/classes.ts index 47d5ab0daa..00c9e1ee9b 100644 --- a/packages/select/src/common/classes.ts +++ b/packages/select/src/common/classes.ts @@ -25,3 +25,4 @@ export const OMNIBAR = `${NS}-omnibar`; export const OMNIBAR_OVERLAY = `${OMNIBAR}-overlay`; export const SELECT = `${NS}-select`; export const SELECT_POPOVER = `${SELECT}-popover`; +export const SELECT_MATCH_TARGET_WIDTH = `${SELECT}-match-target-width`; diff --git a/packages/select/src/components/select/_select.scss b/packages/select/src/components/select/_select.scss index f4f009dae6..128ad07885 100644 --- a/packages/select/src/components/select/_select.scss +++ b/packages/select/src/components/select/_select.scss @@ -7,6 +7,15 @@ $select-popover-max-height: $pt-grid-size * 30 !default; $select-popover-max-width: $pt-grid-size * 40 !default; .#{$ns}-select-popover { + &.#{$ns}-select-match-target-width { + width: 100%; + + .#{$ns}-menu { + max-width: none; + min-width: 0; + } + } + .#{$ns}-popover-content { // use padding on container rather than margin on input group // because top margin leaves some empty space with no background color. diff --git a/packages/select/src/components/select/select.tsx b/packages/select/src/components/select/select.tsx index e03ac779a1..a2cd1125a9 100644 --- a/packages/select/src/components/select/select.tsx +++ b/packages/select/src/components/select/select.tsx @@ -63,6 +63,16 @@ export interface ISelectProps<T> extends IListItemsProps<T> { */ inputProps?: InputGroupProps2; + /** + * Whether the select popover should be styled so that it matches the width of the target. + * This is done using a popper.js modifier passed through `popoverProps`. + * + * Note that setting `matchTargetWidth={true}` will also set `popoverProps.usePortal={false}` and `popoverProps.wrapperTagName="div"`. + * + * @default false + */ + matchTargetWidth?: boolean; + /** Props to spread to `Popover`. Note that `content` cannot be changed. */ // eslint-disable-next-line @typescript-eslint/ban-types popoverProps?: Partial<IPopoverProps> & object; @@ -129,7 +139,31 @@ export class Select<T> extends AbstractPureComponent2<SelectProps<T>, ISelectSta private renderQueryList = (listProps: IQueryListRendererProps<T>) => { // not using defaultProps cuz they're hard to type with generics (can't use <T> on static members) - const { filterable = true, disabled = false, inputProps = {}, popoverProps = {} } = this.props; + const { + filterable = true, + disabled = false, + inputProps = {}, + popoverProps = {}, + matchTargetWidth, + } = this.props; + + if (matchTargetWidth) { + if (popoverProps.modifiers == null) { + popoverProps.modifiers = {}; + } + + popoverProps.modifiers.minWidth = { + enabled: true, + fn: data => { + data.styles.width = `${data.offsets.reference.width}px`; + return data; + }, + order: 800, + }; + + popoverProps.usePortal = false; + popoverProps.wrapperTagName = "div"; + } const input = ( <InputGroup @@ -155,7 +189,9 @@ export class Select<T> extends AbstractPureComponent2<SelectProps<T>, ISelectSta {...popoverProps} className={classNames(listProps.className, popoverProps.className)} onInteraction={this.handlePopoverInteraction} - popoverClassName={classNames(Classes.SELECT_POPOVER, popoverProps.popoverClassName)} + popoverClassName={classNames(Classes.SELECT_POPOVER, popoverProps.popoverClassName, { + [Classes.SELECT_MATCH_TARGET_WIDTH]: matchTargetWidth, + })} onOpening={this.handlePopoverOpening} onOpened={this.handlePopoverOpened} onClosing={this.handlePopoverClosing}