From 6a410c5129dee74ab4567d5caf1a7c305e9f61e3 Mon Sep 17 00:00:00 2001 From: Benjamin Lee Date: Fri, 13 Jan 2017 10:54:05 -0800 Subject: [PATCH] Consistent space and enter keystrokes between buttons and anchor buttons (#459) * have consistent actions with space and enter between buttons and anchorbuttons * create `AbstractButton` class that implementations extend so code is easily shared. --- .../src/components/button/abstractButton.tsx | 61 +++++++++++++++++++ .../core/src/components/button/buttons.tsx | 39 +++++------- packages/core/test/buttons/buttonTests.tsx | 14 +++++ 3 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 packages/core/src/components/button/abstractButton.tsx diff --git a/packages/core/src/components/button/abstractButton.tsx b/packages/core/src/components/button/abstractButton.tsx new file mode 100644 index 0000000000..65cb256aff --- /dev/null +++ b/packages/core/src/components/button/abstractButton.tsx @@ -0,0 +1,61 @@ +import * as React from "react"; + +import * as Keys from "../../common/keys"; +import { IActionProps } from "../../common/props"; +import { safeInvoke } from "../../common/utils"; + +export interface IButtonProps extends IActionProps { + /** A ref handler that receives the native HTML element backing this component. */ + elementRef?: (ref: HTMLElement) => any; + + /** Name of icon (the part after `pt-icon-`) to add to button. */ + rightIconName?: string; + + /** + * If set to true, the button will display a centered loading spinner instead of its contents. + * The width of the button is not affected by the value of this prop. + * @default false + */ + loading?: boolean; +} + +export interface IButtonState { + isActive: boolean; +} + +export abstract class AbstractButton extends React.Component & IButtonProps, IButtonState> { + public state = { + isActive: false, + }; + + protected buttonRef: HTMLElement; + protected refHandlers = { + button: (ref: HTMLElement) => { + this.buttonRef = ref; + safeInvoke(this.props.elementRef, ref); + }, + }; + + public abstract render(): JSX.Element; + + protected onKeyDown = (e: React.KeyboardEvent) => { + switch (e.which) { + case Keys.SPACE: + e.preventDefault(); + this.setState({ isActive: true }); + break; + case Keys.ENTER: + this.buttonRef.click(); + break; + default: + break; + } + } + + protected onKeyUp = (e: React.KeyboardEvent) => { + if (e.which === Keys.SPACE) { + this.setState({ isActive: false }); + this.buttonRef.click(); + } + } +} diff --git a/packages/core/src/components/button/buttons.tsx b/packages/core/src/components/button/buttons.tsx index ce470bd5c1..54eaa57de7 100644 --- a/packages/core/src/components/button/buttons.tsx +++ b/packages/core/src/components/button/buttons.tsx @@ -12,38 +12,26 @@ import * as classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; -import { IActionProps, removeNonHTMLProps } from "../../common/props"; +import { removeNonHTMLProps } from "../../common/props"; import { Spinner } from "../spinner/spinner"; +import { AbstractButton, IButtonProps } from "./abstractButton"; -export interface IButtonProps extends IActionProps { - /** A ref handler that receives the native HTML element backing this component. */ - elementRef?: (ref: HTMLElement) => any; - - /** Name of icon (the part after `pt-icon-`) to add to button. */ - rightIconName?: string; - - /** - * If set to true, the button will display a centered loading spinner instead of its contents. - * The width of the button is not affected by the value of this prop. - * @default false - */ - loading?: boolean; -} - -export class Button extends React.Component & IButtonProps, {}> { +export class Button extends AbstractButton { public static displayName = "Blueprint.Button"; public render() { - const { children, elementRef, loading, onClick, rightIconName, text } = this.props; + const { children, loading, onClick, rightIconName, text } = this.props; const disabled = isButtonDisabled(this.props); return (