71 lines
1.9 KiB
TypeScript
71 lines
1.9 KiB
TypeScript
import type { ChangeEvent } from "react";
|
|
|
|
export type PropertySliderFieldProps = {
|
|
// 슬라이더 그룹 전체 라벨 텍스트
|
|
label: string;
|
|
// 슬라이더 요소에 대한 접근성 라벨
|
|
ariaLabelSlider: string;
|
|
// 텍스트 입력 요소에 대한 접근성 라벨
|
|
ariaLabelInput: string;
|
|
// 현재 값 (슬라이더와 입력이 공유)
|
|
value: number;
|
|
// 최소 값
|
|
min: number;
|
|
// 최대 값
|
|
max: number;
|
|
// 슬라이더 스텝
|
|
step: number;
|
|
// 값 변경 콜백
|
|
onChange: (value: number) => void;
|
|
};
|
|
|
|
export function PropertySliderField({
|
|
label,
|
|
ariaLabelSlider,
|
|
ariaLabelInput,
|
|
value,
|
|
min,
|
|
max,
|
|
step,
|
|
onChange,
|
|
}: PropertySliderFieldProps) {
|
|
// 슬라이더 변경 시 상위에서 전달받은 onChange에 숫자 값으로 전달한다.
|
|
const handleSliderChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
const numeric = Number(event.target.value || 0);
|
|
onChange(numeric);
|
|
};
|
|
|
|
// 텍스트 입력 변경 시에도 숫자 값으로 파싱하여 onChange를 호출한다.
|
|
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
const raw = event.target.value;
|
|
const numeric = Number(raw);
|
|
if (!Number.isNaN(numeric)) {
|
|
onChange(numeric);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<label className="flex flex-col gap-1 text-xs text-slate-400">
|
|
<span>{label}</span>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="range"
|
|
min={min}
|
|
max={max}
|
|
step={step}
|
|
aria-label={ariaLabelSlider}
|
|
value={value}
|
|
onChange={handleSliderChange}
|
|
className="flex-1"
|
|
/>
|
|
</div>
|
|
<input
|
|
className="w-full rounded border border-slate-700 bg-slate-900 px-2 py-1 text-xs outline-none focus:border-sky-500"
|
|
aria-label={ariaLabelInput}
|
|
value={Number.isFinite(value) ? value : ""}
|
|
onChange={handleInputChange}
|
|
/>
|
|
</label>
|
|
);
|
|
}
|