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

276 lines
7.5 KiB
JavaScript
Raw Normal View History

import React from 'react'
import PropTypes from 'prop-types'
2020-02-19 23:57:07 +00:00
import { NavLink } from 'react-router-dom'
import { CX } from '../constants'
2020-02-21 00:57:29 +00:00
import Icon from './icon'
import Tooltip from './tooltip'
2020-02-19 23:57:07 +00:00
2020-04-23 07:13:29 +01:00
// Define colors for enumeration for Button component `color`, `backgroundColor` props
2020-02-19 23:57:07 +00:00
const COLORS = {
primary: 'primary',
secondary: 'secondary',
tertiary: 'tertiary',
2020-02-21 00:57:29 +00:00
white: 'white',
2020-03-26 03:11:32 +00:00
black: 'black',
2020-02-19 23:57:07 +00:00
brand: 'brand',
2020-03-26 03:11:32 +00:00
danger: 'danger',
2020-02-19 23:57:07 +00:00
none: 'none',
}
2020-04-23 07:13:29 +01:00
/**
* 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
2020-04-29 23:32:49 +01:00
* @param {bool} [props.rel] - rel for the button
* @param {bool} [props.target] - target for the button
2020-04-23 07:13:29 +01:00
* @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
2020-04-23 07:13:29 +01:00
* @param {bool} [props.type] - `type` attribute for button
* @param {bool} [props.underlineOnHover] - if the button has underline on hover
*/
class Button extends React.PureComponent {
2020-02-19 23:57:07 +00:00
state = {
isHovering: false,
}
2020-02-19 23:57:07 +00:00
handleClick = (e) => {
2020-04-23 07:13:29 +01:00
if (!this.props.isDisabled && this.props.onClick) {
2020-02-19 23:57:07 +00:00
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 })
}
}
2020-02-19 23:57:07 +00:00
setRef = (c) => {
2020-04-23 07:13:29 +01:00
try {
this.node = c
this.props.buttonRef(c)
} catch (error) {
//
}
2020-02-19 23:57:07 +00:00
}
focus() {
this.node.focus()
}
2020-02-22 23:26:23 +00:00
render() {
2020-02-21 00:57:29 +00:00
const {
2020-04-23 07:13:29 +01:00
backgroundColor,
children,
2020-02-21 00:57:29 +00:00
className,
2020-04-23 07:13:29 +01:00
color,
href,
2020-02-21 00:57:29 +00:00
icon,
iconClassName,
2020-04-23 07:13:29 +01:00
iconSize,
isBlock,
isDisabled,
isNarrow,
isOutline,
isText,
2020-03-14 17:31:29 +00:00
noClasses,
2020-04-23 07:13:29 +01:00
onClick,
onMouseEnter,
onMouseLeave,
radiusSmall,
2020-04-29 23:32:49 +01:00
rel,
target,
2020-04-23 07:13:29 +01:00
title,
to,
tooltip,
2020-04-23 07:13:29 +01:00
type,
underlineOnHover,
2020-02-21 00:57:29 +00:00
} = this.props
const { isHovering } = this.state
2020-02-21 00:57:29 +00:00
2020-04-23 07:13:29 +01:00
// Style the component according to props
const classes = noClasses ? className : CX(className, {
d: 1,
2020-02-19 23:57:07 +00:00
noUnderline: 1,
font: 1,
cursorPointer: 1,
textAlignCenter: 1,
2020-03-06 15:38:22 +00:00
outlineNone: 1,
2020-05-03 06:22:49 +01:00
// outlineOnFocus: !isText,
2020-03-07 04:53:28 +00:00
flexRow: !!children && !!icon,
2020-04-23 07:13:29 +01:00
cursorNotAllowed: isDisabled,
opacity05: isDisabled,
2020-02-19 23:57:07 +00:00
bgPrimary: backgroundColor === COLORS.primary,
bgWhite: backgroundColor === COLORS.white,
2020-04-29 23:32:49 +01:00
bgBlack: backgroundColor === COLORS.black,
bgBrand: backgroundColor === COLORS.brand,
bgTransparent: backgroundColor === COLORS.none,
bgSecondary: backgroundColor === COLORS.tertiary,
bgSubtle: backgroundColor === COLORS.secondary,
bgDanger: backgroundColor === COLORS.danger,
2020-03-26 03:11:32 +00:00
cPrimary: color === COLORS.primary,
cSecondary: color === COLORS.secondary,
cTertiary: color === COLORS.tertiary,
cWhite: color === COLORS.white,
cBrand: color === COLORS.brand,
2020-02-19 23:57:07 +00:00
2020-04-23 07:13:29 +01:00
borderColorBrand: color === COLORS.brand && isOutline,
border1PX: isOutline,
2020-02-19 23:57:07 +00:00
2020-04-23 07:13:29 +01:00
circle: !isText,
2020-02-22 23:26:23 +00:00
radiusSmall: radiusSmall,
2020-02-19 23:57:07 +00:00
2020-04-23 07:13:29 +01:00
py5: isNarrow,
py10: !isText && !isNarrow,
px15: !isText,
2020-02-19 23:57:07 +00:00
w100PC: isBlock,
2020-02-21 00:57:29 +00:00
underline_onHover: underlineOnHover,
2020-04-29 23:32:49 +01:00
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,
2020-02-21 00:57:29 +00:00
2020-04-29 23:32:49 +01:00
bgBrand_onHover: color === COLORS.brand && isOutline && !isDisabled,
cWhite_onHover: color === COLORS.brand && isOutline && !isDisabled,
2020-02-19 23:57:07 +00:00
})
const tagName = !!href ? 'a' : !!to ? 'NavLink' : 'button'
2020-04-23 07:13:29 +01:00
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>
2020-02-21 00:57:29 +00:00
{theIcon}
{children}
{theTooltip}
</React.Fragment>
2020-02-21 00:57:29 +00:00
) : children
2020-04-23 07:13:29 +01:00
const handlers = {
onClick: !!onClick ? this.handleClick : undefined,
onMouseEnter: !!onMouseEnter || !!tooltip ? this.handleOnMouseEnter : undefined,
onMouseLeave: !!onMouseLeave || !!tooltip ? this.handleOnMouseLeave : undefined,
2020-02-22 23:26:23 +00:00
}
if (tagName === 'NavLink' && !!to) {
return (
2020-04-23 07:13:29 +01:00
<NavLink
title={title}
className={classes}
disabled={isDisabled}
to={to}
{...handlers}
>
2020-02-22 23:26:23 +00:00
{theChildren}
</NavLink>
)
}
2020-05-03 06:22:49 +01:00
const isLogout = href === '/auth/sign_out'
const dataMethod = isLogout ? 'delete' : undefined
2020-04-23 07:13:29 +01:00
const options = {
2020-04-29 23:32:49 +01:00
rel,
target,
2020-04-23 07:13:29 +01:00
title,
'aria-label': title,
2020-04-23 07:13:29 +01:00
type,
disabled: isDisabled,
className: classes,
href: href || undefined,
ref: this.setRef,
2020-05-03 06:22:49 +01:00
'data-method': dataMethod,
2020-04-23 07:13:29 +01:00
...handlers,
}
2020-02-22 23:26:23 +00:00
return React.createElement(tagName, options, theChildren)
2020-02-19 23:57:07 +00:00
}
}
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