"use client"; import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"; import { CheckIcon, LinkIcon, PlusIcon, SearchIcon, XIcon } from "lucide-react"; import { AnimatePresence, motion, MotionConfig, useReducedMotion } from "motion/react"; import { type ReactNode, useEffect, useId, useMemo, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; function useReducedMotionSafe() { const prefers = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted && Boolean(prefers); } export interface FaqItem { id: string; question: string; answer: string; category: string; } export interface FaqProps { eyebrow?: string; title?: ReactNode; description?: ReactNode; items: FaqItem[]; categories?: string[]; contact?: { title: string; text: string; label: string; href: string }; className?: string; } const ease = [0.23, 1, 0.32, 1] as const; const ALL = "All"; function escape(s: string) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function Marked({ text, query }: { text: string; query: string }) { const q = query.trim(); if (!q) return <>{text}; const parts = text.split(new RegExp(`(${escape(q)})`, "gi")); return ( <> {parts.map((part, i) => part.toLowerCase() === q.toLowerCase() ? ( // biome-ignore lint/suspicious/noArrayIndexKey: stable split {part} ) : ( // biome-ignore lint/suspicious/noArrayIndexKey: stable split {part} ), )} ); } function CopyLink({ id, question, variant }: { id: string; question: string; variant: "gutter" | "inline" }) { const [copied, setCopied] = useState(false); const timer = useRef(undefined); useEffect(() => () => window.clearTimeout(timer.current), []); const copy = async () => { const url = `${window.location.origin}${window.location.pathname}${window.location.search}#faq-${id}`; try { await navigator.clipboard.writeText(url); } catch { } window.history.replaceState(null, "", `#faq-${id}`); setCopied(true); window.clearTimeout(timer.current); timer.current = window.setTimeout(() => setCopied(false), 1600); }; const icon = ( {copied ? : } ); if (variant === "inline") { return ( ); } return ( ); } export function Faq({ eyebrow = "FAQ", title = "Questions, answered", description, items, categories: categoriesProp, contact, className, }: FaqProps) { const reduce = useReducedMotionSafe(); const searchId = useId(); const pillId = useId(); const categories = useMemo( () => [ALL, ...(categoriesProp ?? Array.from(new Set(items.map((i) => i.category))))], [categoriesProp, items], ); const [category, setCategory] = useState(ALL); const [query, setQuery] = useState(""); const [open, setOpen] = useState([]); const listRef = useRef(null); useEffect(() => { const match = window.location.hash.match(/^#faq-(.+)$/); if (!match) return; const id = decodeURIComponent(match[1]); if (!items.some((i) => i.id === id)) return; setOpen([id]); requestAnimationFrame(() => listRef.current?.querySelector(`[data-faq="${CSS.escape(id)}"]`)?.scrollIntoView({ block: "center", behavior: reduce ? "auto" : "smooth" }), ); }, [items, reduce]); const q = query.trim().toLowerCase(); const visible = items.filter( (i) => (category === ALL || i.category === category) && (!q || i.question.toLowerCase().includes(q) || i.answer.toLowerCase().includes(q)), ); const counts = (c: string) => items.filter((i) => (c === ALL || i.category === c) && (!q || `${i.question} ${i.answer}`.toLowerCase().includes(q))).length; return (
{eyebrow &&

{eyebrow}

}

{title}

{description &&

{description}

}
setQuery(e.target.value)} onKeyDown={(e) => e.key === "Escape" && setQuery("")} placeholder="Search questions" type="search" value={query} /> {query && ( )}
{categories.map((c) => { const on = c === category; const n = counts(c); return ( ); })}
{contact && (

{contact.title}

{contact.text}

)}
setOpen(v as string[])} value={open} > {visible.map((item, index) => ( {category === ALL && visible[index - 1]?.category !== item.category && (

0 && "pt-10", )} > {item.category}

)}

))}
{visible.length === 0 && (

Nothing for “{query.trim()}”{category !== ALL && ` in ${category}`}

Try a different word, or ask us directly. A human replies within a few hours.

{contact && ( )}
)}
{contact && (

{contact.title}{" "} {contact.label}

)}
); }