Files
gebos-landing/component-library/src/components/solenos/faq-accordion.tsx
T
Lars Nolden 3f21baa13d
Deploy Static Site / deploy (push) Failing after 4m58s
rename
2026-08-14 14:41:46 +02:00

85 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }