test-react-ui-button
Version:
The test-react-ui-button component provides users a way to trigger actions in the UI.
253 lines (224 loc) • 6.68 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames/bind';
import 'terra-base/lib/baseStyles';
import styles from './Button.module.scss';
const cx = classNames.bind(styles);
const KEYCODES = {
ENTER: 13,
SPACE: 32,
TAB: 9,
};
const ButtonVariants = {
NEUTRAL: 'neutral',
EMPHASIS: 'emphasis',
// TODO: this should be removed on the next major version bump
'DE-EMPHSASIS': 'de-emphasis',
'DE-EMPHASIS': 'de-emphasis',
ACTION: 'action',
UTILITY: 'utility',
};
const ButtonTypes = {
BUTTON: 'button',
SUBMIT: 'submit',
RESET: 'reset',
};
const propTypes = {
/**
* Sets the href. When set will render the component as an anchor tag.
*/
href: PropTypes.string,
/**
* An optional icon. Nested inline with the text when provided.
*/
icon: PropTypes.element,
/**
* Whether or not the button should only display as an icon.
*/
isIconOnly: PropTypes.bool,
/**
* Whether or not the button should display as a block.
*/
isBlock: PropTypes.bool,
/**
* Whether or not the button has reduced padding
*/
isCompact: PropTypes.bool,
/**
* Whether or not the button should be disabled.
*/
isDisabled: PropTypes.bool,
/**
* Reverses the position of the icon and text.
*/
isReversed: PropTypes.bool,
/**
* Callback function triggered when clicked.
*/
onClick: PropTypes.func,
/**
* Callback function triggered when button loses focus.
*/
onBlur: PropTypes.func,
/**
* Callback function triggered when button gains focus.
*/
onFocus: PropTypes.func,
/**
* Callback function triggered when key is pressed.
*/
onKeyDown: PropTypes.func,
/**
* Callback function triggered when key is released.
*/
onKeyUp: PropTypes.func,
/**
* Sets the button text. If the button `isIconOnly` or of variant `Button.Opts.Variants.UTILITY`, this text is set as the aria-label for accessibility.
*/
text: PropTypes.string.isRequired,
/**
* Sets the button type. One of `Button.Opts.Types.BUTTON`, `Button.Opts.Types.SUBMIT`, or `Button.Opts.Types.RESET`.
*/
type: PropTypes.oneOf([ButtonTypes.BUTTON, ButtonTypes.SUBMIT, ButtonTypes.RESET]),
/**
* Sets the button variant. One of `Button.Opts.Variants.NEUTRAL`, `Button.Opts.Variants.EMPHASIS`, `Button.Opts.Variants['DE-EMPHASIS']`, `Button.Opts.Variants.ACTION` or `Button.Opts.Variants.UTILITY`.
*/
variant: PropTypes.oneOf([ButtonVariants.NEUTRAL, ButtonVariants.EMPHASIS, ButtonVariants['DE-EMPHASIS'], ButtonVariants.ACTION, ButtonVariants.UTILITY]),
};
const defaultProps = {
isBlock: false,
isCompact: false,
isDisabled: false,
isIconOnly: false,
isReversed: false,
type: ButtonTypes.BUTTON,
variant: ButtonVariants.NEUTRAL,
};
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { active: false, focused: false, mouseWasClicked: false };
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleOnBlur = this.handleOnBlur.bind(this);
}
handleOnBlur(event) {
this.setState({ focused: false });
if (this.props.onBlur) {
this.props.onBlur(event);
}
}
handleKeyDown(event) {
// Add active state to FF browsers
if (event.nativeEvent.keyCode === KEYCODES.SPACE) {
this.setState({ active: true });
// Follow href on space keydown when rendered as an anchor tag
if (this.props.href) {
// Prevent window scrolling
event.preventDefault();
window.location.href = this.props.href;
}
}
// Add focus styles for keyboard navigation
if (event.nativeEvent.keyCode === KEYCODES.SPACE || event.nativeEvent.keyCode === KEYCODES.ENTER) {
this.setState({ focused: true });
}
if (this.props.onKeyDown) {
this.props.onKeyDown(event);
}
}
handleKeyUp(event) {
// Remove active state from FF broswers
if (event.nativeEvent.keyCode === KEYCODES.SPACE) {
this.setState({ active: false });
}
// Apply focus styles for keyboard navigation
if (event.nativeEvent.keyCode === KEYCODES.TAB) {
this.setState({ focused: true });
}
if (this.props.onKeyUp) {
this.props.onKeyUp(event);
}
}
render() {
const {
icon,
isBlock,
isCompact,
isDisabled,
isIconOnly,
isReversed,
text,
type,
variant,
href,
onClick,
onBlur,
onFocus,
onKeyDown,
onKeyUp,
...customProps
} = this.props;
const buttonClasses = cx([
'button',
variant,
{ 'is-disabled': isDisabled },
{ block: isBlock },
{ compact: isCompact },
{ 'is-active': this.state.active },
{ 'is-focused': this.state.focused },
customProps.className,
]);
const buttonLabelClasses = cx([
{ 'text-and-icon': icon && !isIconOnly },
{ 'icon-only': isIconOnly || variant === 'utility' },
{ 'text-only': !icon },
]);
const buttonTextClasses = cx([
{ 'text-first': icon && isReversed },
]);
const iconClasses = cx([
'icon',
{ 'icon-first': (!isIconOnly && variant !== 'utility') && !isReversed },
]);
const buttonText = !isIconOnly && variant !== 'utility' ? <span className={buttonTextClasses}>{text}</span> : null;
let buttonIcon = null;
if (icon) {
const iconSvgClasses = icon.props.className ? `${icon.props.className} ${cx('icon-svg')}` : cx('icon-svg');
const cloneIcon = React.cloneElement(icon, { className: iconSvgClasses });
buttonIcon = <span className={iconClasses}>{cloneIcon}</span>;
}
const buttonLabel = (
<span className={buttonLabelClasses}>
{isReversed ? buttonText : buttonIcon }
{isReversed ? buttonIcon : buttonText }
</span>
);
const ComponentType = href ? 'a' : 'button';
return (
<ComponentType
{...customProps}
className={buttonClasses}
type={type}
disabled={isDisabled}
tabIndex={isDisabled ? '-1' : undefined}
aria-disabled={isDisabled}
aria-label={isIconOnly || variant === 'utility' ? text : null}
onKeyDown={this.handleKeyDown}
onKeyUp={this.handleKeyUp}
onBlur={this.handleOnBlur}
onClick={onClick}
onFocus={onFocus}
href={href}
>
{buttonLabel}
</ComponentType>
);
}
}
Button.propTypes = propTypes;
Button.defaultProps = defaultProps;
Button.Opts = {};
Button.Opts.Types = ButtonTypes;
Button.Opts.Variants = ButtonVariants;
export default Button;