Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core, select] feat: add fill prop to Popover, InputGroup, Suggest, MultiSelect #3636

Merged
merged 2 commits into from
Jul 1, 2019
Merged
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
10 changes: 8 additions & 2 deletions packages/core/src/components/forms/inputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export interface IInputGroupProps extends IControlledProps, IIntentProps, IProps
*/
disabled?: boolean;

/**
* Whether the component should take up the full width of its container.
*/
fill?: boolean;

/** Ref handler that receives HTML `<input>` element backing this component. */
inputRef?: (ref: HTMLInputElement | null) => any;

Expand Down Expand Up @@ -92,12 +97,13 @@ export class InputGroup extends React.PureComponent<IInputGroupProps & HTMLInput
};

public render() {
const { className, intent, large, small, leftIcon, round } = this.props;
const { className, disabled, fill, intent, large, small, leftIcon, round } = this.props;
const classes = classNames(
Classes.INPUT_GROUP,
Classes.intentClass(intent),
{
[Classes.DISABLED]: this.props.disabled,
[Classes.DISABLED]: disabled,
[Classes.FILL]: fill,
[Classes.LARGE]: large,
[Classes.SMALL]: small,
[Classes.ROUND]: round,
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/components/popover/_popover.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,7 @@ span.#{$ns}-popover-target {
// this is important for span tag as default inline display height only includes text.
// div tag can be used for display: block, which works fine.
}

.#{$ns}-popover-wrapper.#{$ns}-fill {
width: 100%;
}
8 changes: 0 additions & 8 deletions packages/core/src/components/popover/popover.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,6 @@ Minimal popovers are also useful for context menus that require quick enter and
support fast workflows. You can see an example in the [context menus](#core/components/context-menu)
documentation.

@### Filling container width

Sometimes you will have a React components or element which uses `width: 100%` or `fill: true` to
fill its container width. If you wrap this component or element in a `Popover`, it may no longer
achieve the layout you expect. This is likely because `Popover` injects an inline `<span>` tag wrapper.
To work around this issue, supply the prop `targetTagName="div"` to get a block layout wrapper
instead, which should better respect width layout styling.

@## Testing

<div class="@ns-callout @ns-intent-primary @ns-icon-info-sign">
Expand Down
26 changes: 23 additions & 3 deletions packages/core/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ export interface IPopoverProps extends IPopoverSharedProps {
*/
content?: string | JSX.Element;

/**
* Whether the wrapper and target should take up the full width of their container.
* Note that supplying `true` for this prop will force `targetTagName="div"` and
* `wrapperTagName="div"`.
*/
fill?: boolean;

/**
* The kind of interaction that triggers the display of the popover.
* @default PopoverInteractionKind.CLICK
Expand Down Expand Up @@ -100,6 +107,7 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>
captureDismiss: false,
defaultIsOpen: false,
disabled: false,
fill: false,
hasBackdrop: false,
hoverCloseDelay: 300,
hoverOpenDelay: 150,
Expand Down Expand Up @@ -156,8 +164,12 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>
// as JSX component instead of intrinsic element. but because of its
// type, tsc actually recognizes that it is _any_ intrinsic element, so
// it can typecheck the HTML props!!
const { className, disabled, wrapperTagName: WrapperTagName } = this.props;
const { className, disabled, fill } = this.props;
const { isOpen } = this.state;
let { wrapperTagName: WrapperTagName } = this.props;
if (fill) {
WrapperTagName = "div";
}

const isContentEmpty = Utils.ensureElement(this.understandChildren().content) == null;
// need to do this check in render(), because `isOpen` is derived from
Expand All @@ -166,9 +178,13 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>
console.warn(Errors.POPOVER_WARN_EMPTY_CONTENT);
}

const wrapperClasses = classNames(Classes.POPOVER_WRAPPER, className, {
[Classes.FILL]: fill,
});

return (
<Manager>
<WrapperTagName className={classNames(Classes.POPOVER_WRAPPER, className)}>
<WrapperTagName className={wrapperClasses}>
<Reference innerRef={this.refHandlers.target}>{this.renderTarget}</Reference>
<Overlay
autoFocus={this.props.autoFocus}
Expand Down Expand Up @@ -316,9 +332,13 @@ export class Popover extends AbstractPureComponent<IPopoverProps, IPopoverState>
};

private renderTarget = (referenceProps: ReferenceChildrenProps) => {
const { openOnTargetFocus, targetClassName, targetProps = {}, targetTagName: TagName } = this.props;
const { fill, openOnTargetFocus, targetClassName, targetProps = {} } = this.props;
const { isOpen } = this.state;
const isHoverInteractionKind = this.isHoverInteractionKind();
let { targetTagName: TagName } = this.props;
if (fill) {
TagName = "div";
}

const finalTargetProps: React.HTMLProps<HTMLElement> = isHoverInteractionKind
? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const INTENTS = [Intent.NONE, Intent.PRIMARY, Intent.SUCCESS, Intent.DANGER, Int
export interface IMultiSelectExampleState {
allowCreate: boolean;
createdItems: IFilm[];
fill: boolean;
films: IFilm[];
hasInitialContent: boolean;
intent: boolean;
Expand All @@ -52,6 +53,7 @@ export class MultiSelectExample extends React.PureComponent<IExampleProps, IMult
public state: IMultiSelectExampleState = {
allowCreate: false,
createdItems: [],
fill: false,
films: [],
hasInitialContent: false,
intent: false,
Expand All @@ -67,6 +69,7 @@ export class MultiSelectExample extends React.PureComponent<IExampleProps, IMult
private handleResetChange = this.handleSwitchChange("resetOnSelect");
private handlePopoverMinimalChange = this.handleSwitchChange("popoverMinimal");
private handleTagMinimalChange = this.handleSwitchChange("tagMinimal");
private handleFillChange = this.handleSwitchChange("fill");
private handleIntentChange = this.handleSwitchChange("intent");
private handleInitialContentChange = this.handleSwitchChange("hasInitialContent");

Expand Down Expand Up @@ -138,6 +141,7 @@ export class MultiSelectExample extends React.PureComponent<IExampleProps, IMult
checked={this.state.allowCreate}
onChange={this.handleAllowCreateChange}
/>
<Switch label="Fill container width" checked={this.state.fill} onChange={this.handleFillChange} />
<H5>Tag props</H5>
<Switch
label="Minimal tag style"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface ISuggestExampleState {
allowCreate: boolean;
closeOnSelect: boolean;
createdItems: IFilm[];
fill: boolean;
film: IFilm;
items: IFilm[];
minimal: boolean;
Expand All @@ -50,6 +51,7 @@ export class SuggestExample extends React.PureComponent<IExampleProps, ISuggestE
allowCreate: false,
closeOnSelect: true,
createdItems: [],
fill: false,
film: TOP_100_FILMS[0],
items: filmSelectProps.items,
minimal: true,
Expand All @@ -63,6 +65,7 @@ export class SuggestExample extends React.PureComponent<IExampleProps, ISuggestE
private handleCloseOnSelectChange = this.handleSwitchChange("closeOnSelect");
private handleOpenOnKeyDownChange = this.handleSwitchChange("openOnKeyDown");
private handleMinimalChange = this.handleSwitchChange("minimal");
private handleFillChange = this.handleSwitchChange("fill");
private handleResetOnCloseChange = this.handleSwitchChange("resetOnClose");
private handleResetOnQueryChange = this.handleSwitchChange("resetOnQuery");
private handleResetOnSelectChange = this.handleSwitchChange("resetOnSelect");
Expand Down Expand Up @@ -127,6 +130,7 @@ export class SuggestExample extends React.PureComponent<IExampleProps, ISuggestE
checked={this.state.allowCreate}
onChange={this.handleAllowCreateChange}
/>
<Switch label="Fill container width" checked={this.state.fill} onChange={this.handleFillChange} />
<H5>Popover props</H5>
<Switch
label="Minimal popover style"
Expand Down
18 changes: 15 additions & 3 deletions packages/select/src/components/select/multiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ import { Classes, IListItemsProps } from "../../common";
import { IQueryListRendererProps, QueryList } from "../query-list/queryList";

export interface IMultiSelectProps<T> extends IListItemsProps<T> {
/** Controlled selected values. */
selectedItems?: T[];
/**
* Whether the component should take up the full width of its container.
* This overrides `popoverProps.fill` and `tagInputProps.fill`.
*/
fill?: boolean;

/**
* Whether the popover opens on key down or when `TagInput` is focused.
Expand All @@ -49,6 +52,9 @@ export interface IMultiSelectProps<T> extends IListItemsProps<T> {
/** Props to spread to `Popover`. Note that `content` cannot be changed. */
popoverProps?: Partial<IPopoverProps> & object;

/** Controlled selected values. */
selectedItems?: T[];

/** Props to spread to `TagInput`. Use `query` and `onQueryChange` to control the input. */
tagInputProps?: Partial<ITagInputProps> & object;

Expand All @@ -64,6 +70,7 @@ export class MultiSelect<T> extends React.PureComponent<IMultiSelectProps<T>, IM
public static displayName = `${DISPLAYNAME_PREFIX}.MultiSelect`;

public static defaultProps = {
fill: false,
placeholder: "Search...",
};

Expand Down Expand Up @@ -102,9 +109,14 @@ export class MultiSelect<T> extends React.PureComponent<IMultiSelectProps<T>, IM
}

private renderQueryList = (listProps: IQueryListRendererProps<T>) => {
const { tagInputProps = {}, popoverProps = {}, selectedItems = [], placeholder } = this.props;
const { fill, tagInputProps = {}, popoverProps = {}, selectedItems = [], placeholder } = this.props;
const { handlePaste, handleKeyDown, handleKeyUp } = listProps;

if (fill) {
popoverProps.fill = true;
tagInputProps.fill = true;
}

const handleTagInputAdd = (values: any[], method: TagInputAddMethod) => {
if (method === "paste") {
handlePaste(values);
Expand Down
14 changes: 13 additions & 1 deletion packages/select/src/components/select/suggest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export interface ISuggestProps<T> extends IListItemsProps<T> {
/** Whether the input field should be disabled. */
disabled?: boolean;

/**
* Whether the component should take up the full width of its container.
* This overrides `popoverProps.fill` and `inputProps.fill`.
*/
fill?: boolean;

/**
* Props to spread to the query `InputGroup`. To control this input, use
* `query` and `onQueryChange` instead of `inputProps.value` and
Expand Down Expand Up @@ -91,6 +97,7 @@ export class Suggest<T> extends React.PureComponent<ISuggestProps<T>, ISuggestSt

public static defaultProps: Partial<ISuggestProps<any>> = {
closeOnSelect: true,
fill: false,
openOnKeyDown: false,
resetOnClose: false,
};
Expand Down Expand Up @@ -143,7 +150,7 @@ export class Suggest<T> extends React.PureComponent<ISuggestProps<T>, ISuggestSt
}

private renderQueryList = (listProps: IQueryListRendererProps<T>) => {
const { inputProps = {}, popoverProps = {} } = this.props;
const { fill, inputProps = {}, popoverProps = {} } = this.props;
const { isOpen, selectedItem } = this.state;
const { handleKeyDown, handleKeyUp } = listProps;
const { placeholder = "Search..." } = inputProps;
Expand All @@ -157,6 +164,11 @@ export class Suggest<T> extends React.PureComponent<ISuggestProps<T>, ISuggestSt
? listProps.query
: selectedItemText || (this.props.resetOnClose ? "" : listProps.query);

if (fill) {
popoverProps.fill = true;
inputProps.fill = true;
}

return (
<Popover
autoFocus={false}
Expand Down