UNPKG

react-cm-ui

Version:
586 lines (568 loc) 17.6 kB
import { isFunction, } from 'lodash'; import ClassNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { UI_CLASS_NAME, BEM_BUTTON, } from '../../global/constants'; import Utils from '../../utils/utils'; import withStyles from '../../styles/withStyles'; import { AsType, ColorType, ButtonType, VariantType, } from './models'; export const propTypes = { as: PropTypes.oneOf(Object.values(AsType)), /** * Primary content. */ children: PropTypes.node.isRequired, /** * Additional classes. */ className: PropTypes.string, /** * Override or extend the styles applied to ButtonDropdown. */ classes: PropTypes.shape({ colorActive: PropTypes.string, colorDefault: PropTypes.string, colorError: PropTypes.string, colorLink: PropTypes.string, colorPrimary: PropTypes.string, colorSecondary: PropTypes.string, colorSuccess: PropTypes.string, colorWarning: PropTypes.string, compact: PropTypes.string, contained: PropTypes.string, disabled: PropTypes.string, fixedWidth: PropTypes.string, fullWidth: PropTypes.string, icon: PropTypes.string, innerContainer: PropTypes.string, inverse: PropTypes.string, outlined: PropTypes.string, pill: PropTypes.string, relax: PropTypes.string, root: PropTypes.string, text: PropTypes.string, transparent: PropTypes.string, }), /** * Color of the button. */ color: PropTypes.oneOf(Object.values(ColorType)), /** * A button can reduce its padding. */ compact: PropTypes.bool, /** * A button can be disabled. */ disabled: PropTypes.bool, /** * The Button will be resized to its parent container's width. */ fullWidth: PropTypes.bool, /** * The URL that the hyperlink points to. */ href: PropTypes.string, /** * If `true`, the button will be a square, housing the icon child. */ icon: PropTypes.bool, /** * Assign the button an id attribute value. */ id: PropTypes.string, /** * Allows for style overrides of the Button's inner container. */ innerStyle: PropTypes.shape({}), /** * A button can be formatted to appear on dark backgrounds better. */ inverse: PropTypes.bool, /** * The onClick event handler. */ onClick: PropTypes.func, /** * A button can be outlined. */ outline: PropTypes.bool, /** * Set a button with a pill like form. */ pill: PropTypes.bool, /** * A button can relax its padding. */ relax: PropTypes.bool, /** * A button can relax its padding. */ style: PropTypes.shape({}), /** * Where to display the linked URL. */ target: PropTypes.oneOf(['_blank']), /** * If `true`, only the button's text is shown. */ text: PropTypes.bool, /** * The title attribute. */ title: PropTypes.string, /** * Set transparent styles. */ transparent: PropTypes.bool, /** * The default behavior of the button. * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type */ type: PropTypes.oneOf(Object.values(ButtonType)), /** * The variant to use. */ variant: PropTypes.oneOf(Object.values(VariantType)), /** * Set a fixed width. */ width: PropTypes.oneOfType([ PropTypes.number, PropTypes.string, ]), }; const defaultProps = { as: AsType.Button, className: undefined, classes: undefined, color: ColorType.Default, compact: false, disabled: false, fullWidth: false, href: undefined, icon: false, id: undefined, innerStyle: {}, inverse: false, onClick: undefined, outline: false, pill: false, relax: false, style: {}, target: undefined, text: false, title: undefined, transparent: false, type: undefined, variant: VariantType.Contained, width: undefined, }; const styles = (theme) => { const { palette, shape, typography, } = theme; const buttonSize = 32; const buttonPillSize = 46; const outlinedWidth = 'inset 0 0 0 1px'; return { contained: {}, root: { backgroundColor: 'transparent', border: 0, borderRadius: shape.borderRadius, color: palette.text.contrastText, cursor: 'pointer', display: 'inline-block', fontSize: typography.fontSize, fontWeight: typography.fontWeightMedium, letterSpacing: '1px', lineHeight: 1, margin: '0 11px 0 0', minHeight: buttonSize, outline: 'none', overflow: 'hidden', padding: '0 22px 1px', textAlign: 'center', textDecoration: 'none', textTransform: 'capitalize', transition: 'background-color 125ms linear, color 125ms linear, opacity 250ms ease-out', verticalAlign: 'top', whiteSpace: 'normal', width: (props) => props.width || '', '&:focus': { boxShadow: `0 0 0 1px ${theme.palette.active.main}`, }, '&:last-child': { marginRight: 0, }, '& .ui.icon:only-child': { margin: 0, }, '&$icon': { height: buttonSize, padding: 0, width: buttonSize, }, '& .icon .icon-use-path': { fill: theme.palette.background.primary, }, }, innerContainer: { alignItems: 'center', display: 'flex', justifyContent: 'center', minHeight: buttonSize, '& .ui.icon': { outline: 'none', pointerEvents: 'none', }, '& span + .ui.icon': { margin: '0 0 0 11px', }, '&$icon': { alignItems: 'center', display: 'inline-flex', height: buttonSize, justifyContent: 'center', width: buttonSize, '& .ui.icon': { float: 'none', fontSize: 16, }, }, }, colorActive: { '&:not($text)': { backgroundColor: theme.palette.active.main, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.active.main}`, '& .icon .icon-use-path': { fill: theme.palette.active.main, }, }, }, '&$text': { color: theme.palette.active.main, '& .icon .icon-use-path': { fill: theme.palette.active.main, }, }, }, colorDefault: { '&:not($text)': { backgroundColor: theme.palette.grey[600], '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.grey[600]}`, '& .icon .icon-use-path': { fill: theme.palette.grey[600], }, '&$inverse': { boxShadow: `${outlinedWidth} ${theme.palette.common.white}`, '& .icon .icon-use-path': { fill: theme.palette.common.white, }, }, }, }, '&$text': { color: theme.palette.text.primary, '&:not($inverse) .icon .icon-use-path': { fill: theme.palette.text.primary, }, '&$inverse .icon .icon-use-path': { fill: theme.palette.text.primary, }, }, }, colorLink: { '&:not($text)': { backgroundColor: theme.palette.text.link, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.text.link}`, '& .icon .icon-use-path': { fill: theme.palette.text.link, }, }, }, '&$text': { color: theme.palette.text.link, '& .icon .icon-use-path': { fill: theme.palette.text.link, }, }, }, colorPrimary: { '&:not($text)': { backgroundColor: theme.palette.primary.main, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.primary.main}`, '& .icon .icon-use-path': { fill: theme.palette.primary.main, }, }, }, '&$text': { color: theme.palette.primary.main, '& .icon .icon-use-path': { fill: theme.palette.primary.main, }, }, }, colorError: { '&:not($text)': { backgroundColor: theme.palette.error.main, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.error.main}`, '& .icon .icon-use-path': { fill: theme.palette.error.main, }, }, }, '&$text': { color: theme.palette.error.main, '& .icon .icon-use-path': { fill: theme.palette.error.main, }, }, }, colorSecondary: { '&:not($text)': { backgroundColor: theme.palette.secondary.main, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.secondary.main}`, '& .icon .icon-use-path': { fill: theme.palette.secondary.main, }, '&$inverse': { boxShadow: `${outlinedWidth} ${theme.palette.common.white}`, '& .icon .icon-use-path': { fill: theme.palette.common.white, }, }, }, }, '&$text': { color: theme.palette.secondary.main, '&:not($inverse) .icon .icon-use-path': { fill: theme.palette.secondary.main, }, '&$inverse .icon .icon-use-path': { fill: theme.palette.text.inverseText, }, }, }, colorSuccess: { '&:not($text)': { backgroundColor: theme.palette.success.main, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.success.main}`, '& .icon .icon-use-path': { fill: theme.palette.success.main, }, }, }, '&$text': { color: theme.palette.success.main, '& .icon .icon-use-path': { fill: theme.palette.success.main, }, }, }, colorWarning: { '&:not($text)': { backgroundColor: theme.palette.warning.main, '&$outlined': { boxShadow: `${outlinedWidth} ${theme.palette.warning.main}`, '& .icon .icon-use-path': { fill: theme.palette.warning.main, }, }, }, '&$text': { color: theme.palette.warning.main, '& .icon .icon-use-path': { fill: theme.palette.warning.main, }, }, }, compact: { paddingLeft: 11, paddingRight: 11, }, fullWidth: { width: '100%', }, icon: {}, fixedWidth: { paddingLeft: 0, paddingRight: 0, }, outlined: { '&:not($text)': { backgroundColor: 'transparent', color: theme.palette.text.primary, }, }, inverse: { '&:not($text)': { color: theme.palette.text.contrastText, }, }, pill: { borderRadius: buttonPillSize / 2, fontWeight: theme.typography.fontWeightBold, minHeight: buttonPillSize, }, relax: { paddingLeft: 33, paddingRight: 33, }, text: { '& .icon .icon-use-path': { fill: theme.palette.text.primary, }, }, transparent: { '&:not($text)': { backgroundColor: theme.palette.hexToRGBA(theme.palette.background.primary, 0.5), boxShadow: `inset 0 0 0 1px ${theme.palette.border.contrastPrimary}`, }, }, disabled: { '&:not($text)': { backgroundColor: theme.palette.action.disabled, color: theme.palette.text.constrastText, }, '&$outlined': { backgroundColor: 'transparent', boxShadow: `${outlinedWidth} ${theme.palette.action.disabled}`, '& .icon .icon-use-path': { fill: theme.palette.action.disabled, }, }, '&$text': { color: theme.palette.text.disable, }, }, }; }; class Button extends React.PureComponent { constructor(props) { super(props); this.onClick = this.onClick.bind(this); this.onMouseDown = this.onMouseDown.bind(this); } onClick(event) { const { disabled, onClick, } = this.props; if (!disabled && isFunction(onClick)) { onClick(event); } } onMouseDown(event) { event.preventDefault(); } render() { const { as, children, classes, className, color, compact, disabled, fullWidth, href, icon, id, innerStyle, inverse, outline, pill, relax, style, text, target, title, transparent, type, variant, width, ...otherProps } = this.props; const ElementType = Utils.getElementType(as, this.props); const rootClasses = ClassNames( UI_CLASS_NAME, BEM_BUTTON, classes.root, className, { [classes.colorActive]: !disabled && color === ColorType.Active, [classes.colorDefault]: !disabled && color === ColorType.Default, [classes.colorError]: !disabled && color === ColorType.Error, [classes.colorLink]: !disabled && color === ColorType.Link, [classes.colorPrimary]: !disabled && color === ColorType.Primary, [classes.colorSecondary]: !disabled && color === ColorType.Secondary, [classes.colorSuccess]: !disabled && color === ColorType.Success, [classes.colorWarning]: !disabled && color === ColorType.Warning, [classes.compact]: compact, [classes.disabled]: disabled, [classes.fixedWidth]: !!width, [classes.fullWidth]: fullWidth, [classes.icon]: icon, [classes.inverse]: inverse, [classes.contained]: variant === VariantType.Contained, [classes.outlined]: variant === VariantType.Outlined, [classes.pill]: pill, [classes.relax]: relax, [classes.text]: variant === VariantType.Text, [classes.transparent]: transparent, }, ); const innerContainerClasses = ClassNames( 'button-inner-container', classes.innerContainer, ); return ( <ElementType // eslint-disable-next-line react/jsx-props-no-spreading {...otherProps} className={rootClasses} disabled={disabled} id={id} href={href} onClick={this.onClick} onMouseDown={this.onMouseDown} style={style} target={target} title={title} type={as === AsType.Button ? type ?? AsType.Button : null} > <span className={innerContainerClasses} style={innerStyle} > {children} </span> </ElementType> ); } } Button.propTypes = propTypes; Button.defaultProps = defaultProps; export default withStyles(styles)(Button);