fbredius/storybook

View on GitHub
lib/components/src/Button/Button.tsx

Summary

Maintainability
D
1 day
Test Coverage
import React, { forwardRef, FunctionComponent, ComponentProps, ReactNode } from 'react';
import { styled } from '@storybook/theming';
import { darken, lighten, rgba, transparentize } from 'polished';

export interface ButtonProps {
  isLink?: boolean;
  primary?: boolean;
  secondary?: boolean;
  tertiary?: boolean;
  gray?: boolean;
  inForm?: boolean;
  disabled?: boolean;
  small?: boolean;
  outline?: boolean;
  containsIcon?: boolean;
  children?: ReactNode;
  href?: string;
}

type ButtonWrapperProps = ButtonProps;

const ButtonWrapper = styled.button<ButtonWrapperProps>(
  ({ small, theme }) => ({
    border: 0,
    borderRadius: '3em',
    cursor: 'pointer',
    display: 'inline-block',
    overflow: 'hidden',
    padding: small ? '8px 16px' : '13px 20px',
    position: 'relative',
    textAlign: 'center',
    textDecoration: 'none',
    transition: 'all 150ms ease-out',
    transform: 'translate3d(0,0,0)',
    verticalAlign: 'top',
    whiteSpace: 'nowrap',
    userSelect: 'none',
    opacity: 1,
    margin: 0,
    background: 'transparent',

    fontSize: `${small ? theme.typography.size.s1 : theme.typography.size.s2 - 1}px`,
    fontWeight: theme.typography.weight.bold,
    lineHeight: '1',

    svg: {
      display: 'inline-block',
      height: small ? 14 : 16,
      width: small ? 14 : 16,

      verticalAlign: 'top',
      marginRight: small ? 4 : 6,
      marginTop: small ? -1 : -2,
      marginBottom: small ? -1 : -2,

      /* Necessary for js mouse events to not glitch out when hovering on svgs */
      pointerEvents: 'none',

      path: {
        fill: 'currentColor',
      },
    },
  }),
  ({ disabled }) =>
    disabled
      ? {
          cursor: 'not-allowed !important',
          opacity: 0.5,
          '&:hover': {
            transform: 'none',
          },
        }
      : {},
  ({ containsIcon, small }) =>
    containsIcon
      ? {
          svg: {
            display: 'block',
            margin: 0,
          },
          ...(small ? { padding: 9 } : { padding: 12 }),
        }
      : {},
  ({ theme, primary, secondary, gray }) => {
    let color;

    if (gray) {
      color = theme.color.medium;
    } else if (secondary) {
      color = theme.color.secondary;
    } else if (primary) {
      color = theme.color.primary;
    }

    return color
      ? {
          background: color,
          color: gray ? theme.color.darkest : theme.color.lightest,

          '&:hover': {
            background: darken(0.05, color),
          },
          '&:active': {
            boxShadow: 'rgba(0, 0, 0, 0.1) 0 0 0 3em inset',
          },
          '&:focus': {
            boxShadow: `${rgba(color, 1)} 0 1px 9px 2px`,
            outline: 'none',
          },
          '&:focus:hover': {
            boxShadow: `${rgba(color, 0.2)} 0 8px 18px 0px`,
          },
        }
      : {};
  },
  ({ theme, tertiary, inForm, small }) =>
    tertiary
      ? {
          background:
            theme.base === 'light'
              ? darken(0.02, theme.input.background)
              : lighten(0.02, theme.input.background),
          color: theme.input.color,
          boxShadow: `${theme.input.border} 0 0 0 1px inset`,
          borderRadius: theme.input.borderRadius,

          ...(inForm && small ? { padding: '10px 16px' } : {}),

          '&:hover': {
            background:
              theme.base === 'light'
                ? darken(0.05, theme.input.background)
                : lighten(0.05, theme.input.background),
            ...(inForm
              ? {}
              : {
                  boxShadow: 'rgba(0,0,0,.2) 0 2px 6px 0, rgba(0,0,0,.1) 0 0 0 1px inset',
                }),
          },
          '&:active': {
            background: theme.input.background,
          },
          '&:focus': {
            boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`,
            outline: 'none',
          },
        }
      : {},
  ({ theme, outline }) =>
    outline
      ? {
          boxShadow: `${transparentize(0.8, theme.color.defaultText)} 0 0 0 1px inset`,
          color: transparentize(0.3, theme.color.defaultText),
          background: 'transparent',

          '&:hover, &:focus': {
            boxShadow: `${transparentize(0.5, theme.color.defaultText)} 0 0 0 1px inset`,
            outline: 'none',
          },

          '&:active': {
            boxShadow: `${transparentize(0.5, theme.color.defaultText)} 0 0 0 2px inset`,
            color: transparentize(0, theme.color.defaultText),
          },
        }
      : {},
  ({ theme, outline, primary }) => {
    const color = theme.color.primary;

    return outline && primary
      ? {
          boxShadow: `${color} 0 0 0 1px inset`,
          color,

          'svg path': {
            fill: color,
          },

          '&:hover': {
            boxShadow: `${color} 0 0 0 1px inset`,
            background: 'transparent',
          },

          '&:active': {
            background: color,
            boxShadow: `${color} 0 0 0 1px inset`,
            color: theme.color.tertiary,
          },
          '&:focus': {
            boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.4)} 0 1px 9px 2px`,
            outline: 'none',
          },
          '&:focus:hover': {
            boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.2)} 0 8px 18px 0px`,
          },
        }
      : {};
  },
  ({ theme, outline, primary, secondary }) => {
    let color;

    if (secondary) {
      color = theme.color.secondary;
    } else if (primary) {
      color = theme.color.primary;
    }

    return outline && color
      ? {
          boxShadow: `${color} 0 0 0 1px inset`,
          color,

          'svg path': {
            fill: color,
          },

          '&:hover': {
            boxShadow: `${color} 0 0 0 1px inset`,
            background: 'transparent',
          },

          '&:active': {
            background: color,
            boxShadow: `${color} 0 0 0 1px inset`,
            color: theme.color.tertiary,
          },
          '&:focus': {
            boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.4)} 0 1px 9px 2px`,
            outline: 'none',
          },
          '&:focus:hover': {
            boxShadow: `${color} 0 0 0 1px inset, ${rgba(color, 0.2)} 0 8px 18px 0px`,
          },
        }
      : {};
  }
);

const ButtonLink = ButtonWrapper.withComponent('a');

export const Button: FunctionComponent<ComponentProps<typeof ButtonWrapper>> = Object.assign(
  forwardRef<any, ButtonProps>(({ isLink, children, ...props }, ref) => {
    if (isLink) {
      return (
        <ButtonLink {...props} ref={ref}>
          {children}
        </ButtonLink>
      );
    }
    return (
      <ButtonWrapper {...props} ref={ref}>
        {children}
      </ButtonWrapper>
    );
  }),
  {
    defaultProps: {
      isLink: false,
    },
  }
);