VandyHacks/vaken

View on GitHub
src/client/components/Buttons/RadioSlider.tsx

Summary

Maintainability
C
1 day
Test Coverage
F
57%
import React, { useState, useEffect, useCallback, FC } from 'react';
import styled from 'styled-components';
import STRINGS from '../../assets/strings.json';

export interface Props {
    disable?: boolean;
    large?: boolean;
    onChange?: (input: string) => void;
    option1: string;
    option2: string;
    option3: string;
    value: string;
    confirmMessageFunc?: (input: string) => string;
}

interface SelectorProps {
    color: string;
    left: string;
    width: string;
}

interface WrapperProps {
    disable: boolean;
    large: boolean;
}

const Wrapper = styled('div')`
    cursor: pointer;
    margin: auto;
    height: ${({ large }: WrapperProps): string => (large ? '3.0rem' : '1.5rem')};
    line-height: ${({ large }: WrapperProps): string => (large ? '3.0rem' : '1.5rem')};
    border-radius: 0.25rem;
    background: ${({ disable }: WrapperProps): string => (!disable ? '#ccc' : '#B0B0B0')};
    position: relative;
    display: block;
    float: left;
`;

const Switch = styled('div')`
    cursor: pointer;
    position: relative;
    display: block;
    float: left;
    transition: 300ms ease-out;
    padding: 0 0.75rem;
`;

const Selector = styled('div')`
    left: ${({ left }: SelectorProps): string => left};
    text-align: center;
    position: absolute;
    width: ${({ width }: SelectorProps): string => width};
    box-sizing: border-box;
    transition: 300ms ease-out;
    border-radius: 0.225rem;
    color: white;
    box-shadow: 0px 0.125rem 0.625rem 0px #9b9b9b;
    background-color: ${({ color }: SelectorProps): string => color};
`;

const disabledColor = '#696969';

export const RadioSlider: FC<Props> = (props: Props) => {
    const { option1, option2, option3, disable = false, confirmMessageFunc } = props;
    const [selected, setSelected] = useState(option2);
    const [width, setWidth] = useState(0);
    const [left, setLeft] = useState(0);
    const [color, setColor] = useState(!disable ? STRINGS.ACCENT_COLOR_DARK : disabledColor);
    const [isLoaded, setIsLoaded] = useState(false);
    const [option1Width, setOption1Width] = useState(0);
    const [option2Width, setOption2Width] = useState(0);
    const [option3Width, setOption3Width] = useState(0);

    // Defines the width after the node as been loaded in the Node
    const option1Ref = useCallback((node): void => {
        if (node !== null) {
            setOption1Width(node.getBoundingClientRect().width);
        }
    }, []);
    const option2Ref = useCallback((node): void => {
        if (node !== null) {
            setOption2Width(node.getBoundingClientRect().width);
        }
    }, []);
    const option3Ref = useCallback((node): void => {
        if (node !== null) {
            setOption3Width(node.getBoundingClientRect().width);
        }
    }, []);

    // Moves the selector to the correct place, with the correct colors
    // Takes into account the disable state
    const toggle = useCallback(
        (input: string): void => {
            switch (input) {
                case option1:
                    setWidth(option1Width);
                    setLeft(0);
                    setColor(!disable ? '#00C48C' : disabledColor);
                    break;
                case option2:
                    setWidth(option2Width);
                    setLeft(option1Width);
                    setColor(!disable ? STRINGS.ACCENT_COLOR_DARK : disabledColor);
                    break;
                case option3:
                    setWidth(option3Width);
                    setLeft(option1Width + option2Width);
                    setColor(!disable ? '#FF647C' : disabledColor);
                    break;
                default:
            }
            setSelected(input);
        },
        [option1, disable, option2, option3, option1Width, option2Width, option3Width]
    );

    const { value } = props;
    // Forces a retoggle when the props.value changes (ie for outside changes)
    useEffect((): void => {
        if (isLoaded) {
            toggle(value);
        }
    }, [isLoaded, value, toggle]);

    // Will set isLoaded = true once the widths are successfully taken (ie non-zero) from the switch elements
    useEffect((): void => {
        if (isLoaded === false && option1Width && option2Width && option3Width) {
            toggle(value);
            setIsLoaded(true);
        }
    }, [isLoaded, option1Width, option2Width, option3Width, value, toggle]);

    const { onChange = null } = props;
    const onClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
        const target = event.target as HTMLDivElement;
        if (!disable) {
            if (confirmMessageFunc) {
                // eslint-disable-next-line no-alert
                const confirmed = window.confirm(confirmMessageFunc(target.id));
                if (!confirmed) {
                    return;
                }
            }
            toggle(target.id);
            if (typeof onChange === 'function') {
                onChange(target.id);
            }
        }
    };

    const { large = false } = props;
    // Selector's onClick works to allow you to click the middle button if it is already selected by default for the large button
    return (
        <Wrapper large={large} disable={disable}>
            <Switch id={option1} onClick={onClick} ref={option1Ref}>
                {option1}
            </Switch>
            <Switch id={option2} onClick={onClick} ref={option2Ref}>
                {option2}
            </Switch>
            <Switch id={option3} onClick={onClick} ref={option3Ref}>
                {option3}
            </Switch>
            {isLoaded && (
                <Selector
                    left={`${left}px`}
                    width={`${width}px`}
                    color={color}
                    id={selected}
                    onClick={(event: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
                        if (large) onClick(event);
                    }}>
                    {selected}
                </Selector>
            )}
        </Wrapper>
    );
};

export default RadioSlider;