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

"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;