|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- 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<string | number> = {
- 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)
-
- 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<Variant, React.CSSProperties> = {
- outline: defaultVariantStyleSet,
- primary: {
- backgroundColor: 'var(--color-accent, blue)',
- color: 'var(--color-bg, white)',
- },
- }
-
- const propTypes = {
- /**
- * Size of the component.
- */
- size: PropTypes.oneOf<Size>(['small', 'medium', 'large']),
- /**
- * Variant of the component.
- */
- variant: PropTypes.oneOf<Variant>(['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<ButtonElement>(['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<ButtonType>(['submit', 'reset', 'button']),
- /**
- * Does the button display a border?
- */
- border: PropTypes.bool,
- }
-
- type Props = PropTypes.InferProps<typeof propTypes>
-
- const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement, Props>(
- (
- {
- size = 'medium',
- variant = 'outline',
- disabled = false,
- children,
- element = 'button',
- href,
- target,
- rel,
- type = 'button',
- border = false,
- },
- ref,
- ) => {
- const { [variant as Variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets
- const commonButtonStyles: React.CSSProperties = {
- ...theVariantStyleSet,
- minHeight: MIN_HEIGHTS[size!],
- }
- const buttonContent = (
- <React.Fragment>
- {
- border
- && (
- <Border />
- )
- }
- {stringify(children)}
- </React.Fragment>
- )
-
- switch (element) {
- case 'button':
- return (
- <Base type={type!} ref={ref as React.Ref<HTMLButtonElement>} disabled={disabled!} style={commonButtonStyles}>
- {buttonContent}
- </Base>
- )
- case 'a':
- if (disabled) {
- return (
- <DisabledLinkBase ref={ref as React.Ref<HTMLSpanElement>} style={commonButtonStyles}>
- {buttonContent}
- </DisabledLinkBase>
- )
- }
- return (
- <LinkBase
- href={href!}
- target={target!}
- rel={rel!}
- ref={ref as React.Ref<HTMLAnchorElement>}
- style={commonButtonStyles}
- >
- {buttonContent}
- </LinkBase>
- )
- default:
- break
- }
-
- return null
- },
- )
-
- Button.propTypes = propTypes
-
- Button.displayName = 'Button'
-
- export default Button
|