From 7167c440727ed5f64ddb1a2e3c2d749d697ae2c4 Mon Sep 17 00:00:00 2001 From: ejose19 <8742215+ejose19@users.noreply.github.com> Date: Tue, 19 Jan 2021 14:25:42 -0300 Subject: [PATCH] [core] refactor(InputGroup): improve interface (#4441) --- packages/core/src/common/props.ts | 16 +++- .../core/src/components/forms/inputGroup.tsx | 91 +++++++++++++++++-- packages/datetime/src/dateInput.tsx | 7 +- packages/datetime/src/dateRangeInput.tsx | 7 +- .../datetime/test/dateRangeInputTests.tsx | 4 +- .../docs-theme/src/components/navigator.tsx | 4 +- .../select/src/components/omnibar/omnibar.tsx | 11 +-- .../select/src/components/select/select.tsx | 5 +- .../select/src/components/select/suggest.tsx | 5 +- .../timezone-picker/timezonePicker.tsx | 7 +- .../timezone/test/timezonePickerTests.tsx | 6 +- 11 files changed, 121 insertions(+), 42 deletions(-) diff --git a/packages/core/src/common/props.ts b/packages/core/src/common/props.ts index f6bd5aa398..c03924f9eb 100644 --- a/packages/core/src/common/props.ts +++ b/packages/core/src/common/props.ts @@ -82,7 +82,11 @@ export interface ILinkProps { target?: string; } -/** Interface for a controlled input. */ +/** + * @deprecated use IControlledProps2. + * + * Interface for a controlled input. + */ export interface IControlledProps { /** Initial value of the input, for uncontrolled usage. */ defaultValue?: string; @@ -94,6 +98,14 @@ export interface IControlledProps { value?: string; } +export interface IControlledProps2 { + /** Initial value of the input, for uncontrolled usage. */ + defaultValue?: string; + + /** Form value of the input, for controlled usage. */ + value?: string; +} + export interface IElementRefProps { /** A ref handler or a ref object that receives the native HTML element rendered by this component. */ elementRef?: IRef; @@ -117,7 +129,7 @@ export interface IOptionProps extends IProps { const INVALID_PROPS = [ "active", "alignText", - "asyncControl", // IInputGroupProps + "asyncControl", // IInputGroupProps2 "containerRef", "current", "elementRef", diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx index 543e9e63d9..efa1a80d69 100644 --- a/packages/core/src/components/forms/inputGroup.tsx +++ b/packages/core/src/components/forms/inputGroup.tsx @@ -24,6 +24,7 @@ import { DISPLAYNAME_PREFIX, HTMLInputProps, IControlledProps, + IControlledProps2, IIntentProps, IProps, MaybeElement, @@ -32,9 +33,87 @@ import { import { Icon, IconName } from "../icon/icon"; import { AsyncControllableInput } from "./asyncControllableInput"; -// NOTE: This interface does not extend HTMLInputProps due to incompatiblity with `IControlledProps`. -// Instead, we union the props in the component definition, which does work and properly disallows `string[]` values. -export interface IInputGroupProps extends IControlledProps, IIntentProps, IProps { +/** + * @deprecated use IInputGroupProps2. + * + * NOTE: This interface does not extend HTMLInputProps due to incompatiblity with `IControlledProps`. + * Instead, we union the props in the component definition, which does work and properly disallows `string[]` values. + */ + +export interface IInputGroupProps + // eslint-disable-next-line deprecation/deprecation + extends IControlledProps, + IIntentProps, + IProps { + /** + * Set this to `true` if you will be controlling the `value` of this input with asynchronous updates. + * These may occur if you do not immediately call setState in a parent component with the value from + * the `onChange` handler, or if working with certain libraries like __redux-form__. + * + * @default false + */ + asyncControl?: boolean; + + /** + * Whether the input is non-interactive. + * Note that `rightElement` must be disabled separately; this prop will not affect it. + * + * @default false + */ + disabled?: boolean; + + /** + * Whether the component should take up the full width of its container. + */ + fill?: boolean; + + /** Ref handler or a ref object that receives HTML `` element backing this component. */ + inputRef?: IRef; + + /** + * Element to render on the left side of input. This prop is mutually exclusive + * with `leftIcon`. + */ + leftElement?: JSX.Element; + + /** + * Name of a Blueprint UI icon to render on the left side of the input group, + * before the user's cursor. This prop is mutually exclusive with `leftElement`. + * Usage with content is deprecated. Use `leftElement` for elements. + */ + leftIcon?: IconName | MaybeElement; + + /** Whether this input should use large styles. */ + large?: boolean; + + /** Whether this input should use small styles. */ + small?: boolean; + + /** Placeholder text in the absence of any value. */ + placeholder?: string; + + /** + * Element to render on right side of input. + * For best results, use a minimal button, tag, or small spinner. + */ + rightElement?: JSX.Element; + + /** Whether the input (and any buttons) should appear with rounded caps. */ + round?: boolean; + + /** + * HTML `input` type attribute. + * + * @default "text" + */ + type?: string; +} + +export interface IInputGroupProps2 + extends Omit, + IControlledProps2, + IIntentProps, + IProps { /** * Set this to `true` if you will be controlling the `value` of this input with asynchronous updates. * These may occur if you do not immediately call setState in a parent component with the value from @@ -105,7 +184,7 @@ export interface IInputGroupState { } @polyfill -export class InputGroup extends AbstractPureComponent2 { +export class InputGroup extends AbstractPureComponent2 { public static displayName = `${DISPLAYNAME_PREFIX}.InputGroup`; public state: IInputGroupState = {}; @@ -162,14 +241,14 @@ export class InputGroup extends AbstractPureComponent2) { + private safeInvokeInputProp(name: keyof IInputGroupProps2, e: React.SyntheticEvent) { const { inputProps = {} } = this.props; inputProps[name]?.(e); } diff --git a/packages/datetime/src/dateRangeInput.tsx b/packages/datetime/src/dateRangeInput.tsx index 9a49a9008a..d9ae108314 100644 --- a/packages/datetime/src/dateRangeInput.tsx +++ b/packages/datetime/src/dateRangeInput.tsx @@ -25,8 +25,7 @@ import { Classes, DISPLAYNAME_PREFIX, getRef, - HTMLInputProps, - IInputGroupProps, + IInputGroupProps2, InputGroup, Intent, IPopoverProps, @@ -96,7 +95,7 @@ export interface IDateRangeInputProps extends IDatePickerBaseProps, IDateFormatP * `disabled` and `value` will be ignored in favor of the top-level props on this component. * `ref` is not supported; use `inputRef` instead. */ - endInputProps?: HTMLInputProps & IInputGroupProps; + endInputProps?: IInputGroupProps2; /** * Called when the user selects a day. @@ -158,7 +157,7 @@ export interface IDateRangeInputProps extends IDatePickerBaseProps, IDateFormatP * `disabled` and `value` will be ignored in favor of the top-level props on this component. * `ref` is not supported; use `inputRef` instead. */ - startInputProps?: HTMLInputProps & IInputGroupProps; + startInputProps?: IInputGroupProps2; /** * The currently selected date range. diff --git a/packages/datetime/test/dateRangeInputTests.tsx b/packages/datetime/test/dateRangeInputTests.tsx index e715713d22..848547f245 100644 --- a/packages/datetime/test/dateRangeInputTests.tsx +++ b/packages/datetime/test/dateRangeInputTests.tsx @@ -26,7 +26,7 @@ import { Classes as CoreClasses, HTMLDivProps, HTMLInputProps, - IInputGroupProps, + IInputGroupProps2, InputGroup, IPopoverProps, Keys, @@ -258,7 +258,7 @@ describe("", () => { function runTestSuite( inputGetterFn: (root: WrappedComponentRoot) => WrappedComponentInput, - mountFn: (inputGroupProps: HTMLInputProps & IInputGroupProps) => any, + mountFn: (inputGroupProps: IInputGroupProps2) => any, ) { it("allows custom placeholder text", () => { const root = mountFn({ placeholder: "Hello" }); diff --git a/packages/docs-theme/src/components/navigator.tsx b/packages/docs-theme/src/components/navigator.tsx index 5737885e6d..18d9295908 100644 --- a/packages/docs-theme/src/components/navigator.tsx +++ b/packages/docs-theme/src/components/navigator.tsx @@ -18,7 +18,7 @@ import { IHeadingNode, IPageNode } from "@documentalist/client"; import { filter } from "fuzzaldrin-plus"; import * as React from "react"; -import { Classes, Icon, IInputGroupProps, MenuItem } from "@blueprintjs/core"; +import { Classes, Icon, IInputGroupProps2, MenuItem } from "@blueprintjs/core"; import { ItemListPredicate, ItemRenderer, Omnibar } from "@blueprintjs/select"; import { eachLayoutNode } from "../common/utils"; @@ -47,7 +47,7 @@ export interface INavigationSection { } const NavOmnibar = Omnibar.ofType(); -const INPUT_PROPS: IInputGroupProps = { placeholder: "Fuzzy search headings..." }; +const INPUT_PROPS: IInputGroupProps2 = { placeholder: "Fuzzy search headings..." }; export class Navigator extends React.PureComponent { private sections: INavigationSection[]; diff --git a/packages/select/src/components/omnibar/omnibar.tsx b/packages/select/src/components/omnibar/omnibar.tsx index 5a9bc5a93c..fdfe0c7d97 100644 --- a/packages/select/src/components/omnibar/omnibar.tsx +++ b/packages/select/src/components/omnibar/omnibar.tsx @@ -17,14 +17,7 @@ import classNames from "classnames"; import * as React from "react"; -import { - DISPLAYNAME_PREFIX, - HTMLInputProps, - IInputGroupProps, - InputGroup, - IOverlayProps, - Overlay, -} from "@blueprintjs/core"; +import { DISPLAYNAME_PREFIX, IInputGroupProps2, InputGroup, IOverlayProps, Overlay } from "@blueprintjs/core"; import { Classes, IListItemsProps } from "../../common"; import { IQueryListRendererProps, QueryList } from "../query-list/queryList"; @@ -35,7 +28,7 @@ export interface IOmnibarProps extends IListItemsProps { * `onQueryChange` instead of `inputProps.value` and `inputProps.onChange` * to control this input. */ - inputProps?: IInputGroupProps & HTMLInputProps; + inputProps?: IInputGroupProps2; /** * Toggles the visibility of the omnibar. diff --git a/packages/select/src/components/select/select.tsx b/packages/select/src/components/select/select.tsx index 4416f1efe1..6882fa067e 100644 --- a/packages/select/src/components/select/select.tsx +++ b/packages/select/src/components/select/select.tsx @@ -22,8 +22,7 @@ import { Button, DISPLAYNAME_PREFIX, getRef, - HTMLInputProps, - IInputGroupProps, + IInputGroupProps2, InputGroup, IPopoverProps, IRef, @@ -60,7 +59,7 @@ export interface ISelectProps extends IListItemsProps { * `onQueryChange` instead of `inputProps.value` and `inputProps.onChange` * to control this input. */ - inputProps?: IInputGroupProps & HTMLInputProps; + inputProps?: IInputGroupProps2; /** Props to spread to `Popover`. Note that `content` cannot be changed. */ // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/packages/select/src/components/select/suggest.tsx b/packages/select/src/components/select/suggest.tsx index fe4694925b..790f06825f 100644 --- a/packages/select/src/components/select/suggest.tsx +++ b/packages/select/src/components/select/suggest.tsx @@ -21,8 +21,7 @@ import { AbstractPureComponent2, DISPLAYNAME_PREFIX, getRef, - HTMLInputProps, - IInputGroupProps, + IInputGroupProps2, InputGroup, IPopoverProps, IRef, @@ -59,7 +58,7 @@ export interface ISuggestProps extends IListItemsProps { * `query` and `onQueryChange` instead of `inputProps.value` and * `inputProps.onChange`. */ - inputProps?: IInputGroupProps & HTMLInputProps; + inputProps?: IInputGroupProps2; /** Custom renderer to transform an item into a string for the input value. */ inputValueRenderer: (item: T) => string; diff --git a/packages/timezone/src/components/timezone-picker/timezonePicker.tsx b/packages/timezone/src/components/timezone-picker/timezonePicker.tsx index b7fb883a7f..5e323f2783 100644 --- a/packages/timezone/src/components/timezone-picker/timezonePicker.tsx +++ b/packages/timezone/src/components/timezone-picker/timezonePicker.tsx @@ -23,9 +23,8 @@ import { Button, Classes as CoreClasses, DISPLAYNAME_PREFIX, - HTMLInputProps, IButtonProps, - IInputGroupProps, + IInputGroupProps2, IPopoverProps, IProps, MenuItem, @@ -102,7 +101,7 @@ export interface ITimezonePickerProps extends IProps { * If you want to control the filter input, you can pass `value` and `onChange` here * to override `Select`'s own behavior. */ - inputProps?: IInputGroupProps & HTMLInputProps; + inputProps?: IInputGroupProps2; /** Props to spread to `Popover`. Note that `content` cannot be changed. */ popoverProps?: Partial; @@ -146,7 +145,7 @@ export class TimezonePicker extends AbstractPureComponent2", () => { }); it("input can be controlled with input props", () => { - const inputProps: IInputGroupProps = { + const inputProps: IInputGroupProps2 = { disabled: true, leftIcon: "airplane", placeholder: "test placeholder", @@ -206,7 +206,7 @@ describe("", () => { const timezonePicker = shallow(); const inputGroup = findInputGroup(timezonePicker); for (const key of Object.keys(inputProps)) { - assert.deepEqual(inputGroup.prop(key), inputProps[key as keyof IInputGroupProps]); + assert.deepEqual(inputGroup.prop(key), inputProps[key as keyof IInputGroupProps2]); } });