diff --git a/packages/core/src/common/props.ts b/packages/core/src/common/props.ts index 1a4e6300f2..44267aab71 100644 --- a/packages/core/src/common/props.ts +++ b/packages/core/src/common/props.ts @@ -9,6 +9,12 @@ import * as React from "react"; import { IconName } from "@blueprintjs/icons"; import { Intent } from "./intent"; +/** + * Alias for all valid HTML props for `
` element. + * Does not include React's `ref` or `key`. + */ +export type HTMLDivProps = React.HTMLAttributes; + /** * Alias for all valid HTML props for `` element. * Does not include React's `ref` or `key`. diff --git a/packages/core/src/components/button/abstractButton.tsx b/packages/core/src/components/button/abstractButton.tsx index 269bf7cf71..080c712693 100644 --- a/packages/core/src/components/button/abstractButton.tsx +++ b/packages/core/src/components/button/abstractButton.tsx @@ -56,7 +56,10 @@ export interface IButtonState { isActive: boolean; } -export abstract class AbstractButton extends React.Component & IButtonProps, IButtonState> { +export abstract class AbstractButton> extends React.PureComponent< + IButtonProps & H, + IButtonState +> { public state = { isActive: false, }; diff --git a/packages/core/src/components/button/buttonGroup.tsx b/packages/core/src/components/button/buttonGroup.tsx index 26f3604dcd..e0017731fe 100644 --- a/packages/core/src/components/button/buttonGroup.tsx +++ b/packages/core/src/components/button/buttonGroup.tsx @@ -8,9 +8,9 @@ import classNames from "classnames"; import * as React from "react"; import { Alignment } from "../../common/alignment"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; -export interface IButtonGroupProps extends IProps, React.HTMLProps { +export interface IButtonGroupProps extends IProps, HTMLDivProps { /** * Text alignment of button contents. * This prop only has an effect if buttons are wider than their default widths. diff --git a/packages/core/src/components/button/buttons.tsx b/packages/core/src/components/button/buttons.tsx index 0924c4551b..9584d1a71c 100644 --- a/packages/core/src/components/button/buttons.tsx +++ b/packages/core/src/components/button/buttons.tsx @@ -14,7 +14,7 @@ import { AbstractButton, IButtonProps } from "./abstractButton"; export { IButtonProps }; -export class Button extends AbstractButton { +export class Button extends AbstractButton> { public static displayName = "Blueprint2.Button"; public render() { @@ -26,7 +26,7 @@ export class Button extends AbstractButton { } } -export class AnchorButton extends AbstractButton { +export class AnchorButton extends AbstractButton> { public static displayName = "Blueprint2.AnchorButton"; public render() { diff --git a/packages/core/src/components/callout/callout.tsx b/packages/core/src/components/callout/callout.tsx index 2c6552e9e9..582fde3dd3 100644 --- a/packages/core/src/components/callout/callout.tsx +++ b/packages/core/src/components/callout/callout.tsx @@ -7,12 +7,12 @@ import classNames from "classnames"; import * as React from "react"; -import { Classes, IIntentProps, Intent, IProps } from "../../common"; +import { Classes, HTMLDivProps, IIntentProps, Intent, IProps } from "../../common"; import { Icon } from "../../index"; import { IconName } from "../icon/icon"; /** This component also supports the full range of HTML `
` props. */ -export interface ICalloutProps extends IIntentProps, IProps { +export interface ICalloutProps extends IIntentProps, IProps, HTMLDivProps { /** * Name of a Blueprint UI icon (or an icon element) to render on the left side. * @@ -30,7 +30,7 @@ export interface ICalloutProps extends IIntentProps, IProps { title?: string; } -export class Callout extends React.PureComponent, {}> { +export class Callout extends React.PureComponent { public render() { const { className, children, icon: _nospread, intent, title, ...htmlProps } = this.props; const iconName = this.getIconName(); diff --git a/packages/core/src/components/card/card.tsx b/packages/core/src/components/card/card.tsx index 36f6fa1c21..3ee88922d4 100644 --- a/packages/core/src/components/card/card.tsx +++ b/packages/core/src/components/card/card.tsx @@ -7,9 +7,9 @@ import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; -export interface ICardProps extends IProps { +export interface ICardProps extends IProps, HTMLDivProps { /** * Controls the intensity of the drop shadow beneath the card: the higher * the elevation, the higher the drop shadow. At elevation `0`, no drop @@ -61,20 +61,13 @@ export class Card extends React.PureComponent { }; public render() { - return ( -
- {this.props.children} -
- ); - } - - private getClassName() { - const { elevation, interactive, className } = this.props; - return classNames( + const { className, elevation, interactive, ...htmlProps } = this.props; + const classes = classNames( Classes.CARD, { [Classes.INTERACTIVE]: interactive }, ELEVATION_CLASSES[elevation], className, ); + return
; } } diff --git a/packages/core/src/components/forms/controlGroup.tsx b/packages/core/src/components/forms/controlGroup.tsx index fa884f267e..a929c6d09b 100644 --- a/packages/core/src/components/forms/controlGroup.tsx +++ b/packages/core/src/components/forms/controlGroup.tsx @@ -7,9 +7,9 @@ import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; -export interface IControlGroupProps extends React.AllHTMLAttributes, IProps { +export interface IControlGroupProps extends IProps, HTMLDivProps { /** * Whether the control group should take up the full width of its container. */ diff --git a/packages/core/src/components/forms/fileInput.tsx b/packages/core/src/components/forms/fileInput.tsx index 837757d22c..75dee52e4c 100644 --- a/packages/core/src/components/forms/fileInput.tsx +++ b/packages/core/src/components/forms/fileInput.tsx @@ -10,7 +10,7 @@ import { Utils } from "../../common"; import * as Classes from "../../common/classes"; import { IProps } from "../../common/props"; -export interface IFileInputProps extends React.AllHTMLAttributes, IProps { +export interface IFileInputProps extends React.LabelHTMLAttributes, IProps { /** * Whether the file input is non-interactive. * Setting this to `true` will automatically disable the child input too. @@ -56,7 +56,7 @@ export interface IFileInputProps extends React.AllHTMLAttributes { +export class FileInput extends React.PureComponent { public static displayName = "Blueprint2.FileInput"; public static defaultProps: IFileInputProps = { diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx index 38a124ae62..ad17a74163 100644 --- a/packages/core/src/components/forms/inputGroup.tsx +++ b/packages/core/src/components/forms/inputGroup.tsx @@ -11,6 +11,8 @@ import * as Classes from "../../common/classes"; import { HTMLInputProps, IControlledProps, IIntentProps, IProps, removeNonHTMLProps } from "../../common/props"; import { Icon, IconName } from "../icon/icon"; +// 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 { /** * Whether the input is non-interactive. @@ -48,7 +50,7 @@ export interface IInputGroupState { rightElementWidth?: number; } -export class InputGroup extends React.PureComponent { +export class InputGroup extends React.PureComponent { public static displayName = "Blueprint2.InputGroup"; public state: IInputGroupState = { diff --git a/packages/core/src/components/forms/label.tsx b/packages/core/src/components/forms/label.tsx index 3e862bbd0e..fadac921ad 100644 --- a/packages/core/src/components/forms/label.tsx +++ b/packages/core/src/components/forms/label.tsx @@ -9,7 +9,7 @@ import * as React from "react"; import * as Classes from "../../common/classes"; import { IProps } from "../../common/props"; -export interface ILabelProps extends React.AllHTMLAttributes, IProps { +export interface ILabelProps extends React.LabelHTMLAttributes, IProps { /** * Whether the label is non-interactive. * Be sure to explicitly disable any child controls as well. diff --git a/packages/core/src/components/forms/textArea.tsx b/packages/core/src/components/forms/textArea.tsx index 91968f826e..a62f095392 100644 --- a/packages/core/src/components/forms/textArea.tsx +++ b/packages/core/src/components/forms/textArea.tsx @@ -9,7 +9,7 @@ import * as React from "react"; import * as Classes from "../../common/classes"; import { IIntentProps, IProps } from "../../common/props"; -export interface ITextAreaProps extends React.AllHTMLAttributes, IIntentProps, IProps { +export interface ITextAreaProps extends React.TextareaHTMLAttributes, IIntentProps, IProps { /** * Whether the text area should take up the full width of its container. */ diff --git a/packages/core/src/components/navbar/navbar.tsx b/packages/core/src/components/navbar/navbar.tsx index 70093237f7..307e653663 100644 --- a/packages/core/src/components/navbar/navbar.tsx +++ b/packages/core/src/components/navbar/navbar.tsx @@ -7,7 +7,7 @@ import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; import { NavbarDivider } from "./navbarDivider"; import { NavbarGroup } from "./navbarGroup"; import { NavbarHeading } from "./navbarHeading"; @@ -15,8 +15,7 @@ import { NavbarHeading } from "./navbarHeading"; export { INavbarDividerProps } from "./navbarDivider"; // allow the empty interface so we can label it clearly in the docs -// tslint:disable-next-line:no-empty-interface -export interface INavbarProps extends React.HTMLProps, IProps { +export interface INavbarProps extends IProps, HTMLDivProps { // Empty } diff --git a/packages/core/src/components/navbar/navbarDivider.tsx b/packages/core/src/components/navbar/navbarDivider.tsx index d9872b4fa1..14a45917fa 100644 --- a/packages/core/src/components/navbar/navbarDivider.tsx +++ b/packages/core/src/components/navbar/navbarDivider.tsx @@ -7,11 +7,10 @@ import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; // allow the empty interface so we can label it clearly in the docs -// tslint:disable-next-line:no-empty-interface -export interface INavbarDividerProps extends React.HTMLProps, IProps { +export interface INavbarDividerProps extends IProps, HTMLDivProps { // Empty } diff --git a/packages/core/src/components/navbar/navbarGroup.tsx b/packages/core/src/components/navbar/navbarGroup.tsx index 46f1035ad3..1954e7b291 100644 --- a/packages/core/src/components/navbar/navbarGroup.tsx +++ b/packages/core/src/components/navbar/navbarGroup.tsx @@ -8,9 +8,9 @@ import classNames from "classnames"; import * as React from "react"; import { Alignment } from "../../common/alignment"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; -export interface INavbarGroupProps extends React.HTMLProps, IProps { +export interface INavbarGroupProps extends IProps, HTMLDivProps { /** * The side of the navbar on which the group should appear. * The `Alignment` enum provides constants for these values. diff --git a/packages/core/src/components/navbar/navbarHeading.tsx b/packages/core/src/components/navbar/navbarHeading.tsx index 6f4dbb4315..a19d651324 100644 --- a/packages/core/src/components/navbar/navbarHeading.tsx +++ b/packages/core/src/components/navbar/navbarHeading.tsx @@ -7,17 +7,16 @@ import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; // allow the empty interface so we can label it clearly in the docs -// tslint:disable-next-line:no-empty-interface -export interface INavbarHeadingProps extends React.HTMLProps, IProps { +export interface INavbarHeadingProps extends IProps, HTMLDivProps { // Empty } // this component is simple enough that tests would be purely tautological. /* istanbul ignore next */ -export class NavbarHeading extends React.PureComponent, {}> { +export class NavbarHeading extends React.PureComponent { public static displayName = "Blueprint2.NavbarHeading"; public render() { diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx index 188fdb3101..2e77d161a9 100644 --- a/packages/core/src/components/popover/popover.tsx +++ b/packages/core/src/components/popover/popover.tsx @@ -13,7 +13,7 @@ import { AbstractPureComponent } from "../../common/abstractPureComponent"; import * as Classes from "../../common/classes"; import * as Errors from "../../common/errors"; import { Position } from "../../common/position"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; import * as Utils from "../../common/utils"; import { IOverlayableProps, Overlay } from "../overlay/overlay"; import { Tooltip } from "../tooltip/tooltip"; @@ -406,7 +406,7 @@ export class Popover extends AbstractPureComponent private renderPopper(content: JSX.Element) { const { usePortal, interactionKind, modifiers } = this.props; - const popoverHandlers: React.HTMLAttributes = { + const popoverHandlers: HTMLDivProps = { // always check popover clicks for dismiss class onClick: this.handlePopoverClick, }; diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index c8b876da34..4349d4ea35 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -9,10 +9,10 @@ import * as ReactDOM from "react-dom"; import * as Classes from "../../common/classes"; import * as Errors from "../../common/errors"; -import { IProps } from "../../common/props"; +import { HTMLDivProps, IProps } from "../../common/props"; import { safeInvoke } from "../../common/utils"; -export interface IPortalProps extends IProps, React.HTMLProps { +export interface IPortalProps extends IProps, HTMLDivProps { /** * Callback invoked when the children of this `Portal` have been added to the DOM. */ diff --git a/packages/core/test/card/cardTests.tsx b/packages/core/test/card/cardTests.tsx index 54f713102a..1e6586182f 100644 --- a/packages/core/test/card/cardTests.tsx +++ b/packages/core/test/card/cardTests.tsx @@ -37,4 +37,11 @@ describe("", () => { shallow().simulate("click"); assert.isTrue(onClick.calledOnce); }); + + it("supports HTML props", () => { + const onChange = sinon.spy(); + const card = shallow().find("div"); + assert.strictEqual(card.prop("onChange"), onChange); + assert.strictEqual(card.prop("title"), "foo"); + }); }); diff --git a/packages/datetime/test/dateRangeInputTests.tsx b/packages/datetime/test/dateRangeInputTests.tsx index 4171df7652..bee410dbb1 100644 --- a/packages/datetime/test/dateRangeInputTests.tsx +++ b/packages/datetime/test/dateRangeInputTests.tsx @@ -11,6 +11,7 @@ import * as sinon from "sinon"; import { Classes as CoreClasses, + HTMLDivProps, HTMLInputProps, IInputGroupProps, InputGroup, @@ -26,8 +27,8 @@ import { DATE_FORMAT } from "./common/dateFormat"; import * as DateTestUtils from "./common/dateTestUtils"; type WrappedComponentRoot = ReactWrapper; -type WrappedComponentInput = ReactWrapper, any>; -type WrappedComponentDayElement = ReactWrapper, any>; +type WrappedComponentInput = ReactWrapper; +type WrappedComponentDayElement = ReactWrapper; type OutOfRangeTestFunction = ( inputGetterFn: (root: WrappedComponentRoot) => WrappedComponentInput, diff --git a/packages/docs-app/src/components/clickToCopy.tsx b/packages/docs-app/src/components/clickToCopy.tsx index d0a6635a13..a8a590456a 100644 --- a/packages/docs-app/src/components/clickToCopy.tsx +++ b/packages/docs-app/src/components/clickToCopy.tsx @@ -7,10 +7,10 @@ import classNames from "classnames"; import * as React from "react"; -import { IProps, Keys, removeNonHTMLProps, Utils } from "@blueprintjs/core"; +import { HTMLDivProps, IProps, Keys, removeNonHTMLProps, Utils } from "@blueprintjs/core"; import { createKeyEventHandler } from "@blueprintjs/docs-theme"; -export interface IClickToCopyProps extends IProps, React.HTMLProps { +export interface IClickToCopyProps extends IProps, HTMLDivProps { /** * Additional class names to apply after value has been copied * @default "docs-clipboard-copied"