Skip to content

Instantly share code, notes, and snippets.

@lucax88x
Created November 10, 2020 09:04
Show Gist options
  • Select an option

  • Save lucax88x/96dea5a8baeff69b8f2481464d675519 to your computer and use it in GitHub Desktop.

Select an option

Save lucax88x/96dea5a8baeff69b8f2481464d675519 to your computer and use it in GitHub Desktop.

Revisions

  1. lucax88x created this gist Nov 10, 2020.
    152 changes: 152 additions & 0 deletions ripplebutton.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    /* eslint-disable react/button-has-type */
    import { useStyledTheme } from '@pfb/hooks/useTheme';
    import { Theme } from '@pfb/models/theme.model';
    import {
    ButtonHTMLAttributes,
    PropsWithChildren,
    useCallback,
    MouseEvent,
    forwardRef,
    ReactNode,
    } from 'react';
    import { keyframes } from '@emotion/core';

    export interface ButtonProps {
    primary?: boolean;
    error?: boolean;
    link?: boolean;
    transparent?: boolean;
    after?: ReactNode;
    }

    const getColorPair = (theme: Theme, props: ButtonProps) => {
    if (props.primary) {
    return theme.colors.primary;
    }
    if (props.error) {
    return theme.colors.error;
    }
    if (props.link) {
    return {
    backgroundColor: 'transparent',
    color: theme.elements.link.color,
    borderColor: 'transparent',
    };
    }
    if (props.transparent) {
    return {
    backgroundColor: 'transparent',
    color: theme.elements.link.color,
    borderColor: 'transparent',
    };
    }
    return theme.colors.default;
    };

    const rippleTransition = keyframes`
    to {
    transform: scale(4);
    opacity: 0;
    }`;

    const styles = (props: {
    primary: boolean;
    error: boolean;
    link: boolean;
    transparent: boolean;
    }) => (theme: Theme) => {
    const colorPairByProps = getColorPair(theme, props);
    return {
    button: {
    width: '100%',
    color: colorPairByProps.color,
    borderRadius: '4px',
    fontWeight: 'bold' as const,
    padding: `${theme.spacing(1)} ${theme.spacing(4)}`,
    border: '1px solid',
    borderColor: colorPairByProps.borderColor,
    backgroundColor: colorPairByProps.backgroundColor,
    position: 'relative' as const,
    overflow: 'hidden',
    transition: 'background 400ms',
    outline: '0',
    boxShadow:
    !props.link && !props.transparent && '0 0 0.5rem rgba(0, 0, 0, 0.3)',
    cursor: 'pointer',

    gridGap: theme.spacing(1),
    display: 'grid',
    gridAutoFlow: 'column',

    'span.ripple': {
    position: 'absolute' as const,
    borderRadius: '50%',
    transform: 'scale(0)',
    animation: `${rippleTransition} 600ms linear`,
    backgroundColor: 'rgba(255, 255, 255, 0.7)',
    },
    ':disabled': {
    backgroundColor: theme.palette.neutral,
    borderColor: theme.palette.darkerNeutral,
    color: theme.palette.darkerNeutral,
    cursor: 'not-allowed',
    },
    },
    };
    };

    export const Button = forwardRef<
    HTMLButtonElement,
    PropsWithChildren<ButtonProps & ButtonHTMLAttributes<unknown>>
    >((props, ref) => {
    const {
    children,
    primary = false,
    error = false,
    link = false,
    transparent = false,
    after,
    onClick,
    ...otherProps
    } = props;
    const styled = useStyledTheme(styles({ primary, error, link, transparent }));
    console.log(styled.button.backgroundColor, 'transparent', transparent);

    const createRipple = useCallback(
    (event: MouseEvent<HTMLButtonElement>) => {
    if (!!onClick) {
    onClick(event);
    }

    const button = event.currentTarget;

    const circle = document.createElement('span');
    const diameter = Math.max(button.clientWidth, button.clientHeight);
    const radius = diameter / 2;

    circle.style.width = `${diameter}px`;
    circle.style.height = `${diameter}px`;
    circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
    circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
    circle.classList.add('ripple');

    circle.addEventListener('animationend', () => button.removeChild(circle));

    button.appendChild(circle);
    },
    [onClick],
    );

    return (
    <button
    css={styled.button}
    {...otherProps}
    ref={ref}
    onClick={createRipple}
    >
    {styled.button.backgroundColor}
    {children}
    {!!after && after}
    </button>
    );
    });