faq-1

PreviousNext

FAQ grid block with categorized tabs and icons

Docs
smoothuiui

Preview

Loading preview…
index.tsx
"use client";

import {
  Activity,
  Clock,
  DollarSign,
  Download,
  Globe,
  HelpCircle,
  Lock,
  MessageCircle,
  Scale,
  Settings,
  ShoppingBag,
  Undo2,
  Users,
  WalletCards,
  Zap,
} from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";

const ANIMATION_DURATION = 0.3;
const SPRING_STIFFNESS = 500;
const SPRING_DAMPING = 30;
const HOVER_SCALE = 1.02;
const TAP_SCALE = 0.98;
const FAQ_STAGGER_DELAY = 0.1;
const INITIAL_Y_OFFSET = 20;
const EXIT_Y_OFFSET = -20;

const iconMap = {
  Clock,
  WalletCards,
  ShoppingBag,
  Globe,
  Scale,
  Activity,
  DollarSign,
  Lock,
  Undo2,
  Download,
  Settings,
  HelpCircle,
  MessageCircle,
  Users,
  Zap,
};

type FaqsGridProps = {
  title?: string;
  description?: string;
  categories?: Array<{
    name: string;
    id: string;
    faqs: Array<{
      question: string;
      answer: string;
      icon: string;
    }>;
  }>;
};

export function FaqsGrid({
  title = "FAQs",
  description = "Discover quick and comprehensive answers to common questions about our platform, services, and features.",
  categories = [
    {
      name: "General",
      id: "general",
      faqs: [
        {
          question: "How do I install Smoothui?",
          answer:
            "You can install Smoothui using npm or yarn. Run `npm install smoothui` or `yarn add smoothui` in your project directory. It's completely free and open source.",
          icon: "Download",
        },
        {
          question: "What are the system requirements?",
          answer:
            "Smoothui works with React 16.8+ and supports all modern browsers. No additional dependencies are required beyond React and Tailwind CSS.",
          icon: "Settings",
        },
        {
          question: "Is Smoothui free to use?",
          answer:
            "Yes! Smoothui is completely free and open source. You can start using it immediately without any trial limitations or hidden costs.",
          icon: "Zap",
        },
      ],
    },
    {
      name: "Components",
      id: "components",
      faqs: [
        {
          question: "How many components are included?",
          answer:
            "Smoothui includes over 50+ pre-built components including buttons, forms, navigation, modals, animations, and more. New components are added regularly.",
          icon: "HelpCircle",
        },
        {
          question: "Can I customize the styling?",
          answer:
            "Absolutely! All components are fully customizable using CSS variables, Tailwind classes, or custom CSS. You have complete control over the appearance.",
          icon: "Settings",
        },
        {
          question: "Are the components accessible?",
          answer:
            "Yes, all components follow WCAG guidelines and include proper ARIA attributes for screen readers and keyboard navigation. Accessibility is a top priority.",
          icon: "Users",
        },
      ],
    },
    {
      name: "Support",
      id: "support",
      faqs: [
        {
          question: "How can I get help?",
          answer:
            "You can reach out to our community on Discord, GitHub discussions, or email our support team directly. We're here to help you succeed.",
          icon: "MessageCircle",
        },
        {
          question: "Do you offer custom development?",
          answer:
            "Yes, we offer custom component development and consulting services for enterprise clients. Contact us to discuss your specific needs.",
          icon: "Users",
        },
        {
          question: "What's your response time?",
          answer:
            "We typically respond to support requests within 24 hours during business days. For urgent issues, please mark them as high priority.",
          icon: "Clock",
        },
      ],
    },
  ],
}: FaqsGridProps) {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <section className="bg-muted py-16 md:py-24">
      <div className="mx-auto max-w-5xl px-6">
        <div className="max-w-lg">
          <h2 className="font-semibold text-4xl text-foreground">{title}</h2>
          <p className="mt-4 text-balance text-lg text-muted-foreground">
            {description}
          </p>
        </div>

        {/* Tabs Navigation */}
        <div className="mt-8 md:mt-12">
          <div className="flex flex-wrap gap-2 border-border border-b">
            {categories.map((category, index) => (
              <motion.button
                className={`relative rounded-t-lg px-4 py-2 font-medium text-sm transition-all duration-200 ${
                  activeTab === index
                    ? "bg-background text-brand"
                    : "border-transparent text-muted-foreground hover:border-border/50 hover:text-foreground"
                }`}
                key={category.id}
                onClick={() => setActiveTab(index)}
                type="button"
                whileHover={{ scale: HOVER_SCALE }}
                whileTap={{ scale: TAP_SCALE }}
              >
                {category.name}
                {activeTab === index && (
                  <motion.div
                    className="absolute right-0 bottom-0 left-0 h-0.5 rounded-t-full bg-brand"
                    initial={false}
                    layoutId="activeTab"
                    transition={{
                      type: "spring",
                      stiffness: SPRING_STIFFNESS,
                      damping: SPRING_DAMPING,
                    }}
                  />
                )}
              </motion.button>
            ))}
          </div>
        </div>

        {/* Tab Content */}
        <div className="mt-8 md:mt-8">
          <AnimatePresence mode="wait">
            <motion.div
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: EXIT_Y_OFFSET }}
              initial={{ opacity: 0, y: INITIAL_Y_OFFSET }}
              key={activeTab}
              transition={{ duration: ANIMATION_DURATION, ease: "easeInOut" }}
            >
              <div className="space-y-6">
                <dl className="grid gap-12 sm:grid-cols-2 lg:grid-cols-3">
                  {categories[activeTab].faqs.map((faq, faqIndex) => {
                    const IconComponent =
                      iconMap[faq.icon as keyof typeof iconMap] || HelpCircle;

                    return (
                      <motion.div
                        animate={{ opacity: 1, y: 0 }}
                        className="space-y-3"
                        initial={{ opacity: 0, y: INITIAL_Y_OFFSET }}
                        key={`${categories[activeTab].id}-${faqIndex}`}
                        transition={{
                          duration: 0.4,
                          delay: faqIndex * FAQ_STAGGER_DELAY,
                        }}
                      >
                        <div className="flex size-8 items-center justify-center rounded-md border bg-card *:m-auto *:size-4">
                          <IconComponent className="h-4 w-4" />
                        </div>
                        <dt className="font-semibold text-foreground">
                          {faq.question}
                        </dt>
                        <dd className="text-muted-foreground">{faq.answer}</dd>
                      </motion.div>
                    );
                  })}
                </dl>
              </div>
            </motion.div>
          </AnimatePresence>
        </div>
      </div>
    </section>
  );
}

export default FaqsGrid;

Installation

npx shadcn@latest add @smoothui/faq-1

Usage

import { Faq1 } from "@/components/ui/faq-1"
<Faq1 />