import { useRef, useState } from 'react'; import styled from 'styled-components'; const THUMB_WIDTH = 16; const Range = styled.div` position: relative; --range-width: 100%; --range-handle-color: #ffffff; --range-handle-size: ${() => `${THUMB_WIDTH}px`}; --range-track-color: #f7f7f7; --range-track-height: 8px; `; const SliderThumbTriangle = styled.div` position: absolute; display: block; top: -5px; left: 24px; content: ' '; width: 0; height: 0; border-style: solid; border-right: 8px solid transparent; border-left: 8px solid transparent; border-top: 8px solid var(--range-handle-color); border-bottom: 0; background-color: transparent; box-shadow: none; z-index: 3; border-radius: 0; transform: translateX(-50%); cursor: pointer; `; const RangeInput = styled.input` -webkit-appearance: none; width: 100%; height: var(--range-track-height); border-radius: 5px; background-color: transparent; outline: none; padding: 0; margin: 0; &::-webkit-slider-thumb { position: relative; appearance: none; width: var(--range-handle-size); height: 12px; z-index: 3; border-radius: 3px 3px 0 0; transform: translateY(-22px); background: var(--range-handle-color); box-shadow: 1px 6px 10px 0px rgba(0, 0, 0, 0.5); cursor: pointer; } &::-moz-range-thumb { position: relative; appearance: none; width: var(--range-handle-size); height: 12px; z-index: 3; border-radius: 3px 3px 0 0; transform: translateY(-19px); outline: 0; border: 0; background: var(--range-handle-color); box-shadow: 1px 6px 10px 0px rgba(0, 0, 0, 0.5); cursor: pointer; } `; const ExtraTrack = styled.div` position: absolute; z-index: 2; width: 100%; height: 100%; `; const Mark = styled.div<{ text: string | number }>` height: 8px; background-color: #000; position: absolute; top: -2px; left: 0; &:after { content: '${(props) => props.text}'; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); font-size: 12px; color: #666; font-weight: bold; } `; const MarkWrapper = styled.div` background-color: var(--range-track-color); position: absolute; top: 0px; left: 0; height: 8px; ${Mark} { position: static; transform: translateX(50%); height: 12px; } display: flex; justify-content: center; align-items: center; `; const NormalRange = styled.div` display: flex; align-items: center; gap: 0.5rem; span { color: #666; } `; const NormalRangeInner = styled.div` flex: 1; position: relative; ${RangeInput} { background-color: #007aff; height: 2px; transform: translateY(-3px); } ${SliderThumbTriangle} { top: -5px; } `; const NormalValue = styled.span` position: absolute; top: 14px; transform: translateX(-50%); color: #333 !important; `; export interface SliderProps { name?: string; max: number; min: number; value: number; step?: number; onChange?: (value: number) => void; className?: string; type: 'mark' | 'fill' | 'normal'; } export default function Slider({ name, max, min, step = 1, value: propsValue, className, type = 'mark', onChange, }: SliderProps) { const [value, setValue] = useState(`${propsValue}`); const range = max - min; const amount = range / step; const thumbRef = useRef(null); const ratio = (Number(value) - min) / range; const handleOnChange = (e: React.ChangeEvent) => { setValue(e.target.value); onChange?.(Number(e.target.value)); }; if (type === 'normal') { return ( {min} {value} {max} ); } return ( {type === 'mark' && ( {new Array(Math.floor(amount + 1)).fill(0).map((_, index) => { return ( ); })} )} {type === 'fill' && ( {new Array(Math.floor(amount + 1)).fill(0).map((_, index) => { const _value = (min + step * index).toFixed(1); return ( ); })} )} ); }