Slider.tsx 2.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
import React, {FunctionComponent, useCallback, useState} from 'react';
import {borderColor, borderFocusedColor, rem, size, transitionProps} from '~/utils/style';
import {height, padding} from '~/components/Input';

import BigNumber from 'bignumber.js';
import RangeSlider from '~/components/RangeSlider';
import styled from 'styled-components';

const Wrapper = styled.div`
    display: flex;
    align-items: center;
`;

const Input = styled.input`
    ${size(height, rem(52))};
    line-height: ${height};
    display: inline-block;
    outline: none;
    padding: ${padding};
    ${transitionProps('border-color')}
    border: none;
    border-bottom: 1px solid ${borderColor};
    text-align: center;

    &:hover,
    &:focus {
        border-bottom-color: ${borderFocusedColor};
    }
`;

const FullWidthRangeSlider = styled(RangeSlider)`
    flex-grow: 1;
    margin-right: ${rem(20)};
`;

type SliderProps = {
    min: number;
    max: number;
    step: number;
    value: number;
    onChange?: (value: number) => unknown;
    onChangeComplete?: (value: number) => unknown;
};

const Slider: FunctionComponent<SliderProps> = ({onChange, onChangeComplete, value, min, max, step}) => {
    const fixNumber = useCallback(
        (v: number) =>
            new BigNumber(v).dividedBy(step).integerValue(BigNumber.ROUND_HALF_UP).multipliedBy(step).toNumber(),
        [step]
    );

    const [sliderValue, setSliderValue] = useState(fixNumber(value));
    const [inputValue, setInputValue] = useState(sliderValue + '');

    const changeSliderValue = useCallback(
        (value: number) => {
            const v = fixNumber(value);
            setInputValue(v + '');
            setSliderValue(v);
            onChange?.(v);
        },
        [fixNumber, onChange]
    );

    const changeInputValue = useCallback(
        (value: string) => {
            setInputValue(value);

            const v = Number.parseFloat(value);

            if (v < min || v > max || Number.isNaN(v)) {
                return;
            }

            const result = fixNumber(v);

            setSliderValue(result);
            onChange?.(result);
            onChangeComplete?.(result);
        },
        [onChange, onChangeComplete, min, max, fixNumber]
    );

    const confirmInput = useCallback(() => {
        setInputValue(sliderValue + '');
    }, [sliderValue]);

    return (
        <Wrapper>
            <FullWidthRangeSlider
                min={min}
                max={max}
                step={step}
                value={sliderValue}
                onChange={changeSliderValue}
                onChangeComplete={() => onChangeComplete?.(sliderValue)}
            />
            <Input
                type="text"
                value={inputValue}
                onChange={e => changeInputValue(e.currentTarget.value)}
                onBlur={confirmInput}
                onKeyDown={e => e.key === 'Enter' && confirmInput()}
            />
        </Wrapper>
    );
};

export default Slider;