done landing page

This commit is contained in:
2025-11-10 17:10:34 +05:30
parent 3852d46661
commit 483515e163
105 changed files with 3529 additions and 104 deletions

148
components/ui/accordion.tsx Normal file
View File

@@ -0,0 +1,148 @@
"use client";
import React, { useState, useRef, useEffect } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Plus } from "lucide-react";
import { cn } from "@/lib/utils";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
interface FAQItemType {
question: string;
answer: string;
}
interface FAQProps {
title?: string;
subtitle?: string;
faqData: FAQItemType[];
className?: string;
}
export const FAQ: React.FC<FAQProps> = ({
title = "FAQs",
subtitle = "Frequently Asked Questions",
faqData,
className,
...props
}) => {
const accordionRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const items = accordionRef.current?.querySelectorAll(".accordion-item");
if (items) {
gsap.set(items, {
opacity: 0,
y: 50,
});
ScrollTrigger.batch(items, {
onEnter: (batch) => {
gsap.to(batch, {
opacity: 1,
y: 0,
stagger: 0.12,
duration: 1,
ease: "power1.inOut",
overwrite: true,
});
},
start: "top 90%",
once: true,
});
}
return () => {
ScrollTrigger.getAll().forEach((t) => t.kill());
};
}, []);
return (
<div className={cn("relative", className)} {...props}>
<FAQList faqData={faqData} accordionRef={accordionRef} />
</div>
);
};
const FAQList = ({
faqData,
accordionRef,
}: {
faqData: FAQItemType[];
accordionRef: React.RefObject<HTMLDivElement | null>;
}) => (
<div ref={accordionRef} className="mx-auto max-w-4xl space-y-4">
<AnimatePresence mode="popLayout">
{faqData?.map((faq, index) => (
<FAQItem
key={index}
question={faq.question}
answer={faq.answer}
className="accordion-item"
/>
))}
</AnimatePresence>
</div>
);
const FAQItem = ({
question,
answer,
className,
}: FAQItemType & { className?: string }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<motion.div
animate={isOpen ? "open" : "closed"}
className={cn(
"bg-light-200 border-light-200 rounded-xl border text-black transition-colors",
className
)}
>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex w-full cursor-pointer items-center justify-between gap-4 px-5 py-6 text-left"
>
<span
className={cn(
"font-switzer text-lg font-medium text-black transition-colors",
isOpen ? "text-foreground" : "text-muted-foreground"
)}
>
{question}
</span>
<motion.span
variants={{
open: { rotate: "45deg" },
closed: { rotate: "0deg" },
}}
transition={{ duration: 0.2 }}
>
<Plus
className={cn(
"h-6 w-6 transition-colors",
isOpen ? "text-foreground" : "text-muted-foreground"
)}
/>
</motion.span>
</button>
<motion.div
initial={false}
animate={{
height: isOpen ? "auto" : "0px",
marginBottom: isOpen ? "16px" : "0px",
}}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="overflow-hidden px-4"
>
<p className="font-mium-reg text-sm leading-6 font-normal tracking-[-0.8px]">
{answer}
</p>
</motion.div>
</motion.div>
);
};