@@ -0,0 +1,84 @@
|
||||
import * as React from "react"
|
||||
import { Plus } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion"
|
||||
|
||||
type FaqItem = {
|
||||
question: React.ReactNode
|
||||
answer: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle disc: hairline teal plus that fills in and turns into an × once its
|
||||
* item opens. Driven by the trigger's own `group`, so hovering the answer body
|
||||
* below leaves the disc alone.
|
||||
*/
|
||||
function FaqIndicator() {
|
||||
return (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="flex size-8 shrink-0 items-center justify-center rounded-full border border-brand-100 bg-card text-primary shadow-pill transition-[transform,color,background-color,border-color] duration-200 group-hover:border-brand-200 group-hover:bg-accent group-data-[state=open]:rotate-45 group-data-[state=open]:border-transparent group-data-[state=open]:bg-primary group-data-[state=open]:text-primary-foreground"
|
||||
>
|
||||
<Plus className="size-4" strokeWidth={2.5} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* FAQ list: one white card per question, plus disc as the toggle, and the open
|
||||
* card tinted brand-50 behind a mint hairline so the answer reads as its own
|
||||
* panel. Single-open and collapsible — the block only ever moves one card's
|
||||
* worth of height, and every answer can be closed again.
|
||||
*
|
||||
* Sized for a reading column (centred `max-w-3xl`) instead of the full 6xl
|
||||
* container; override the width via `className`.
|
||||
*/
|
||||
function FaqAccordion({
|
||||
items,
|
||||
defaultOpen = 0,
|
||||
className,
|
||||
}: {
|
||||
items: FaqItem[]
|
||||
/** index of the item open on first paint; `null` starts fully collapsed */
|
||||
defaultOpen?: number | null
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
defaultValue={defaultOpen == null ? undefined : `faq-${defaultOpen}`}
|
||||
className={cn("mx-auto flex w-full max-w-3xl flex-col gap-3", className)}
|
||||
>
|
||||
{items.map((item, i) => (
|
||||
<AccordionItem
|
||||
key={i}
|
||||
value={`faq-${i}`}
|
||||
/* the all-round `border` supersedes the primitive's divider width,
|
||||
which leaves `last:border-b-0` to strip the final card's bottom
|
||||
edge — `last:border-b` puts it back. */
|
||||
className="rounded-2xl border border-border/70 bg-card px-5 shadow-card transition-colors duration-200 last:border-b hover:border-brand-200 data-[state=open]:border-brand-200 data-[state=open]:bg-brand-50/50 sm:px-6"
|
||||
>
|
||||
<AccordionTrigger
|
||||
className="group items-center gap-5 rounded-xl py-5 text-base leading-snug font-bold text-navy"
|
||||
indicator={<FaqIndicator />}
|
||||
>
|
||||
{item.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-6 leading-relaxed text-pretty sm:pr-13">
|
||||
{item.answer}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
|
||||
export { FaqAccordion }
|
||||
export type { FaqItem }
|
||||
Reference in New Issue
Block a user