@atlaskit/button
Version:
A button triggers an event or action. They let users know what will happen next.
141 lines • 6.25 kB
JavaScript
/** @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