[React + Typescript + emotion] RangeSlider 컴포넌트 제작
프로젝트 내에 필터 설정에 키, 나이의 범위를 지정하는 부분에 양방향 RangeSlider가 필요해서 제작하게 되었다.
input Range, Range Slider 등등 다양하게 불리는 거 같다.
참고가 많이 되었던 블로그! 감사합니다.
*참고 - https://velog.io/@forest_xox/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%96%91%EB%B0%A9%ED%96%A5-Range-%EB%A7%8C%EB%93%A4%EA%B8%B0
우선 props로 받아올 값에는
1. 기준이 될 슬라이더의 최소, 최댓값(min, max)
2. 내가 선택한 현재의 최소, 최댓값(currentMin, currentMax)
3. 값 변경 시 호출될 콜백함수(handleValueChange)
가 필요하다 생각했다.
여기서 step은 slider가 움직일 때마다 +- 될 값이다.
나는 사용할 rangeSlider들이 다 1씩 움직일 거라 우선 고정시켜 놨지만, 추후 더 많이 쓰고 변경되면 props로 받아야 할 것 같다.
import { useEffect, useState } from "react";
import * as S from "./RangeSlider.styled";
interface RangeSliderProps {
currentMinValue?: number;
currentMaxValue?: number;
min: number;
max: number;
handleValueChange?: (minValue: number, maxValue: number) => void;
}
const RangeSlider = ({
currentMinValue,
currentMaxValue,
min,
max,
handleValueChange,
}: RangeSliderProps) => {
const [minValue, setMinValue] = useState(currentMinValue ?? min);
const [maxValue, setMaxValue] = useState(currentMaxValue ?? max);
const [minPercent, setMinPercent] = useState(0);
const [maxPercent, setMaxPercent] = useState(100);
const step = 1;
슬라이더 값 변경 시 호출될 함수들을 작성해 줬다.
const handleMinChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setMinValue(parseInt(e.target.value));
};
const handleMaxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setMaxValue(parseInt(e.target.value));
};
양방향 슬라이더다 보니 최솟값과 최댓값의 차이가 step 보다 작을 경우가 되지 않게 하는 함수도 만들었다.
(현재 maxValue가 작아지면 minValue = maxValue-1인 채로 같이 줄어들게 되어있다.)
정상적으로 최소 최대값의 차이가 step 보다 크면 그대로 minValue, maxValue에 넣고,
그게 아닐 경우 css에 필요한 percentage를 계산하게 했다.
const updatePercentages = () => {
if (maxValue - minValue < step) {
setMaxValue(minValue + step);
setMinValue(maxValue - step);
} else {
const range = max - min;
setMinPercent(((minValue - min) / range) * 100);
setMaxPercent(100 - ((max - maxValue) / range) * 100);
}
};
useEffect(() => {
updatePercentages();
if (handleValueChange) {
handleValueChange(minValue, maxValue);
}
}, [minValue, maxValue]);
return
return (
<S.SliderContainer>
<S.SliderWrapper>
<S.SliderRangeInner left={minPercent} width={maxPercent - minPercent} />
<S.RangeWrapper>
<S.Range
type="range"
min={min}
max={max - step}
step={step}
value={minValue}
onChange={(e) => handleMinChange(e)}
/>
<S.Range
type="range"
min={min + step}
max={max}
step={step}
value={maxValue}
onChange={(e) => handleMaxChange(e)}
/>
</S.RangeWrapper>
</S.SliderWrapper>
</S.SliderContainer>
);
};
export default RangeSlider;
css
SliderRangeInner의 left, width에 계산한 percent값들을 넣어준다.
import { css } from "@emotion/react";
import styled from "@emotion/styled";
export const SliderContainer = styled.div`
width: 320px;
height: 24px;
display: flex;
align-items: center;
align-self: center;
`;
export const SliderWrapper = styled.div`
position: relative;
width: 100%;
height: 4px;
border-radius: 16px;
background-color: #ddd;
`;
export const SliderRangeInner = styled.div<{ left: number; width: number }>`
${({ left, width, theme }) => css`
position: absolute;
left: ${left}%;
width: ${width}%;
background: ${theme.colors.navy900};
height: 4px;
`}
`;
export const RangeWrapper = styled.div`
position: relative;
height: 4px;
`;
export const Range = styled.input`
${({ theme }) => css`
position: absolute;
top: -10px;
width: 100%;
-webkit-appearance: none;
background: none;
pointer-events: none;
&::-webkit-slider-thumb {
height: 24px;
width: 24px;
border-radius: 50%;
border: 1.5px solid ${theme.colors.navy900};
background-color: ${theme.colors.white};
-webkit-appearance: none;
pointer-events: auto;
}
`}
`;
&::-webkit-slider-thumb 이 부분을 잘 생각하지 못했는데 블로그에서 많이 참고했다.
'FrontEnd > Component' 카테고리의 다른 글
[React + Typescript + emotion] Spinner 컴포넌트 제작 (0) | 2024.08.17 |
---|---|
[React + Typescript + emotion] Tag 컴포넌트 제작 (1) | 2024.08.14 |
[React + Typescript + emotion] Dropdown 컴포넌트 제작 (0) | 2024.08.13 |
[React + Typescript + emotion] Badge 컴포넌트 제작 (0) | 2024.08.09 |
[React + Typescript + emotion] Toggle 컴포넌트 제작 (0) | 2024.08.08 |