import { useState } from 'react' import type { ReactNode } from 'react' import { cn } from '../../lib/utils' import { motion } from 'framer-motion' import { IconButton } from '../../basic/icon-button' import { Button } from '../../basic/button' import { OptionList } from '../../basic/option-list' import { Scrollbar } from '../../basic/scrollbar' import type { OptionItem } from '../../basic/option-list' import { useUserQuestionContext } from './context' import type { UserQuestionItem } from './types' // ===== UserQuestion.Header ===== export interface UserQuestionHeaderProps { className?: string } export function UserQuestionHeader({ className }: UserQuestionHeaderProps) { const ctx = useUserQuestionContext() return (
{ctx.chat4Icon} {ctx.currentQuestion?.header || ctx.labels.questionHeader} {ctx.currentQuestion?.multiSelect ? ctx.labels.multiSelect : ctx.labels.singleSelect}
{ctx.questions.length > 1 && (
{ctx.currentQuestionIndex + 1} / {ctx.questions.length}
)}
) } UserQuestionHeader.displayName = 'UserQuestionHeader' // ===== UserQuestion.Card ===== export interface UserQuestionCardProps { question: UserQuestionItem qIndex: number className?: string } export function UserQuestionCard({ question, qIndex, className }: UserQuestionCardProps) { const ctx = useUserQuestionContext() const questionOptions = question.options || [] const [isInputFocused, setIsInputFocused] = useState(false) const hasInputValue = !!(ctx.customInputs[question.question]?.trim()) const showUnderline = isInputFocused || hasInputValue const isCurrentQuestion = qIndex === ctx.currentQuestionIndex return (
{ if (el) ctx.questionRefs.current.set(qIndex, el) else ctx.questionRefs.current.delete(qIndex) }} className={cn( 'transition-opacity duration-200', isCurrentQuestion ? 'opacity-100' : 'opacity-40', className )} >
{qIndex + 1}.
{question.question}
({ id: opt.label, label: opt.label, description: opt.description, }))} selectedIds={ctx.answers[question.question]?.map((l) => l) || []} focusedId={ isCurrentQuestion ? questionOptions[ctx.focusedOptionIndex]?.label : undefined } disabled={ctx.isSubmitting || !isCurrentQuestion} onItemClick={(item: OptionItem, index: number) => { if (!isCurrentQuestion) { ctx.setCurrentQuestionIndex(qIndex) ctx.setFocusedOptionIndex(ctx.getSelectedOptionIndex(qIndex)) return } ctx.handleOptionClick(question.question, item.label, qIndex) ctx.setFocusedOptionIndex(index) }} /> {question.allowCustomInput !== false && (
{questionOptions.length + 1}
{ if (el) ctx.customInputRefs.current.set(question.question, el) else ctx.customInputRefs.current.delete(question.question) }} type="text" value={ctx.customInputs[question.question] || ''} style={{ outline: 'none', outlineOffset: 0, boxShadow: 'none', border: 'none' }} onChange={(e) => { const value = e.target.value ctx.setCustomInputs((prev) => ({ ...prev, [question.question]: value })) if (!question.multiSelect && value.trim()) { ctx.setAnswers((prev) => ({ ...prev, [question.question]: [], })) } }} onFocus={() => { setIsInputFocused(true) if (isCurrentQuestion) { ctx.setFocusedOptionIndex(questionOptions.length) if (!question.multiSelect) { ctx.setAnswers((prev) => ({ ...prev, [question.question]: [], })) } } }} onBlur={() => setIsInputFocused(false)} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() e.stopPropagation() const currentCustomInput = ctx.customInputs[question.question]?.trim() const currentSelectedOptions = ctx.answers[question.question] || [] const hasAnswer = currentSelectedOptions.length > 0 || !!currentCustomInput if (hasAnswer) { if (ctx.allQuestionsAnswered) { ctx.handleContinue() } else if (qIndex < ctx.questions.length - 1) { const nextIndex = qIndex + 1 ctx.setShouldScrollToQuestion(true) ctx.setCurrentQuestionIndex(nextIndex) ctx.setFocusedOptionIndex(0) } else { ctx.handleContinue() } } } }} placeholder={ctx.labels.customInputPlaceholder} disabled={ctx.isSubmitting || !isCurrentQuestion} className={cn( 'w-full h-full py-0 bg-transparent text-sm leading-sm font-medium text-text', 'placeholder:text-text-tertiary placeholder:font-normal', 'border-0 rounded-none outline-none outline-offset-0', 'focus:outline-none focus:outline-offset-0', 'focus-visible:outline-none focus-visible:outline-offset-0', 'ring-0 focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 shadow-none', 'disabled:cursor-not-allowed' )} />
)}
) } UserQuestionCard.displayName = 'UserQuestionCard' // ===== UserQuestion.Cards (渲染所有问题卡片) ===== export interface UserQuestionCardsProps { className?: string children?: ReactNode } export function UserQuestionCards({ className, children }: UserQuestionCardsProps) { const ctx = useUserQuestionContext() // Destructure to avoid lint false positives about ref access during render const { scrollContainerRef, maxQuestionHeight, questions } = ctx return (
{children ?? questions.map((question, qIndex) => ( ))}
) } UserQuestionCards.displayName = 'UserQuestionCards' // ===== UserQuestion.Footer ===== export interface UserQuestionFooterProps { className?: string } export function UserQuestionFooter({ className }: UserQuestionFooterProps) { const ctx = useUserQuestionContext() return (
{!ctx.allQuestionsAnswered && ( )}
) } UserQuestionFooter.displayName = 'UserQuestionFooter' // ===== UserQuestion.DefaultLayout ===== export function UserQuestionDefaultLayout() { return ( ) } UserQuestionDefaultLayout.displayName = 'UserQuestionDefaultLayout'