Accordin
An Accordin componnet that can be used to display frequently asked questions (FAQs) or other collapsible content sections.
Frequently Asked Questions
tsx
1import { AccordionFAQ, FAQItem } from '@/components/ui/accordion';
2
3const myFaqItems: FAQItem[] = [
4 {
5 question: 'Why should I use this?',
6 answer: 'Okay look, its a really good question. I mean, why not? It\'s free, it\'s easy to use, and it looks great!'
7 },
8 {
9 question: 'Why vote for me?',
10 answer: 'Oh, cool question! but simply look around you, do you see a cool liquid ui library? no? well then you know what to do :)'
11 }
12];
13
14const ComponentPreview = () => (
15 <div>
16 <AccordionFAQ faqItems={myFaqItems} />
17 </div>
18);
19
20export {ComponentPreview};Installation
First, install the necessary dependencies for the glass panel effect.
tsx
1npm install framer-motion react-icons clsxNext, 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};