"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { ExplanationUiFont } from "./explanation-ui-font"; import type { QuestionField } from "@/lib/schema-adapter"; import { useI18n } from "@/translations/provider"; import { useQuestionAnswers } from "./question-answer-storage"; import QuestionTitle from "./question-title"; type QuestionDropdownProps = { question: QuestionField; disabled?: boolean; }; export function QuestionDropdown({ question, disabled, }: QuestionDropdownProps) { const { dictionary: t } = useI18n(); const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); const rawValue = getAnswerValue(question); const isMulti = Array.isArray(rawValue) || question.type === "checkbox" || (question.extras?.range && question.extras.range[1] > 1); const selectedList = Array.isArray(rawValue) ? rawValue : typeof rawValue === "string" && rawValue ? [rawValue] : []; const singleValue = typeof rawValue === "string" ? rawValue : ""; const [isOpen, setIsOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const containerRef = useRef(null); const listRef = useRef(null); const searchInputRef = useRef(null); // Lock page scroll when dropdown is open useEffect(() => { if (isOpen) { document.body.classList.add("dropdown-open"); document.body.style.overflow = "hidden"; document.documentElement.style.overflow = "hidden"; } else { document.body.classList.remove("dropdown-open"); document.body.style.overflow = ""; document.documentElement.style.overflow = ""; } return () => { document.body.classList.remove("dropdown-open"); document.body.style.overflow = ""; document.documentElement.style.overflow = ""; }; }, [isOpen]); // Prevent wheel scroll from escaping the options list const handleListWheel = useCallback((e: React.WheelEvent) => { e.stopPropagation(); const el = listRef.current; if (!el) return; const atTop = el.scrollTop <= 0 && e.deltaY < 0; const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight && e.deltaY > 0; if (atTop || atBottom) { e.preventDefault(); } }, []); // Close dropdown on click outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( containerRef.current && !containerRef.current.contains(event.target as Node) ) { setIsOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); useEffect(() => { if (isOpen) { const timer = setTimeout(() => { searchInputRef.current?.focus(); }, 50); return () => clearTimeout(timer); } }, [isOpen]); const options = question.options || []; const showSearch = (() => { if (question.extras?.noSearch) return false; return options.length > 10; })(); const filteredOptions = options.filter((option) => option.label.toLowerCase().includes(searchQuery.toLowerCase()), ); const getCleanLabel = (optId: string) => { const opt = options.find((o) => o.id === optId); if (!opt) return optId; return opt.label.split(" - ")[0]; }; const displayLabel = isMulti ? selectedList.length > 0 ? selectedList.map(getCleanLabel).join(", ") : question.extras?.placeHolder || "Select" : singleValue ? getCleanLabel(singleValue) : question.extras?.placeHolder || "Select"; const hasSelectedValue = isMulti ? selectedList.length > 0 : Boolean(singleValue); const toggleMultiOption = (optionId: string) => { let nextValue: string[]; if (selectedList.includes(optionId)) { nextValue = selectedList.filter((v) => v !== optionId); } else { nextValue = [...selectedList, optionId]; } setAnswerValue( question, nextValue.length > 0 ? nextValue : null, ); }; return (
{/* Select Trigger Button */} {/* Dropdown Options Panel */} {isOpen && (
{/* Search Input Bar */} {showSearch ? (
setSearchQuery(e.target.value)} placeholder="Search..." className="flex-1 bg-transparent text-[14px] font-medium text-[#181818] outline-none placeholder:text-[#98A2B3]" /> {searchQuery ? ( ) : null}
) : null} {/* Options List */}
e.stopPropagation()} onTouchMove={(e) => e.stopPropagation()} onTouchEnd={(e) => e.stopPropagation()} className="flex max-h-[220px] flex-col gap-3.5 overflow-y-auto overscroll-contain pr-1" > {filteredOptions.length > 0 ? ( filteredOptions.map((option) => { const isSelected = isMulti ? selectedList.includes(option.id) : singleValue === option.id; return ( ); }) ) : ( No options found )}
)}
); } export default QuestionDropdown;