import * as React from 'react' import * as PropTypes from 'prop-types' import styled, { CSSObject } from 'styled-components' import { Size, SizeMap } from '../../services/utilities' import stringify from '../../services/stringify' export type Variant = 'outline' | 'primary' export type ButtonElement = 'a' | 'button' type ButtonType = 'submit' | 'reset' | 'button' const MIN_HEIGHTS: SizeMap = { small: '2.5rem', medium: '3rem', large: '4rem', } const disabledButtonStyles: CSSObject = { opacity: 0.5, cursor: 'not-allowed', } const buttonStyles: CSSObject = { display: 'grid', appearance: 'none', padding: '0 1rem', font: 'inherit', fontFamily: 'var(--font-family-base, sans-serif)', textTransform: 'uppercase', fontWeight: 'bolder', borderRadius: '0.25rem', placeContent: 'center', position: 'relative', cursor: 'pointer', border: 0, userSelect: 'none', textDecoration: 'none', transitionProperty: 'background-color, color', whiteSpace: 'nowrap', lineHeight: 1, ':focus': { '--color-accent': 'var(--color-active, Highlight)', outline: 0, }, ':disabled': disabledButtonStyles, '::-moz-focus-inner': { outline: 0, border: 0, }, } const disabledLinkButtonStyles: CSSObject = { ...buttonStyles, ...disabledButtonStyles, } const Base = styled('button')({ ...buttonStyles, width: '100%', }) Base.displayName = 'button' const LinkBase = styled('a')(buttonStyles) LinkBase.displayName = 'a' const DisabledLinkBase = styled('span')(disabledLinkButtonStyles) DisabledLinkBase.displayName = 'span' const Border = styled('span')({ borderColor: 'var(--color-accent, blue)', boxSizing: 'border-box', display: 'inline-block', borderWidth: '0.125rem', borderStyle: 'solid', position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', borderRadius: 'inherit', pointerEvents: 'none', transitionProperty: 'border-color', '::before': { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', content: "''", borderRadius: '0.125rem', opacity: 0.5, pointerEvents: 'none', }, [`${Base}:focus &::before`]: { boxShadow: '0 0 0 0.375rem var(--color-accent, blue)', }, }) Border.displayName = 'span' const defaultVariantStyleSet: React.CSSProperties = { backgroundColor: 'transparent', color: 'var(--color-accent, blue)', } const variantStyleSets: Record = { outline: defaultVariantStyleSet, primary: { backgroundColor: 'var(--color-accent, blue)', color: 'var(--color-bg, white)', }, } const propTypes = { /** * Size of the component. */ size: PropTypes.oneOf(['small', 'medium', 'large']), /** * Variant of the component. */ variant: PropTypes.oneOf(['outline', 'primary']), /** * Text to identify the action associated upon activation of the component. */ children: PropTypes.any, /** * Can the component be activated? */ disabled: PropTypes.bool, /** * The corresponding HTML element of the component. */ element: PropTypes.oneOf(['a', 'button']), /** * The URL of the page to navigate to, if element is set to "a". */ href: PropTypes.string, /** * The target on where to display the page navigated to, if element is set to "a". */ target: PropTypes.string, /** * The relationship of the current page to the referred page in "href", if element is set to "a". */ rel: PropTypes.string, /** * The type of the button, if element is set to "button". */ type: PropTypes.oneOf(['submit', 'reset', 'button']), /** * Does the button display a border? */ border: PropTypes.bool, /** * Event handler triggered when the component is clicked. */ onClick: PropTypes.func, /** * Event handler triggered when the component receives focus. */ onFocus: PropTypes.func, /** * Event handler triggered when the component loses focus. */ onBlur: PropTypes.func, } type Props = PropTypes.InferProps const Button = React.forwardRef( ( { size = 'medium', variant = 'outline', disabled = false, children, element = 'button', href, target, rel, type = 'button', border = false, onClick, onFocus, onBlur, }, ref, ) => { const { [variant as Variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets const commonButtonStyles: React.CSSProperties = { ...theVariantStyleSet, minHeight: MIN_HEIGHTS[size!], } const buttonContent = ( {border && } {stringify(children)} ) switch (element) { case 'button': return ( } type={type!} ref={ref as React.Ref} disabled={disabled!} style={commonButtonStyles} onFocus={onFocus as React.FocusEventHandler} onBlur={onBlur as React.FocusEventHandler} > {buttonContent} ) case 'a': if (disabled) { return ( } style={commonButtonStyles}> {buttonContent} ) } return ( } href={href!} target={target!} rel={rel!} ref={ref as React.Ref} style={commonButtonStyles} onFocus={onFocus as React.FocusEventHandler} onBlur={onBlur as React.FocusEventHandler} > {buttonContent} ) default: break } return null }, ) Button.propTypes = propTypes Button.displayName = 'Button' export default Button