gab-social/app/javascript/gabsocial/components/button.js

276 lines
7.5 KiB
JavaScript

import React from 'react'
import PropTypes from 'prop-types'
import { NavLink } from 'react-router-dom'
import { CX } from '../constants'
import Icon from './icon'
import Tooltip from './tooltip'
// Define colors for enumeration for Button component `color`, `backgroundColor` props
const COLORS = {
primary: 'primary',
secondary: 'secondary',
tertiary: 'tertiary',
white: 'white',
black: 'black',
brand: 'brand',
danger: 'danger',
none: 'none',
}
/**
* Renders a button component
* @param {string} [props.backgroundColor='brand'] - background color of the button
* @param {func|node} [props.buttonRef] - ref to send to button component
* @param {string} [props.className] - add custom className
* @param {string} [props.color='white'] - text color of the button
* @param {string} [props.href] - href to send to on click
* @param {string} [props.icon] - prepend icon id
* @param {string} [props.iconClassName] - add custom className to icon
* @param {string} [props.iconSize] - size of the icon
* @param {bool} [props.isBlock] - if button is width: 100%
* @param {bool} [props.isDisabled] - if the button is disabled
* @param {bool} [props.isNarrow] - if the button is narrow
* @param {bool} [props.isOutline] - if the button is outline design
* @param {bool} [props.noClasses] - if the button has no default classes
* @param {func} [props.onClick] - function to call on button click
* @param {func} [props.onMouseEnter] - function to call on button mouse enter
* @param {func} [props.onMouseLeave] - function to call on button mouse leave
* @param {bool} [props.radiusSmall] - if the button has small radius
* @param {bool} [props.rel] - rel for the button
* @param {bool} [props.target] - target for the button
* @param {bool} [props.text] - if the button is just text (i.e. link)
* @param {bool} [props.title] - `title` attribute for button
* @param {bool} [props.to] - `to` to send to on click
* @param {bool} [props.tooltip] - add a tooltip to the button on hover/click
* @param {bool} [props.type] - `type` attribute for button
* @param {bool} [props.underlineOnHover] - if the button has underline on hover
*/
class Button extends React.PureComponent {
state = {
isHovering: false,
}
handleClick = (e) => {
if (!this.props.isDisabled && this.props.onClick) {
this.props.onClick(e)
}
}
handleOnMouseEnter = () => {
if (!this.props.isDisabled && this.props.onMouseEnter) {
this.props.onMouseEnter()
}
if (this.props.tooltip) {
this.setState({ isHovering: true })
}
}
handleOnMouseLeave = () => {
if (!this.props.isDisabled && this.props.onMouseLeave) {
this.props.onMouseLeave()
}
if (this.props.tooltip) {
this.setState({ isHovering: false })
}
}
setRef = (c) => {
try {
this.node = c
this.props.buttonRef(c)
} catch (error) {
//
}
}
focus() {
this.node.focus()
}
render() {
const {
backgroundColor,
children,
className,
color,
href,
icon,
iconClassName,
iconSize,
isBlock,
isDisabled,
isNarrow,
isOutline,
isText,
noClasses,
onClick,
onMouseEnter,
onMouseLeave,
radiusSmall,
rel,
target,
title,
to,
tooltip,
type,
underlineOnHover,
} = this.props
const { isHovering } = this.state
// Style the component according to props
const classes = noClasses ? className : CX(className, {
d: 1,
noUnderline: 1,
font: 1,
cursorPointer: 1,
textAlignCenter: 1,
outlineNone: 1,
// outlineOnFocus: !isText,
flexRow: !!children && !!icon,
cursorNotAllowed: isDisabled,
opacity05: isDisabled,
bgPrimary: backgroundColor === COLORS.primary,
bgWhite: backgroundColor === COLORS.white,
bgBlack: backgroundColor === COLORS.black,
bgBrand: backgroundColor === COLORS.brand,
bgTransparent: backgroundColor === COLORS.none,
bgSecondary: backgroundColor === COLORS.tertiary,
bgSubtle: backgroundColor === COLORS.secondary,
bgDanger: backgroundColor === COLORS.danger,
cPrimary: color === COLORS.primary,
cSecondary: color === COLORS.secondary,
cTertiary: color === COLORS.tertiary,
cWhite: color === COLORS.white,
cBrand: color === COLORS.brand,
borderColorBrand: color === COLORS.brand && isOutline,
border1PX: isOutline,
circle: !isText,
radiusSmall: radiusSmall,
py5: isNarrow,
py10: !isText && !isNarrow,
px15: !isText,
w100PC: isBlock,
underline_onHover: underlineOnHover,
bgSecondaryDark_onHover: backgroundColor === COLORS.tertiary || backgroundColor === COLORS.secondary && !isDisabled,
bgBlackOpaque_onHover: backgroundColor === COLORS.black && !isDisabled,
bgBrandDark_onHover: backgroundColor === COLORS.brand && !isDisabled,
bgDangerDark_onHover: backgroundColor === COLORS.danger && !isDisabled,
bgBrand_onHover: color === COLORS.brand && isOutline && !isDisabled,
cWhite_onHover: color === COLORS.brand && isOutline && !isDisabled,
})
const tagName = !!href ? 'a' : !!to ? 'NavLink' : 'button'
const theIcon = !!icon ? (
<Icon
id={icon}
size={iconSize}
className={iconClassName}
/>
) : undefined
const theTooltip = !!tooltip && isHovering ? (
<Tooltip
message={tooltip}
targetRef={this.node}
/>
) : undefined
const theChildren = !!icon || !!tooltip ? (
<React.Fragment>
{theIcon}
{children}
{theTooltip}
</React.Fragment>
) : children
const handlers = {
onClick: !!onClick ? this.handleClick : undefined,
onMouseEnter: !!onMouseEnter || !!tooltip ? this.handleOnMouseEnter : undefined,
onMouseLeave: !!onMouseLeave || !!tooltip ? this.handleOnMouseLeave : undefined,
}
if (tagName === 'NavLink' && !!to) {
return (
<NavLink
title={title}
className={classes}
disabled={isDisabled}
to={to}
{...handlers}
>
{theChildren}
</NavLink>
)
}
const isLogout = href === '/auth/sign_out'
const dataMethod = isLogout ? 'delete' : undefined
const options = {
rel,
target,
title,
'aria-label': title,
type,
disabled: isDisabled,
className: classes,
href: href || undefined,
ref: this.setRef,
'data-method': dataMethod,
...handlers,
}
return React.createElement(tagName, options, theChildren)
}
}
Button.propTypes = {
backgroundColor: PropTypes.string,
buttonRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.node,
]),
children: PropTypes.node,
className: PropTypes.string,
color: PropTypes.string,
href: PropTypes.string,
icon: PropTypes.string,
iconClassName: PropTypes.string,
iconSize: PropTypes.string,
isBlock: PropTypes.bool,
isDisabled: PropTypes.bool,
isNarrow: PropTypes.bool,
isText: PropTypes.bool,
noClasses: PropTypes.bool,
onClick: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
isOutline: PropTypes.bool,
radiusSmall: PropTypes.bool,
rel: PropTypes.string,
target: PropTypes.string,
title: PropTypes.string,
to: PropTypes.string,
type: PropTypes.string,
underlineOnHover: PropTypes.bool,
}
Button.defaultProps = {
color: COLORS.white,
backgroundColor: COLORS.brand,
}
export default Button