import { forwardRef, useCallback, useLayoutEffect, useRef, useState, type ComponentProps, type ReactElement, type ReactNode } from "react"; import { motion, useAnimationControls, useReducedMotion } from "motion/react"; import { ChevronDown } from "lucide-react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/shared/ui/collapsible"; import { agentEase } from "../components/agent-motion"; import { AnswerMotionContext, useAnswerMotion } from "./answer-motion-context"; /** Initial rendering is instant; subsequent transitions follow pointer or keyboard modality. */ export function AnswerMotionScope({ children, ...props }: ComponentProps<"div">) { const pointer = useRef(false); const reducedMotion = useReducedMotion(); const shouldAnimate = useCallback(() => pointer.current && !reducedMotion, [reducedMotion]); return
{ pointer.current = true; }} onKeyDownCapture={() => { pointer.current = false; }} onClickCapture={(event) => { if (event.detail === 0) pointer.current = false; }}>{children}
; } /** Keep the same mounted editors; retiring content is immediately inert and hidden from AT. */ export const AnswerReveal = forwardRef & { open: boolean }>( function AnswerReveal({ open, children, ...props }, ref) { const shouldAnimate = useAnswerMotion(); const reducedMotion = useReducedMotion(); const controls = useAnimationControls(); const previous = useRef(open); const [retained, setRetained] = useState(open); useLayoutEffect(() => { const changed = previous.current !== open; previous.current = open; controls.stop(); if (!changed || !shouldAnimate() || reducedMotion) { controls.set({ opacity: 1, y: 0 }); setRetained(open); return; } let cancelled = false; if (open) { setRetained(true); controls.set({ opacity: 0, y: -4 }); } void controls.start(open ? { opacity: 1, y: 0 } : { opacity: 0, y: -4 }, { duration: 0.18, ease: agentEase }).then(() => { if (!cancelled) setRetained(open); }); return () => { cancelled = true; controls.stop(); }; }, [controls, open, reducedMotion, shouldAnimate]); return ; } ); export function AnswerDisclosure({ open, onOpenChange, trigger, children, contentClassName, ...props }: { open: boolean; onOpenChange: (open: boolean) => void; trigger: ReactElement; children: ReactNode; contentClassName?: string; } & Omit, "children" | "open" | "onOpenChange">) { return {trigger} {children} ; } export function AnswerDisclosureChevron({ open }: { open: boolean }) { const shouldAnimate = useAnswerMotion(); return ; }