You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
3.7 KiB
116 lines
3.7 KiB
"use client";
|
|
|
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
import type { QuestionField } from "@/data/question-data";
|
|
import { useQuestionAnswers } from "./question-answer-storage";
|
|
import QuestionTitle from "./question-title";
|
|
|
|
type QuestionSliderProps = {
|
|
question: QuestionField;
|
|
questionIndex: number;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
export function QuestionSlider({
|
|
question,
|
|
questionIndex,
|
|
disabled,
|
|
}: QuestionSliderProps) {
|
|
const [min, max] = question.extras.range;
|
|
const initialValue = Math.round((min + max) / 2);
|
|
const { getAnswerValue, setAnswerValue } = useQuestionAnswers();
|
|
const storedValue = getAnswerValue(question, questionIndex);
|
|
const value = typeof storedValue === "number" ? storedValue : initialValue;
|
|
const progress = max === min ? 0 : ((value - min) / (max - min)) * 100;
|
|
const sliderWrapperRef = useRef<HTMLDivElement>(null);
|
|
const sliderRef = useRef<HTMLInputElement>(null);
|
|
const thumbWidth = 18;
|
|
const bubbleHalfWidth = 20;
|
|
const [bubblePosition, setBubblePosition] = useState(bubbleHalfWidth);
|
|
const steps = Array.from(
|
|
{ length: max - min + 1 },
|
|
(_, index) => min + index,
|
|
);
|
|
|
|
useLayoutEffect(() => {
|
|
const wrapper = sliderWrapperRef.current;
|
|
const slider = sliderRef.current;
|
|
|
|
if (!wrapper || !slider) {
|
|
return;
|
|
}
|
|
|
|
const updateBubblePosition = () => {
|
|
const wrapperRect = wrapper.getBoundingClientRect();
|
|
const sliderRect = slider.getBoundingClientRect();
|
|
const sliderLeft = sliderRect.left - wrapperRect.left;
|
|
const thumbCenter =
|
|
sliderLeft +
|
|
thumbWidth / 2 +
|
|
(progress / 100) * (sliderRect.width - thumbWidth);
|
|
|
|
setBubblePosition(
|
|
Math.min(
|
|
Math.max(thumbCenter, bubbleHalfWidth),
|
|
wrapperRect.width - bubbleHalfWidth,
|
|
),
|
|
);
|
|
};
|
|
|
|
updateBubblePosition();
|
|
|
|
const resizeObserver = new ResizeObserver(updateBubblePosition);
|
|
resizeObserver.observe(wrapper);
|
|
resizeObserver.observe(slider);
|
|
|
|
return () => resizeObserver.disconnect();
|
|
}, [progress]);
|
|
|
|
return (
|
|
<div
|
|
className={[
|
|
"flex w-full flex-col gap-6 transition-opacity duration-200",
|
|
disabled ? "pointer-events-none opacity-30" : "",
|
|
].join(" ")}
|
|
>
|
|
<QuestionTitle question={question} />
|
|
<div className="pt-9">
|
|
<div ref={sliderWrapperRef} className="relative">
|
|
<div
|
|
className="-translate-x-1/2 absolute -top-6.5 z-10 flex items-center justify-center rounded-[7px] bg-[#F43F5E] px-2.5 group-12 font-semibold text-white shadow-sm after:absolute after:-bottom-[4px] after:left-1/2 after:h-[9px] after:w-2.5 after:-translate-x-1/2 after:rotate-45 after:rounded-[2px] after:bg-[#F43F5E] after:content-['']"
|
|
style={{ left: `${bubblePosition}px` }}
|
|
>
|
|
{value}
|
|
</div>
|
|
<input
|
|
ref={sliderRef}
|
|
type="range"
|
|
min={min}
|
|
max={max}
|
|
step={1}
|
|
value={value}
|
|
onChange={(event) =>
|
|
setAnswerValue(
|
|
question,
|
|
questionIndex,
|
|
Number(event.target.value),
|
|
)
|
|
}
|
|
disabled={disabled}
|
|
className="question-slider-range w-full"
|
|
style={{
|
|
background: `linear-gradient(to right, #F43F5E 0%, #F43F5E ${progress}%, #FAD1D8 ${progress}%, #FAD1D8 100%)`,
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="mt-2 grid grid-flow-col auto-cols-fr text-center group-10 text-[#B7B7B7]">
|
|
{steps.map((step) => (
|
|
<span key={step}>{step}</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default QuestionSlider;
|