Accordin

An Accordin componnet that can be used to display frequently asked questions (FAQs) or other collapsible content sections.

Frequently Asked Questions

Installation

First, install the necessary dependencies for the glass panel effect.

tsx
1npm install framer-motion react-icons clsx

Next, copy and paste the following component code into your project.

components/ui/glass-panel.tsx
1'use client';
2
3import { useState, ReactNode } from 'react';
4import { motion, AnimatePresence } from 'framer-motion';
5import { FaChevronDown } from 'react-icons/fa';
6import { clsx } from 'clsx';
7
8export interface FAQItem {
9  question: string;
10  answer: string | ReactNode;
11}
12
13interface AccordionItemProps {
14  item: FAQItem;
15  isOpen: boolean;
16  onClick: () => void;
17  className?: string;
18  questionClassName?: string;
19  answerClassName?: string;
20  icon?: ReactNode;
21  iconClassName?: string;
22}
23
24const AccordionItem = ({
25  item,
26  isOpen,
27  onClick,
28  className,
29  questionClassName,
30  answerClassName,
31  icon,
32  iconClassName,
33}: AccordionItemProps) => (
34  <div className={clsx('border-b border-white/10', className)}>
35    <button
36      onClick={onClick}
37      className="w-full flex justify-between items-center text-left py-6 px-2"
38    >
39      <span className={clsx('text-lg font-medium text-foreground', questionClassName)}>
40        {item.question}
41      </span>
42      <motion.div
43        animate={{ rotate: isOpen ? 180 : 0 }}
44        transition={{ duration: 0.3 }}
45      >
46        {icon || (
47          <FaChevronDown
48            className={clsx('text-primary transition-colors', { 'text-primary': isOpen }, iconClassName)}
49          />
50        )}
51      </motion.div>
52    </button>
53    <AnimatePresence>
54      {isOpen && (
55        <motion.div
56          initial={{ opacity: 0, height: 0 }}
57          animate={{ opacity: 1, height: 'auto' }}
58          exit={{ opacity: 0, height: 0 }}
59          transition={{ duration: 0.3, ease: 'easeInOut' }}
60          className="overflow-hidden"
61        >
62          <div className={clsx('pb-6 px-2 text-muted-foreground leading-relaxed', answerClassName)}>
63            {typeof item.answer === 'string' ? <p>{item.answer}</p> : item.answer}
64          </div>
65        </motion.div>
66      )}
67    </AnimatePresence>
68  </div>
69);
70
71interface AccordionFAQProps {
72  faqItems: FAQItem[];
73  className?: string;
74  itemClassName?: string;
75  questionClassName?: string;
76  answerClassName?: string;
77  icon?: ReactNode;
78  iconClassName?: string;
79  title?: string;
80  titleClassName?: string;
81  showTitle?: boolean;
82}
83
84export const AccordionFAQ = ({
85  faqItems,
86  className,
87  itemClassName,
88  questionClassName,
89  answerClassName,
90  icon,
91  iconClassName,
92  title = 'Frequently Asked Questions',
93  titleClassName,
94  showTitle = true,
95}: AccordionFAQProps) => {
96  const [openIndex, setOpenIndex] = useState<number | null>(null);
97
98  const handleClick = (index: number) => {
99    setOpenIndex(openIndex === index ? null : index);
100  };
101
102  return (
103    <section id="faq" className={clsx('max-w-4xl mx-auto', className)}>
104      {showTitle && (
105        <motion.h2
106          initial={{ opacity: 0, y: 50 }}
107          whileInView={{ opacity: 1, y: 0 }}
108          viewport={{ once: true, amount: 0.5 }}
109          transition={{ duration: 0.8 }}
110          className={clsx(
111            'text-4xl sm:text-5xl font-bold font-display text-center mb-16 tracking-wide',
112            titleClassName
113          )}
114        >
115          {title}
116        </motion.h2>
117      )}
118      <div className="p-4">
119        {faqItems.map((item, index) => (
120          <AccordionItem
121            key={index}
122            item={item}
123            isOpen={openIndex === index}
124            onClick={() => handleClick(index)}
125            className={itemClassName}
126            questionClassName={questionClassName}
127            answerClassName={answerClassName}
128            icon={icon}
129            iconClassName={iconClassName}
130          />
131        ))}
132      </div>
133    </section>
134  );
135};
Popular Components