UNPKG

@atlaskit/button

Version:

A button triggers an event or action. They let users know what will happen next.

141 lines 6.25 kB
/** @jsx jsx */ import React from 'react'; import { jsx } from '@emotion/core'; import memoize from 'memoize-one'; import { createAndFireEvent, withAnalyticsContext, withAnalyticsEvents, } from '@atlaskit/analytics-next'; import GlobalTheme from '@atlaskit/theme/components'; import { Theme } from '../theme'; import { name as packageName, version as packageVersion, } from '../version.json'; import Content from './Content'; import IconWrapper from './IconWrapper'; import InnerWrapper from './InnerWrapper'; import LoadingSpinner from './LoadingSpinner'; import { composeRefs, filterProps, mapAttributesToState } from './utils'; export class Button extends React.Component { constructor() { super(...arguments); // ref can be a range of things because we render button, a, span or other React components this.button = React.createRef(); // Makes sure we don't call ref every render. this.getComposedRefs = memoize(composeRefs); this.state = { isActive: false, isFocus: false, isHover: false, }; this.isInteractive = () => !this.props.isDisabled && !this.props.isLoading; this.onMouseEnter = (e) => { this.setState({ isHover: true }); if (this.props.onMouseEnter) { this.props.onMouseEnter(e); } }; this.onMouseLeave = (e) => { this.setState({ isHover: false, isActive: false }); if (this.props.onMouseLeave) { this.props.onMouseLeave(e); } }; this.onMouseDown = (e) => { e.preventDefault(); this.setState({ isActive: true }); if (this.props.onMouseDown) { this.props.onMouseDown(e); } }; this.onMouseUp = (e) => { this.setState({ isActive: false }); if (this.props.onMouseUp) { this.props.onMouseUp(e); } }; this.onFocus = event => { this.setState({ isFocus: true }); if (this.props.onFocus) { this.props.onFocus(event); } }; this.onBlur = event => { this.setState({ isFocus: false }); if (this.props.onBlur) { this.props.onBlur(event); } }; this.getElement = () => { const { href, isDisabled } = this.props; if (href) { return isDisabled ? 'span' : 'a'; } return 'button'; }; // Swallow click events when the button is disabled // to prevent inner child clicks bubbling up. this.onInnerClick = e => { if (!this.isInteractive()) { e.stopPropagation(); } return true; }; } componentDidMount() { if (this.props.autoFocus && this.button instanceof HTMLButtonElement) { this.button.focus(); } } render() { const { appearance = 'default', children, className, component: CustomComponent, consumerRef, iconAfter, iconBefore, isDisabled = false, isLoading = false, isSelected = false, shouldFitContainer = false, spacing = 'default', theme = (current, props) => current(props), testId, ...rest } = this.props; const attributes = { ...this.state, isSelected, isDisabled }; const StyledButton = CustomComponent || this.getElement(); const iconIsOnlyChild = !!((iconBefore && !iconAfter && !children) || (iconAfter && !iconBefore && !children)); const specifiers = (styles) => { if (StyledButton === 'a') { return { 'a&': styles, }; } else if (StyledButton === CustomComponent) { return { '&, a&, &:hover, &:active, &:focus': styles, }; } return styles; }; return (jsx(Theme.Provider, { value: theme }, jsx(GlobalTheme.Consumer, null, ({ mode }) => (jsx(Theme.Consumer, Object.assign({ mode: mode, state: mapAttributesToState(attributes), iconIsOnlyChild: iconIsOnlyChild }, this.props), ({ buttonStyles, spinnerStyles }) => (jsx(StyledButton, Object.assign({}, filterProps(rest, StyledButton), { "data-testid": testId, ref: this.getComposedRefs(this.button, consumerRef), onMouseEnter: this.onMouseEnter, onMouseLeave: this.onMouseLeave, onMouseDown: this.onMouseDown, onMouseUp: this.onMouseUp, onFocus: this.onFocus, onBlur: this.onBlur, disabled: isDisabled, className: className, css: specifiers(buttonStyles) }), jsx(InnerWrapper, { onClick: this.onInnerClick, fit: !!shouldFitContainer }, isLoading && (jsx(LoadingSpinner, { spacing: spacing, appearance: appearance, isSelected: isSelected, isDisabled: isDisabled, styles: spinnerStyles })), iconBefore && (jsx(IconWrapper, { isLoading: isLoading, spacing: spacing, isOnlyChild: iconIsOnlyChild, icon: iconBefore })), children && (jsx(Content, { isLoading: isLoading, followsIcon: !!iconBefore, spacing: spacing }, children)), iconAfter && (jsx(IconWrapper, { isLoading: isLoading, spacing: spacing, isOnlyChild: iconIsOnlyChild, icon: iconAfter })))))))))); } } Button.defaultProps = { appearance: 'default', autoFocus: false, isDisabled: false, isLoading: false, isSelected: false, shouldFitContainer: false, spacing: 'default', type: 'button', }; const createAndFireEventOnAtlaskit = createAndFireEvent('atlaskit'); const ButtonWithRef = React.forwardRef((props, ref) => jsx(Button, Object.assign({}, props, { consumerRef: ref }))); ButtonWithRef.displayName = 'Button'; export default withAnalyticsContext({ componentName: 'button', packageName, packageVersion, })(withAnalyticsEvents({ onClick: createAndFireEventOnAtlaskit({ action: 'clicked', actionSubject: 'button', attributes: { componentName: 'button', packageName, packageVersion, }, }), })(ButtonWithRef)); //# sourceMappingURL=Button.js.map