{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "faq-auto-accordion", "type": "registry:ui", "title": "FAQ Auto Accordion", "description": "Spring-driven FAQ with a traveling accent bar (layoutId), spring height animation, blur-deblur text reveal, and layout FLIP repositioning.", "dependencies": [ "motion" ], "files": [ { "path": "registry/ruixenui/faq-auto-accordion.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, AnimatePresence, LayoutGroup } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\n/* ═══════════════════════════════════════════════════════════\n FAQ Auto Accordion — Spring-Driven Traveling Accent.\n\n A minimal FAQ where the active indicator physically springs\n between items using motion's layoutId. Each answer expands\n with true spring physics — overshoot and settle, not CSS\n cubic-bezier. Siblings smoothly reposition via layout FLIP.\n\n The visual metaphor: a reading marker that slides between\n questions. Only one answer is visible at a time.\n\n Animation signatures (ALL unique to this component):\n 1. layoutId accent bar — springs between items (FLIP)\n 2. Spring height (height: 0 → \"auto\") via AnimatePresence\n 3. Blur-deblur text reveal on answers\n 4. layout=\"position\" sibling repositioning\n 5. Differential spring stiffness (open: 300, close: 400)\n 6. CSS linear progress bar at answer's bottom edge\n\n Four FAQ paradigms in ruixen, zero overlap:\n • faq-auto-accordion: spring traveling accent, motion physics\n • faq-chat-accordion: conversational message exchange\n • faq-scroll-accordion: self-contained scroll cascade\n • staggered-faq: BlurredStagger text reveal, split\n\n Dependencies: motion.\n ═══════════════════════════════════════════════════════════ */\n\nexport interface FAQItem {\n question: string;\n answer: string;\n}\n\nexport interface FAQAutoAccordionProps {\n title?: string;\n subtitle?: string;\n items?: FAQItem[];\n /** Seconds per item auto-advance. 0 to disable. */\n autoInterval?: number;\n className?: string;\n}\n\nconst defaultItems: FAQItem[] = [\n {\n question: \"What is the purpose of this platform?\",\n answer:\n \"Our platform is designed to simplify your workflow and save you hours every week using automation and AI-powered tools.\",\n },\n {\n question: \"Is this service available worldwide?\",\n answer:\n \"Yes, we support users across the globe with localized features and multi-currency billing support.\",\n },\n {\n question: \"Do you offer refunds?\",\n answer:\n \"Yes, we offer a 7-day refund policy. If you're unsatisfied, just contact our support within that time frame.\",\n },\n {\n question: \"Can I change my plan later?\",\n answer:\n \"Absolutely! You can upgrade or downgrade your plan anytime from your account dashboard. Changes take effect immediately.\",\n },\n {\n question: \"Does this integrate with other tools?\",\n answer:\n \"Yes! We support integrations with Slack, Notion, Zapier, and many more through our extensive plugin ecosystem.\",\n },\n {\n question: \"Is there an API available?\",\n answer:\n \"Yes, our public API is available for all Pro users. Documentation can be found in the developer portal.\",\n },\n];\n\nexport function FAQAutoAccordion({\n title = \"Have questions?\",\n subtitle = \"Everything you need to know.\",\n items = defaultItems,\n autoInterval = 6,\n className,\n}: FAQAutoAccordionProps) {\n const [active, setActive] = React.useState(0);\n const [paused, setPaused] = React.useState(false);\n const [cycleKey, setCycleKey] = React.useState(0);\n\n // ── Auto-advance ─────────────────────────────────────────\n React.useEffect(() => {\n if (autoInterval <= 0 || paused || active === null) return;\n\n const timer = setTimeout(() => {\n setActive((prev) => {\n if (prev === null) return 0;\n return (prev + 1) % items.length;\n });\n setCycleKey((k) => k + 1);\n }, autoInterval * 1000);\n\n return () => clearTimeout(timer);\n }, [active, autoInterval, paused, items.length, cycleKey]);\n\n // ── Click handler ────────────────────────────────────────\n const toggle = React.useCallback((i: number) => {\n setActive((prev) => (prev === i ? null : i));\n setCycleKey((k) => k + 1);\n }, []);\n\n return (\n setPaused(true)}\n onMouseLeave={() => {\n setPaused(false);\n setCycleKey((k) => k + 1);\n }}\n >\n
\n {/* ── Header ────────────────────────────────────── */}\n {title && (\n
\n

\n {title}\n

\n {subtitle && (\n

{subtitle}

\n )}\n
\n )}\n\n {/* ── FAQ list with spring layout ────────────────── */}\n \n
\n {items.map((item, i) => {\n const isActive = active === i;\n\n return (\n \n toggle(i)}\n aria-expanded={isActive}\n >\n {/* ── Traveling accent bar ─────────────── */}\n \n {isActive && (\n \n )}\n \n\n {/* ── Question ─────────────────────────── */}\n \n {item.question}\n \n \n\n {/* ── Expandable answer ─────────────────── */}\n \n {isActive && (\n \n \n {item.answer}\n \n\n {/* ── Progress bar ────────────────── */}\n {autoInterval > 0 && (\n
\n \n
\n )}\n \n )}\n
\n \n );\n })}\n
\n
\n
\n\n \n \n );\n}\n", "type": "registry:ui", "target": "components/ruixen/faq-auto-accordion.tsx" } ] }