Merge version_2 into main #14
349
src/app/page.tsx
349
src/app/page.tsx
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from '@/providers/themeProvider/ThemeProvider';
|
||||
import NavbarStyleFullscreen from '@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen';
|
||||
import HeroBillboardTestimonial from '@/components/sections/hero/HeroBillboardTestimonial';
|
||||
@@ -12,233 +13,183 @@ import TestimonialCardFifteen from '@/components/sections/testimonial/Testimonia
|
||||
import FaqSplitMedia from '@/components/sections/faq/FaqSplitMedia';
|
||||
import ContactCTA from '@/components/sections/contact/ContactCTA';
|
||||
import FooterBase from '@/components/sections/footer/FooterBase';
|
||||
import { Sparkles, Star, Grid, Award, TrendingUp, Briefcase, Mail, HelpCircle, Shirt, Dumbbell, Activity, Zap, Smartphone, Cpu, Home, Sofa, Layout, Heart } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Sparkles, TrendingUp, Users, ArrowRight } from 'lucide-react';
|
||||
|
||||
export default function HomePage() {
|
||||
const plan = {
|
||||
theme: {
|
||||
defaultButtonVariant: "text-stagger" as const,
|
||||
defaultTextAnimation: "entrance-slide" as const,
|
||||
borderRadius: "rounded" as const,
|
||||
contentWidth: "medium" as const,
|
||||
sizing: "medium" as const,
|
||||
background: "circleGradient" as const,
|
||||
cardStyle: "glass-elevated" as const,
|
||||
primaryButtonStyle: "gradient" as const,
|
||||
secondaryButtonStyle: "glass" as const,
|
||||
headingFontWeight: "normal" as const,
|
||||
},
|
||||
};
|
||||
|
||||
const navItems = [
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "About", id: "about" },
|
||||
{ name: "Services", id: "categories" },
|
||||
{ name: "Contact", id: "contact" },
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="text-stagger"
|
||||
defaultTextAnimation="entrance-slide"
|
||||
borderRadius="rounded"
|
||||
contentWidth="medium"
|
||||
sizing="medium"
|
||||
background="circleGradient"
|
||||
cardStyle="glass-elevated"
|
||||
primaryButtonStyle="gradient"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="normal"
|
||||
defaultButtonVariant={plan.theme.defaultButtonVariant}
|
||||
defaultTextAnimation={plan.theme.defaultTextAnimation}
|
||||
borderRadius={plan.theme.borderRadius}
|
||||
contentWidth={plan.theme.contentWidth}
|
||||
sizing={plan.theme.sizing}
|
||||
background={plan.theme.background}
|
||||
cardStyle={plan.theme.cardStyle}
|
||||
primaryButtonStyle={plan.theme.primaryButtonStyle}
|
||||
secondaryButtonStyle={plan.theme.secondaryButtonStyle}
|
||||
headingFontWeight={plan.theme.headingFontWeight}
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleFullscreen
|
||||
navItems={[
|
||||
{ name: "Home", id: "/" },
|
||||
{ name: "Fashion", id: "fashion" },
|
||||
{ name: "Home & Decor", id: "home-category" },
|
||||
{ name: "Gym", id: "gym" },
|
||||
{ name: "Electronics", id: "electronics" },
|
||||
]}
|
||||
brandName="ZSMX Store"
|
||||
bottomLeftText="Premium Multi-Category Store"
|
||||
bottomRightText="hello@zsmxstore.com"
|
||||
/>
|
||||
<NavbarStyleFullscreen navItems={navItems} />
|
||||
</div>
|
||||
|
||||
<div id="hero" data-section="hero">
|
||||
<HeroBillboardTestimonial
|
||||
title="Discover Your Perfect Style"
|
||||
description="Explore our curated collection of fashion, home decor, fitness equipment, and premium electronics. Where quality meets elegance."
|
||||
tag="Welcome to ZSMX Store"
|
||||
tagIcon={Sparkles}
|
||||
tagAnimation="slide-up"
|
||||
background={{ variant: "radial-gradient" }}
|
||||
imageSrc="http://img.b2bpic.net/free-photo/internationals-people-standing-cafe_1157-32402.jpg"
|
||||
imageAlt="Premium multi-category product showcase"
|
||||
mediaAnimation="slide-up"
|
||||
tag="Testimonials"
|
||||
tagIcon={Sparkles}
|
||||
title="What Our Customers Say"
|
||||
description="Hear from our satisfied clients about their experience with our products and services."
|
||||
testimonials={[
|
||||
{
|
||||
name: "Sarah Mitchell", handle: "Fashion Enthusiast", testimonial: "Exceptional quality and stunning designs. ZSMX Store has become my go-to for everything.", rating: 5,
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/portrait-confident-young-businessman-with-his-arms-crossed_23-2148176206.jpg", imageAlt: "Sarah Mitchell"
|
||||
},
|
||||
name: "Sarah Johnson", handle: "@sarahj", testimonial: "Amazing product that transformed our workflow!", rating: 5,
|
||||
imageSrc: "/placeholders/placeholder1.webp"},
|
||||
{
|
||||
name: "James Chen", handle: "Interior Designer", testimonial: "The home collection is absolutely exquisite. Premium pieces that transform any space.", rating: 5,
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/positive-confident-businessman-posing-outside_74855-1183.jpg", imageAlt: "James Chen"
|
||||
},
|
||||
{
|
||||
name: "Emma Rodriguez", handle: "Fitness Coach", testimonial: "Top-tier gym equipment. My clients and I love the durability and design.", rating: 5,
|
||||
imageSrc: "http://img.b2bpic.net/free-photo/modern-businesswoman_23-2148012909.jpg", imageAlt: "Emma Rodriguez"
|
||||
}
|
||||
name: "John Doe", handle: "@johndoe", testimonial: "Great support and excellent service. Highly recommended!", rating: 5,
|
||||
imageSrc: "/placeholders/placeholder2.webp"},
|
||||
]}
|
||||
testimonialRotationInterval={5000}
|
||||
buttons={[
|
||||
{ text: "Shop Now", href: "fashion" },
|
||||
{ text: "Explore Categories", href: "categories" }
|
||||
{ text: "Get Started", href: "/" },
|
||||
{ text: "Learn More", href: "categories" },
|
||||
]}
|
||||
buttonAnimation="slide-up"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="products" data-section="products">
|
||||
<ProductCardTwo
|
||||
title="Featured Collection"
|
||||
description="Hand-picked premium products across all categories. New arrivals updated daily."
|
||||
tag="Best Sellers"
|
||||
tagIcon={Star}
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
animationType="slide-up"
|
||||
gridVariant="bento-grid"
|
||||
useInvertedBackground={false}
|
||||
products={[
|
||||
{
|
||||
id: "fashion-1", brand: "LuxeStyle", name: "Premium Wool Overcoat", price: "$450.00", rating: 5, reviewCount: "342", imageSrc: "http://img.b2bpic.net/free-photo/bag-hanging-from-furniture-item-indoors_23-2151073505.jpg", imageAlt: "Premium Wool Overcoat"
|
||||
},
|
||||
id: "1", brand: "Premium", name: "Eclipse Motion Pro", price: "$150", rating: 5,
|
||||
reviewCount: "128", imageSrc: "/placeholders/placeholder1.webp"},
|
||||
{
|
||||
id: "fashion-2", brand: "ElegantWear", name: "Designer Evening Gown", price: "$680.00", rating: 5, reviewCount: "289", imageSrc: "http://img.b2bpic.net/free-photo/store-customer-holding-shirt-body_482257-85803.jpg", imageAlt: "Designer Evening Gown"
|
||||
},
|
||||
id: "2", brand: "Standard", name: "Wave Dynamics", price: "$99", rating: 4,
|
||||
reviewCount: "95", imageSrc: "/placeholders/placeholder2.webp"},
|
||||
{
|
||||
id: "fashion-3", brand: "ClassicThreads", name: "Italian Leather Shoes", price: "$395.00", rating: 4, reviewCount: "156", imageSrc: "http://img.b2bpic.net/free-photo/still-life-with-classic-shirts_23-2150828626.jpg", imageAlt: "Italian Leather Shoes"
|
||||
},
|
||||
{
|
||||
id: "home-1", brand: "Luxehome", name: "Modern Sectional Sofa", price: "$1,299.00", rating: 5, reviewCount: "201", imageSrc: "http://img.b2bpic.net/free-photo/beautiful-dried-flowers-table_23-2149591635.jpg", imageAlt: "Modern Sectional Sofa"
|
||||
},
|
||||
{
|
||||
id: "home-2", brand: "DecorPremium", name: "Crystal Chandelier", price: "$850.00", rating: 5, reviewCount: "178", imageSrc: "http://img.b2bpic.net/free-photo/couch-with-cushions-glass-table_1203-764.jpg", imageAlt: "Crystal Chandelier"
|
||||
},
|
||||
{
|
||||
id: "home-3", brand: "InteriorLux", name: "Turkish Area Rug", price: "$625.00", rating: 4, reviewCount: "124", imageSrc: "http://img.b2bpic.net/free-photo/cafe-with-coffee-tables-cosy-sofas-plants-shelves_140725-7785.jpg", imageAlt: "Turkish Area Rug"
|
||||
}
|
||||
]}
|
||||
buttons={[
|
||||
{ text: "View All Products", href: "fashion" }
|
||||
id: "3", brand: "Elite", name: "Aurora Series", price: "$199", rating: 5,
|
||||
reviewCount: "156", imageSrc: "/placeholders/placeholder3.webp"},
|
||||
]}
|
||||
gridVariant="three-columns-all-equal-width"
|
||||
animationType="slide-up"
|
||||
title="Featured Products"
|
||||
description="Discover our latest collection of premium products"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="categories" data-section="categories">
|
||||
<FeatureCardTen
|
||||
title="Our Category Showcase"
|
||||
description="Explore our diverse range of premium products across four expertly curated categories. Each collection represents the finest in quality and design."
|
||||
tag="Categories"
|
||||
tagIcon={Grid}
|
||||
tagAnimation="slide-up"
|
||||
features={[
|
||||
{
|
||||
id: "1", title: "Fast Performance", description: "Lightning-fast speeds optimized for your workflow", media: { imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Performance" },
|
||||
items: [
|
||||
{ icon: TrendingUp, text: "10x faster processing" },
|
||||
{ icon: Users, text: "Real-time collaboration" },
|
||||
],
|
||||
reverse: false,
|
||||
},
|
||||
{
|
||||
id: "2", title: "Scalable Solutions", description: "Grow your business without limitations", media: { imageSrc: "/placeholders/placeholder2.webp", imageAlt: "Scalability" },
|
||||
items: [
|
||||
{ icon: ArrowRight, text: "Unlimited growth" },
|
||||
{ icon: Sparkles, text: "Enterprise ready" },
|
||||
],
|
||||
reverse: true,
|
||||
},
|
||||
]}
|
||||
title="Why Choose Us"
|
||||
description="Powerful features designed for success"
|
||||
textboxLayout="default"
|
||||
animationType="slide-up"
|
||||
useInvertedBackground={false}
|
||||
features={[
|
||||
{
|
||||
id: "1", title: "Fashion Excellence", description: "Premium apparel and accessories designed for those who appreciate style. From casual elegance to formal sophistication.", media: { imageSrc: "http://img.b2bpic.net/free-photo/bag-hanging-from-furniture-item-indoors_23-2151073505.jpg" },
|
||||
items: [
|
||||
{ icon: Shirt, text: "Designer Collections" },
|
||||
{ icon: Sparkles, text: "Premium Fabrics" },
|
||||
{ icon: Heart, text: "Timeless Styles" }
|
||||
],
|
||||
reverse: false
|
||||
},
|
||||
{
|
||||
id: "2", title: "Home Furnishings", description: "Transform your living space with luxury home decor. Curated pieces that combine functionality with aesthetic elegance.", media: { imageSrc: "http://img.b2bpic.net/free-photo/beautiful-dried-flowers-table_23-2149591635.jpg" },
|
||||
items: [
|
||||
{ icon: Home, text: "Modern Design" },
|
||||
{ icon: Sofa, text: "Premium Materials" },
|
||||
{ icon: Layout, text: "Expert Curation" }
|
||||
],
|
||||
reverse: true
|
||||
},
|
||||
{
|
||||
id: "3", title: "Fitness & Gym", description: "Professional-grade fitness equipment and apparel. Engineered for performance and durability in every workout.", media: { imageSrc: "http://img.b2bpic.net/free-photo/still-life-perfectly-ordered-fitness-gym-accessories_52683-100705.jpg" },
|
||||
items: [
|
||||
{ icon: Dumbbell, text: "Professional Equipment" },
|
||||
{ icon: Activity, text: "Performance Gear" },
|
||||
{ icon: Zap, text: "High Durability" }
|
||||
],
|
||||
reverse: false
|
||||
},
|
||||
{
|
||||
id: "4", title: "Premium Electronics", description: "Latest technology and innovative gadgets. Cutting-edge devices that enhance your digital lifestyle.", media: { imageSrc: "http://img.b2bpic.net/free-photo/view-robotic-vacuum-cleaner-flat-surface_23-2151736769.jpg" },
|
||||
items: [
|
||||
{ icon: Smartphone, text: "Latest Technology" },
|
||||
{ icon: Cpu, text: "Advanced Features" },
|
||||
{ icon: Zap, text: "Top Performance" }
|
||||
],
|
||||
reverse: true
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="about" data-section="about">
|
||||
<MetricSplitMediaAbout
|
||||
title="Your Trusted Multi-Category Destination"
|
||||
description="ZSMX Store is your premier destination for premium products across fashion, home, fitness, and electronics. We believe in delivering excellence through carefully curated collections, exceptional quality, and outstanding customer service. Our mission is to make luxury and quality accessible to everyone."
|
||||
tag="About ZSMX"
|
||||
tagIcon={Award}
|
||||
tagAnimation="slide-up"
|
||||
title="About Our Company"
|
||||
description="We\'re dedicated to delivering excellence and innovation in everything we do."
|
||||
metrics={[
|
||||
{ value: "50k+", title: "Satisfied Customers" },
|
||||
{ value: "10k+", title: "Premium Products" }
|
||||
{ value: "10+", title: "Years Experience" },
|
||||
{ value: "500+", title: "Happy Clients" },
|
||||
{ value: "50M+", title: "Users Worldwide" },
|
||||
{ value: "99.9%", title: "Uptime" },
|
||||
]}
|
||||
imageSrc="http://img.b2bpic.net/free-photo/modern-sauna-with-panoramic-windows-wooden-design_169016-70021.jpg"
|
||||
imageAlt="ZSMX Store - Premium retail environment"
|
||||
useInvertedBackground={true}
|
||||
imageSrc="/placeholders/placeholder1.webp"
|
||||
imageAlt="About us"
|
||||
mediaAnimation="slide-up"
|
||||
metricsAnimation="slide-up"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="metrics" data-section="metrics">
|
||||
<MetricCardSeven
|
||||
title="By The Numbers"
|
||||
description="Trusted by thousands of customers worldwide. Our commitment to quality and service speaks for itself."
|
||||
tag="Our Growth"
|
||||
tagIcon={TrendingUp}
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
animationType="slide-up"
|
||||
useInvertedBackground={false}
|
||||
metrics={[
|
||||
{
|
||||
id: "1", value: "98%", title: "Customer Satisfaction Rate", items: ["Premium quality guaranteed", "Expert curation", "Dedicated support team"]
|
||||
id: "1", value: "7,000+", title: "Conversions", items: ["Increased by 45%", "Monthly growth"],
|
||||
},
|
||||
{
|
||||
id: "2", value: "24/7", title: "Customer Support Available", items: ["Real-time assistance", "Expert consultations", "Fast responses"]
|
||||
id: "2", value: "50,000+", title: "Active Users", items: ["Growing daily", "Engaged community"],
|
||||
},
|
||||
{
|
||||
id: "3", value: "100%", title: "Authentic Products", items: ["Verified sources", "Quality assurance", "Brand authenticity"]
|
||||
id: "3", value: "$2.5M", title: "Revenue", items: ["Year-over-year", "Consistent growth"],
|
||||
},
|
||||
{
|
||||
id: "4", value: "Free", title: "Shipping On Orders Over $100", items: ["Fast delivery", "Tracking included", "Safe packaging"]
|
||||
}
|
||||
id: "4", value: "99.9%", title: "Uptime", items: ["24/7 monitoring", "Reliable service"],
|
||||
},
|
||||
]}
|
||||
animationType="slide-up"
|
||||
title="Performance Metrics"
|
||||
description="See how we\'re making a difference"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="social-proof" data-section="social-proof">
|
||||
<SocialProofOne
|
||||
title="Trusted by Leading Brands & Retailers"
|
||||
description="Partnered with premium brands worldwide to bring you authentic luxury products."
|
||||
tag="Our Partners"
|
||||
tagIcon={Briefcase}
|
||||
tagAnimation="slide-up"
|
||||
names={["Company A", "Company B", "Company C", "Company D", "Company E"]}
|
||||
title="Trusted by Leading Companies"
|
||||
description="Join thousands of businesses using our platform"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
names={["LuxeStyle", "ElegantWear", "ClassicThreads", "Luxehome", "DecorPremium", "InteriorLux", "FitnessPro", "SportsTech"]}
|
||||
speed={40}
|
||||
showCard={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="testimonials" data-section="testimonials">
|
||||
<TestimonialCardFifteen
|
||||
testimonial="ZSMX Store has completely revolutionized how I shop online. The selection is incredible, the quality is unmatched, and the customer service is exceptional. I've purchased from all four categories and been amazed every single time."
|
||||
testimonial="This platform has completely transformed how we manage our business. The support team is exceptional!"
|
||||
rating={5}
|
||||
author="Victoria Thompson, Premium Lifestyle Enthusiast"
|
||||
author="Jane Smith"
|
||||
avatars={[
|
||||
{ src: "http://img.b2bpic.net/free-photo/portrait-confident-young-businessman-with-his-arms-crossed_23-2148176206.jpg", alt: "Customer 1" },
|
||||
{ src: "http://img.b2bpic.net/free-photo/positive-confident-businessman-posing-outside_74855-1183.jpg", alt: "Customer 2" },
|
||||
{ src: "http://img.b2bpic.net/free-photo/modern-businesswoman_23-2148012909.jpg", alt: "Customer 3" },
|
||||
{ src: "http://img.b2bpic.net/free-photo/businessman-formal-wear-professional-corporate-concept_53876-71166.jpg", alt: "Customer 4" },
|
||||
{ src: "http://img.b2bpic.net/free-photo/beautiful-business-woman-portrait_23-2149280717.jpg", alt: "Customer 5" },
|
||||
{ src: "http://img.b2bpic.net/free-photo/portrait-outdoors-business-man-smiles_23-2148763856.jpg", alt: "Customer 6" }
|
||||
{ src: "/placeholders/placeholder1.webp", alt: "Avatar 1" },
|
||||
{ src: "/placeholders/placeholder2.webp", alt: "Avatar 2" },
|
||||
{ src: "/placeholders/placeholder3.webp", alt: "Avatar 3" },
|
||||
]}
|
||||
ratingAnimation="slide-up"
|
||||
avatarsAnimation="slide-up"
|
||||
@@ -248,89 +199,67 @@ export default function HomePage() {
|
||||
|
||||
<div id="faq" data-section="faq">
|
||||
<FaqSplitMedia
|
||||
title="Frequently Asked Questions"
|
||||
description="Find answers to common questions about our products, ordering, shipping, and customer service."
|
||||
tag="Help Center"
|
||||
tagIcon={HelpCircle}
|
||||
tagAnimation="slide-up"
|
||||
textboxLayout="default"
|
||||
useInvertedBackground={false}
|
||||
faqs={[
|
||||
{
|
||||
id: "1", title: "Are all products authentic and guaranteed?", content: "Yes, absolutely. We source directly from authorized distributors and verify authenticity of all products. Every item comes with our quality guarantee and certification of authenticity."
|
||||
},
|
||||
id: "1", title: "How do I get started?", content: "Getting started is easy. Sign up for an account, choose your plan, and start using our platform immediately."},
|
||||
{
|
||||
id: "2", title: "What is your return and exchange policy?", content: "We offer hassle-free returns and exchanges within 30 days of purchase. Items must be unused and in original packaging. Simply contact our customer service team to initiate the process."
|
||||
},
|
||||
id: "2", title: "What is your support policy?", content: "We offer 24/7 customer support via email, chat, and phone. Our average response time is under 2 hours."},
|
||||
{
|
||||
id: "3", title: "How long does shipping typically take?", content: "Standard shipping takes 5-7 business days. Express shipping options (2-3 days) are available for most orders. Orders over $100 qualify for free standard shipping."
|
||||
},
|
||||
{
|
||||
id: "4", title: "Do you offer international shipping?", content: "Yes, we ship to most countries worldwide. International shipping costs vary by location and are calculated at checkout. Customs duties may apply depending on your country."
|
||||
},
|
||||
{
|
||||
id: "5", title: "Is my personal information secure?", content: "We use industry-standard SSL encryption to protect all personal and payment information. Your data is never shared with third parties. We comply with all privacy regulations."
|
||||
},
|
||||
{
|
||||
id: "6", title: "What payment methods do you accept?", content: "We accept all major credit cards, debit cards, PayPal, Apple Pay, and Google Pay. All transactions are secure and encrypted."
|
||||
}
|
||||
id: "3", title: "Can I cancel anytime?", content: "Yes, you can cancel your subscription at any time. No long-term contracts or hidden fees."},
|
||||
]}
|
||||
imageSrc="http://img.b2bpic.net/free-photo/woman-sitting-wheelchair-modern-concept_23-2148497283.jpg"
|
||||
imageAlt="Customer service support team"
|
||||
imageSrc="/placeholders/placeholder1.webp"
|
||||
imageAlt="FAQ"
|
||||
mediaAnimation="slide-up"
|
||||
mediaPosition="right"
|
||||
title="Frequently Asked Questions"
|
||||
description="Find answers to common questions"
|
||||
textboxLayout="default"
|
||||
faqsAnimation="slide-up"
|
||||
mediaPosition="left"
|
||||
animationType="smooth"
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="contact" data-section="contact">
|
||||
<ContactCTA
|
||||
tag="Get In Touch"
|
||||
tagIcon={Mail}
|
||||
tagAnimation="slide-up"
|
||||
title="Ready to Discover Premium Products?"
|
||||
description="Have questions about our products or services? Our expert team is here to help. Contact us today and experience the ZSMX Store difference."
|
||||
tag="Get in Touch"
|
||||
title="Ready to Get Started?"
|
||||
description="Contact us today to learn how we can help you achieve your goals."
|
||||
buttons={[
|
||||
{ text: "Contact Our Team", href: "#contact" },
|
||||
{ text: "Shop Now", href: "fashion" }
|
||||
{ text: "Contact Us", href: "#" },
|
||||
{ text: "Schedule Demo", href: "#" },
|
||||
]}
|
||||
buttonAnimation="slide-up"
|
||||
background={{ variant: "plain" }}
|
||||
background={{ variant: "radial-gradient" }}
|
||||
useInvertedBackground={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
logoText="ZSMX Store"
|
||||
copyrightText="© 2025 ZSMX Store. All rights reserved."
|
||||
columns={[
|
||||
{
|
||||
title: "Shop", items: [
|
||||
{ label: "Fashion", href: "fashion" },
|
||||
{ label: "Home", href: "home-category" },
|
||||
{ label: "Gym", href: "gym" },
|
||||
{ label: "Electronics", href: "electronics" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Support", items: [
|
||||
{ label: "Contact Us", href: "#contact" },
|
||||
{ label: "FAQ", href: "#faq" },
|
||||
{ label: "Shipping Info", href: "#" },
|
||||
{ label: "Returns", href: "#" }
|
||||
]
|
||||
title: "Product", items: [
|
||||
{ label: "Features", href: "categories" },
|
||||
{ label: "Pricing", href: "#" },
|
||||
{ label: "Security", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company", items: [
|
||||
{ label: "About Us", href: "#about" },
|
||||
{ label: "About", href: "about" },
|
||||
{ label: "Blog", href: "#" },
|
||||
{ label: "Careers", href: "#" },
|
||||
{ label: "Privacy Policy", href: "#" }
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Legal", items: [
|
||||
{ label: "Privacy", href: "#" },
|
||||
{ label: "Terms", href: "#" },
|
||||
{ label: "Contact", href: "contact" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
logoText="Webild"
|
||||
copyrightText="© 2025 | Webild"
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useCardAnimation } from './hooks/useCardAnimation';
|
||||
import type { CardAnimationConfig } from './types';
|
||||
import React from 'react';
|
||||
|
||||
interface CardListProps {
|
||||
cards: React.ReactNode[];
|
||||
animationConfig: CardAnimationConfig;
|
||||
className?: string;
|
||||
export interface CardListProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const CardList: React.FC<CardListProps> = ({
|
||||
cards,
|
||||
animationConfig,
|
||||
className = '',
|
||||
}) => {
|
||||
const cardsRef = useRef<HTMLDivElement[]>([]);
|
||||
|
||||
useCardAnimation(cardsRef, animationConfig);
|
||||
|
||||
const setCardRef = useCallback((index: number, el: HTMLDivElement | null) => {
|
||||
if (el) {
|
||||
cardsRef.current[index] = el;
|
||||
}
|
||||
}, []);
|
||||
|
||||
export const CardList: React.FC<CardListProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div className={`card-list ${className}`}>
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={el => setCardRef(index, el)}
|
||||
className="card-item"
|
||||
>
|
||||
{card}
|
||||
</div>
|
||||
))}
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardList;
|
||||
|
||||
@@ -1,42 +1,20 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import TimelineBase from './layouts/timelines/TimelineBase';
|
||||
|
||||
interface TimelineItem {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon?: React.ReactNode;
|
||||
export interface CardStackProps {
|
||||
children?: React.ReactNode;
|
||||
items?: any[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface CardStackProps {
|
||||
items: TimelineItem[];
|
||||
className?: string;
|
||||
itemClassName?: string;
|
||||
connectorClassName?: string;
|
||||
contentClassName?: string;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
const CardStack: React.FC<CardStackProps> = ({
|
||||
items,
|
||||
className = '',
|
||||
itemClassName = '',
|
||||
connectorClassName = '',
|
||||
contentClassName = '',
|
||||
ariaLabel = 'Card stack',
|
||||
}) => {
|
||||
export const CardStack: React.FC<CardStackProps> = ({ children, items, ...props }) => {
|
||||
return (
|
||||
<div className={`space-y-8 ${className}`} aria-label={ariaLabel}>
|
||||
<TimelineBase
|
||||
items={items}
|
||||
itemClassName={itemClassName}
|
||||
connectorClassName={connectorClassName}
|
||||
contentClassName={contentClassName}
|
||||
/>
|
||||
<div {...props}>
|
||||
{children}
|
||||
{items && items.map((item: any, idx: number) => (
|
||||
<div key={idx}>{JSON.stringify(item)}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardStack;
|
||||
export default CardStack;
|
||||
|
||||
@@ -1,92 +1,16 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
|
||||
import { memo, useMemo } from "react";
|
||||
import TextBox from "@/components/Textbox";
|
||||
import { cls } from "@/lib/utils";
|
||||
import type { TextBoxProps } from "./types";
|
||||
export interface CardStackTextBoxProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const CardStackTextBox = ({
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
tagAnimation,
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
}: TextBoxProps) => {
|
||||
const styles = useMemo(() => {
|
||||
if (textboxLayout === "default") {
|
||||
return {
|
||||
className: cls("flex flex-col gap-3 md:gap-2", textBoxClassName),
|
||||
titleClassName: cls("text-6xl font-medium text-center", titleClassName),
|
||||
descriptionClassName: cls("text-lg leading-tight text-center md:max-w-6/10", descriptionClassName),
|
||||
tagClassName: cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-0 mx-auto", tagClassName),
|
||||
buttonContainerClassName: cls("flex flex-wrap gap-4 max-md:justify-center mt-1 md:mt-3 justify-center", buttonContainerClassName),
|
||||
center: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (textboxLayout === "inline-image") {
|
||||
return {
|
||||
className: cls("flex flex-col gap-3 md:gap-2", textBoxClassName),
|
||||
titleClassName: cls("text-4xl md:text-5xl font-medium text-center", titleClassName),
|
||||
descriptionClassName: cls("text-lg leading-tight text-center", descriptionClassName),
|
||||
tagClassName: cls("w-fit px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-0 mx-auto", tagClassName),
|
||||
buttonContainerClassName: cls("flex flex-wrap gap-4 max-md:justify-center mt-1 md:mt-3 justify-center", buttonContainerClassName),
|
||||
center: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
className: textBoxClassName,
|
||||
titleClassName: cls("text-6xl font-medium", titleClassName),
|
||||
descriptionClassName: cls("text-lg leading-tight", descriptionClassName),
|
||||
tagClassName: cls("px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2", tagClassName),
|
||||
buttonContainerClassName: cls("flex flex-wrap gap-4 max-md:justify-center", buttonContainerClassName),
|
||||
center: false,
|
||||
};
|
||||
}, [textboxLayout, textBoxClassName, titleClassName, descriptionClassName, tagClassName, buttonContainerClassName]);
|
||||
|
||||
if (!title && !titleSegments && !description) return null;
|
||||
|
||||
return (
|
||||
<TextBox
|
||||
title={title!}
|
||||
titleSegments={titleSegments}
|
||||
description={description!}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
tagAnimation={tagAnimation}
|
||||
buttons={buttons}
|
||||
buttonAnimation={buttonAnimation}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
className={styles.className}
|
||||
titleClassName={styles.titleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={styles.descriptionClassName}
|
||||
tagClassName={styles.tagClassName}
|
||||
buttonContainerClassName={styles.buttonContainerClassName}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={buttonTextClassName}
|
||||
center={styles.center}
|
||||
/>
|
||||
);
|
||||
export const CardStackTextBox: React.FC<CardStackTextBoxProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CardStackTextBox.displayName = "CardStackTextBox";
|
||||
|
||||
export default memo(CardStackTextBox);
|
||||
export default CardStackTextBox;
|
||||
|
||||
@@ -1,144 +1,16 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
|
||||
import { memo, Children, useCallback, useEffect, useState } from "react";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { EmblaCarouselType } from "embla-carousel";
|
||||
import CardStackTextBox from "../../CardStackTextBox";
|
||||
import { cls } from "@/lib/utils";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { ArrowCarouselProps } from "../../types";
|
||||
export interface ArrowCarouselProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const ArrowCarousel = ({
|
||||
children,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
tagAnimation,
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
textboxLayout = "default",
|
||||
useInvertedBackground,
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
carouselClassName = "",
|
||||
controlsClassName = "",
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
ariaLabel = "Carousel section",
|
||||
}: ArrowCarouselProps) => {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: "center" });
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const childrenArray = Children.toArray(children);
|
||||
|
||||
const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
|
||||
setSelectedIndex(emblaApi.selectedScrollSnap());
|
||||
}, []);
|
||||
|
||||
const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);
|
||||
const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
|
||||
onSelect(emblaApi);
|
||||
emblaApi.on("select", onSelect).on("reInit", onSelect);
|
||||
|
||||
return () => {
|
||||
emblaApi.off("select", onSelect).off("reInit", onSelect);
|
||||
};
|
||||
}, [emblaApi, onSelect]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cls(
|
||||
"relative py-20 w-full",
|
||||
useInvertedBackground && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<div className={cls("w-full mx-auto flex flex-col gap-6", containerClassName)}>
|
||||
<div className="w-content-width mx-auto">
|
||||
<CardStackTextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
tagAnimation={tagAnimation}
|
||||
buttons={buttons}
|
||||
buttonAnimation={buttonAnimation}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={titleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={descriptionClassName}
|
||||
tagClassName={tagClassName}
|
||||
buttonContainerClassName={buttonContainerClassName}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={buttonTextClassName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full">
|
||||
<div
|
||||
className={cls(
|
||||
"overflow-hidden w-full relative z-10 mask-fade-x",
|
||||
carouselClassName
|
||||
)}
|
||||
ref={emblaRef}
|
||||
>
|
||||
<div className="flex w-full">
|
||||
{childrenArray.map((child, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex-none w-60 md:w-40 mr-6"
|
||||
>
|
||||
<div className={cls(
|
||||
"transition-all duration-500 ease-out",
|
||||
selectedIndex === index ? "opacity-100 scale-100" : "opacity-70 scale-90"
|
||||
)}>
|
||||
{child}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cls("absolute inset-y-0 w-content-width mx-auto left-0 right-0 flex items-center justify-between pointer-events-none z-10", controlsClassName)}>
|
||||
<button
|
||||
onClick={scrollPrev}
|
||||
className="pointer-events-auto primary-button h-8 w-auto aspect-square rounded-theme flex items-center justify-center cursor-pointer"
|
||||
aria-label="Previous slide"
|
||||
>
|
||||
<ChevronLeft className="w-4/10 h-4/10 text-primary-cta-text" />
|
||||
</button>
|
||||
<button
|
||||
onClick={scrollNext}
|
||||
className="pointer-events-auto primary-button h-8 w-auto aspect-square rounded-theme flex items-center justify-center cursor-pointer"
|
||||
aria-label="Next slide"
|
||||
>
|
||||
<ChevronRight className="w-4/10 h-4/10 text-primary-cta-text" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
export const ArrowCarousel: React.FC<ArrowCarouselProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ArrowCarousel.displayName = "ArrowCarousel";
|
||||
|
||||
export default memo(ArrowCarousel);
|
||||
export default ArrowCarousel;
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useCardAnimation } from '../../hooks/useCardAnimation';
|
||||
import type { CardAnimationConfig } from '../../types';
|
||||
import React from 'react';
|
||||
|
||||
interface AutoCarouselProps {
|
||||
items: React.ReactNode[];
|
||||
animationConfig: CardAnimationConfig;
|
||||
className?: string;
|
||||
export interface AutoCarouselProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const AutoCarousel: React.FC<AutoCarouselProps> = ({
|
||||
items,
|
||||
animationConfig,
|
||||
className = '',
|
||||
}) => {
|
||||
const cardsRef = useRef<HTMLDivElement[]>([]);
|
||||
|
||||
useCardAnimation(cardsRef, animationConfig);
|
||||
|
||||
const setCardRef = useCallback((index: number, el: HTMLDivElement | null) => {
|
||||
if (el) {
|
||||
cardsRef.current[index] = el;
|
||||
}
|
||||
}, []);
|
||||
|
||||
export const AutoCarousel: React.FC<AutoCarouselProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div className={`auto-carousel ${className}`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={el => setCardRef(index, el)}
|
||||
className="carousel-item"
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoCarousel;
|
||||
|
||||
@@ -1,155 +1,16 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
|
||||
import { memo, Children, cloneElement, isValidElement, useCallback, useEffect, useState } from "react";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { EmblaCarouselType } from "embla-carousel";
|
||||
import CardStackTextBox from "../../CardStackTextBox";
|
||||
import { cls } from "@/lib/utils";
|
||||
import { FullWidthCarouselProps } from "../../types";
|
||||
export interface FullWidthCarouselProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const FullWidthCarousel = ({
|
||||
children,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
tagAnimation,
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
textboxLayout = "default",
|
||||
useInvertedBackground,
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
carouselClassName = "",
|
||||
dotsClassName = "",
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
ariaLabel = "Carousel section",
|
||||
}: FullWidthCarouselProps) => {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: "center" });
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const childrenArray = Children.toArray(children);
|
||||
|
||||
const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
|
||||
setSelectedIndex(emblaApi.selectedScrollSnap());
|
||||
}, []);
|
||||
|
||||
const scrollTo = useCallback(
|
||||
(index: number) => {
|
||||
if (!emblaApi) return;
|
||||
emblaApi.scrollTo(index);
|
||||
},
|
||||
[emblaApi]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
|
||||
onSelect(emblaApi);
|
||||
emblaApi.on("select", onSelect).on("reInit", onSelect);
|
||||
|
||||
return () => {
|
||||
emblaApi.off("select", onSelect).off("reInit", onSelect);
|
||||
};
|
||||
}, [emblaApi, onSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
|
||||
const autoplay = setInterval(() => {
|
||||
emblaApi.scrollNext();
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(autoplay);
|
||||
}, [emblaApi]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cls(
|
||||
"relative py-20 w-full",
|
||||
useInvertedBackground && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<div className={cls("w-full mx-auto flex flex-col gap-6", containerClassName)}>
|
||||
<div className="w-content-width mx-auto">
|
||||
<CardStackTextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
tagAnimation={tagAnimation}
|
||||
buttons={buttons}
|
||||
buttonAnimation={buttonAnimation}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={titleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={descriptionClassName}
|
||||
tagClassName={tagClassName}
|
||||
buttonContainerClassName={buttonContainerClassName}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={buttonTextClassName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div
|
||||
className={cls(
|
||||
"overflow-hidden w-full relative z-10",
|
||||
carouselClassName
|
||||
)}
|
||||
ref={emblaRef}
|
||||
>
|
||||
<div className="flex w-full">
|
||||
{Children.map(childrenArray, (child, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex-none w-70 mr-6"
|
||||
>
|
||||
{isValidElement(child)
|
||||
? cloneElement(child, { isActive: selectedIndex === index } as Record<string, unknown>)
|
||||
: child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cls("flex items-center justify-center gap-2", dotsClassName)}>
|
||||
{childrenArray.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={() => scrollTo(index)}
|
||||
className={cls(
|
||||
"relative cursor-pointer h-2 rounded-theme bg-accent transition-all duration-300",
|
||||
selectedIndex === index
|
||||
? "w-8 opacity-100"
|
||||
: "w-2 opacity-20"
|
||||
)}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
aria-current={selectedIndex === index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
export const FullWidthCarousel: React.FC<FullWidthCarouselProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FullWidthCarousel.displayName = "FullWidthCarousel";
|
||||
|
||||
export default memo(FullWidthCarousel);
|
||||
export default FullWidthCarousel;
|
||||
|
||||
@@ -1,147 +1,16 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
|
||||
import React, { useEffect, useRef, memo, Children } from "react";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
import CardStackTextBox from "../../CardStackTextBox";
|
||||
import { cls } from "@/lib/utils";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, ButtonAnimationType, TitleSegment } from "../../types";
|
||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
interface TimelineCardStackProps {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
tagAnimation?: ButtonAnimationType;
|
||||
buttons?: ButtonConfig[];
|
||||
buttonAnimation?: ButtonAnimationType;
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground?: InvertedBackground;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
titleClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
tagClassName?: string;
|
||||
buttonContainerClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
ariaLabel?: string;
|
||||
export interface TimelineCardStackProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const TimelineCardStack = ({
|
||||
children,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
tagAnimation,
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
ariaLabel = "Timeline section",
|
||||
}: TimelineCardStackProps) => {
|
||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const childrenArray = Children.toArray(children);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = gsap.context(() => {
|
||||
itemRefs.current.forEach((ref, position) => {
|
||||
if (!ref) return;
|
||||
|
||||
const isLast = position === itemRefs.current.length - 1;
|
||||
|
||||
const timeline = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: ref,
|
||||
start: "center center",
|
||||
end: "+=100%",
|
||||
scrub: true,
|
||||
},
|
||||
});
|
||||
|
||||
timeline.set(ref, { willChange: "opacity" }).to(ref, {
|
||||
ease: "none",
|
||||
opacity: isLast ? 1 : 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
ctx.revert();
|
||||
};
|
||||
}, [childrenArray.length]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cls(
|
||||
"relative overflow-visible h-fit py-20 w-full",
|
||||
useInvertedBackground && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<div className={cls("w-content-width mx-auto flex flex-col gap-6", containerClassName)}>
|
||||
<CardStackTextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
tagAnimation={tagAnimation}
|
||||
buttons={buttons}
|
||||
buttonAnimation={buttonAnimation}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={titleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={descriptionClassName}
|
||||
tagClassName={tagClassName}
|
||||
buttonContainerClassName={buttonContainerClassName}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={buttonTextClassName}
|
||||
/>
|
||||
<div className="w-full flex flex-col gap-[var(--width-25)] md:gap-[6.25vh]">
|
||||
{Children.map(childrenArray, (child, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={(el) => {
|
||||
itemRefs.current[index] = el;
|
||||
}}
|
||||
className="!sticky w-full card backdrop-blur-xs rounded-theme-capped h-[140vw] md:h-[75vh] top-[25vw] md:top-[12.5vh]"
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
export const TimelineCardStack: React.FC<TimelineCardStackProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TimelineCardStack.displayName = "TimelineCardStack";
|
||||
|
||||
export default memo(TimelineCardStack);
|
||||
export default TimelineCardStack;
|
||||
|
||||
@@ -1,175 +1,16 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
|
||||
import React, { Children, useCallback } from "react";
|
||||
import { cls } from "@/lib/utils";
|
||||
import CardStackTextBox from "../../CardStackTextBox";
|
||||
import { useTimelineHorizontal, type MediaItem } from "../../hooks/useTimelineHorizontal";
|
||||
import MediaContent from "@/components/shared/MediaContent";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ButtonConfig, ButtonAnimationType, TitleSegment, TextboxLayout, InvertedBackground } from "../../types";
|
||||
|
||||
interface TimelineHorizontalCardStackProps {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
titleSegments?: TitleSegment[];
|
||||
description: string;
|
||||
tag?: string;
|
||||
tagIcon?: LucideIcon;
|
||||
tagAnimation?: ButtonAnimationType;
|
||||
buttons?: ButtonConfig[];
|
||||
buttonAnimation?: ButtonAnimationType;
|
||||
textboxLayout: TextboxLayout;
|
||||
useInvertedBackground?: InvertedBackground;
|
||||
mediaItems?: MediaItem[];
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
textBoxClassName?: string;
|
||||
titleClassName?: string;
|
||||
titleImageWrapperClassName?: string;
|
||||
titleImageClassName?: string;
|
||||
descriptionClassName?: string;
|
||||
tagClassName?: string;
|
||||
buttonContainerClassName?: string;
|
||||
buttonClassName?: string;
|
||||
buttonTextClassName?: string;
|
||||
cardClassName?: string;
|
||||
progressBarClassName?: string;
|
||||
mediaContainerClassName?: string;
|
||||
mediaClassName?: string;
|
||||
ariaLabel?: string;
|
||||
export interface TimelineHorizontalCardStackProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const TimelineHorizontalCardStack = ({
|
||||
children,
|
||||
title,
|
||||
titleSegments,
|
||||
description,
|
||||
tag,
|
||||
tagIcon,
|
||||
tagAnimation,
|
||||
buttons,
|
||||
buttonAnimation,
|
||||
textboxLayout,
|
||||
useInvertedBackground,
|
||||
mediaItems,
|
||||
className = "",
|
||||
containerClassName = "",
|
||||
textBoxClassName = "",
|
||||
titleClassName = "",
|
||||
titleImageWrapperClassName = "",
|
||||
titleImageClassName = "",
|
||||
descriptionClassName = "",
|
||||
tagClassName = "",
|
||||
buttonContainerClassName = "",
|
||||
buttonClassName = "",
|
||||
buttonTextClassName = "",
|
||||
cardClassName = "",
|
||||
progressBarClassName = "",
|
||||
mediaContainerClassName = "",
|
||||
mediaClassName = "",
|
||||
ariaLabel = "Timeline section",
|
||||
}: TimelineHorizontalCardStackProps) => {
|
||||
const childrenArray = Children.toArray(children);
|
||||
const itemCount = childrenArray.length;
|
||||
|
||||
const { activeIndex, progressRefs, handleItemClick, imageOpacity, currentMediaSrc } = useTimelineHorizontal({
|
||||
itemCount,
|
||||
mediaItems,
|
||||
});
|
||||
|
||||
const getGridColumns = useCallback(() => {
|
||||
if (itemCount === 2) return "md:grid-cols-2";
|
||||
if (itemCount === 3) return "md:grid-cols-3";
|
||||
return "md:grid-cols-4";
|
||||
}, [itemCount]);
|
||||
|
||||
const getItemOpacity = useCallback(
|
||||
(index: number) => {
|
||||
return index <= activeIndex ? "opacity-100" : "opacity-50";
|
||||
},
|
||||
[activeIndex]
|
||||
);
|
||||
|
||||
export const TimelineHorizontalCardStack: React.FC<TimelineHorizontalCardStackProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<section
|
||||
className={cls(
|
||||
"relative py-20 w-full",
|
||||
useInvertedBackground && "bg-foreground",
|
||||
className
|
||||
)}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
<div className={cls("w-content-width mx-auto flex flex-col gap-6", containerClassName)}>
|
||||
<CardStackTextBox
|
||||
title={title}
|
||||
titleSegments={titleSegments}
|
||||
description={description}
|
||||
tag={tag}
|
||||
tagIcon={tagIcon}
|
||||
tagAnimation={tagAnimation}
|
||||
buttons={buttons}
|
||||
buttonAnimation={buttonAnimation}
|
||||
textboxLayout={textboxLayout}
|
||||
useInvertedBackground={useInvertedBackground}
|
||||
textBoxClassName={textBoxClassName}
|
||||
titleClassName={titleClassName}
|
||||
titleImageWrapperClassName={titleImageWrapperClassName}
|
||||
titleImageClassName={titleImageClassName}
|
||||
descriptionClassName={descriptionClassName}
|
||||
tagClassName={tagClassName}
|
||||
buttonContainerClassName={buttonContainerClassName}
|
||||
buttonClassName={buttonClassName}
|
||||
buttonTextClassName={buttonTextClassName}
|
||||
/>
|
||||
{mediaItems && mediaItems.length > 0 && (
|
||||
<div className={cls("relative card rounded-theme-capped overflow-hidden aspect-square md:aspect-[17/9]", mediaContainerClassName)}>
|
||||
<div
|
||||
className="absolute inset-6 z-1 transition-opacity duration-300 overflow-hidden"
|
||||
style={{ opacity: imageOpacity }}
|
||||
>
|
||||
<MediaContent
|
||||
imageSrc={currentMediaSrc.imageSrc}
|
||||
videoSrc={currentMediaSrc.videoSrc}
|
||||
imageAlt={mediaItems[activeIndex]?.imageAlt}
|
||||
videoAriaLabel={mediaItems[activeIndex]?.videoAriaLabel}
|
||||
imageClassName={cls("w-full h-full object-cover", mediaClassName)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={cls("relative grid grid-cols-1 gap-6 md:gap-6", getGridColumns())}>
|
||||
{Children.map(childrenArray, (child, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cls(
|
||||
"card rounded-theme-capped p-6 flex flex-col justify-between gap-6 transition-all duration-300",
|
||||
index === activeIndex ? "cursor-default" : "cursor-pointer hover:shadow-lg",
|
||||
getItemOpacity(index),
|
||||
cardClassName
|
||||
)}
|
||||
onClick={() => handleItemClick(index)}
|
||||
>
|
||||
{child}
|
||||
<div className="relative w-full h-px overflow-hidden">
|
||||
<div className="absolute z-0 w-full h-full bg-foreground/20" />
|
||||
<div
|
||||
ref={(el) => {
|
||||
if (el !== null) {
|
||||
progressRefs.current[index] = el;
|
||||
}
|
||||
}}
|
||||
className={cls("absolute z-10 h-full w-full bg-foreground origin-left", progressBarClassName)}
|
||||
style={{ transform: "scaleX(0)" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TimelineHorizontalCardStack.displayName = "TimelineHorizontalCardStack";
|
||||
|
||||
export default React.memo(TimelineHorizontalCardStack);
|
||||
export default TimelineHorizontalCardStack;
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useCardAnimation } from '../../hooks/useCardAnimation';
|
||||
import type { CardAnimationConfig } from '../../types';
|
||||
import React from 'react';
|
||||
|
||||
interface TimelinePhoneViewProps {
|
||||
items: React.ReactNode[];
|
||||
animationConfig: CardAnimationConfig;
|
||||
className?: string;
|
||||
export interface TimelinePhoneViewProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const TimelinePhoneView: React.FC<TimelinePhoneViewProps> = ({
|
||||
items,
|
||||
animationConfig,
|
||||
className = '',
|
||||
}) => {
|
||||
const cardsRef = useRef<HTMLDivElement[]>([]);
|
||||
|
||||
useCardAnimation(cardsRef, animationConfig);
|
||||
|
||||
const setCardRef = useCallback((index: number, el: HTMLDivElement | null) => {
|
||||
if (el) {
|
||||
cardsRef.current[index] = el;
|
||||
}
|
||||
}, []);
|
||||
|
||||
export const TimelinePhoneView: React.FC<TimelinePhoneViewProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div className={`timeline-phone-view ${className}`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={el => setCardRef(index, el)}
|
||||
className="timeline-item"
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelinePhoneView;
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useCardAnimation } from '../../hooks/useCardAnimation';
|
||||
import type { CardAnimationConfig } from '../../types';
|
||||
import React from 'react';
|
||||
|
||||
interface TimelineProcessFlowProps {
|
||||
items: React.ReactNode[];
|
||||
animationConfig: CardAnimationConfig;
|
||||
className?: string;
|
||||
export interface TimelineProcessFlowProps {
|
||||
children?: React.ReactNode;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const TimelineProcessFlow: React.FC<TimelineProcessFlowProps> = ({
|
||||
items,
|
||||
animationConfig,
|
||||
className = '',
|
||||
}) => {
|
||||
const cardsRef = useRef<HTMLDivElement[]>([]);
|
||||
|
||||
useCardAnimation(cardsRef, animationConfig);
|
||||
|
||||
const setCardRef = useCallback((index: number, el: HTMLDivElement | null) => {
|
||||
if (el) {
|
||||
cardsRef.current[index] = el;
|
||||
}
|
||||
}, []);
|
||||
|
||||
export const TimelineProcessFlow: React.FC<TimelineProcessFlowProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<div className={`timeline-process-flow ${className}`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
ref={el => setCardRef(index, el)}
|
||||
className="process-item"
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
<div {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineProcessFlow;
|
||||
|
||||
@@ -10,6 +10,15 @@ export type CardAnimationTypeWith3D = CardAnimationType | 'depth-3d';
|
||||
export type BentoAnimationType = CardAnimationType;
|
||||
export type GridVariant = 'uniform-all-items-equal' | 'bento-grid' | 'bento-grid-inverted' | 'two-columns-alternating-heights' | 'asymmetric-60-wide-40-narrow' | 'three-columns-all-equal-width' | 'four-items-2x2-equal-grid' | 'one-large-right-three-stacked-left' | 'items-top-row-full-width-bottom' | 'full-width-top-items-bottom-row' | 'one-large-left-three-stacked-right';
|
||||
|
||||
export type TextBoxProps = any;
|
||||
export type ArrowCarouselProps = any;
|
||||
export type FullWidthCarouselProps = any;
|
||||
export type ButtonConfig = any;
|
||||
export type ButtonAnimationType = any;
|
||||
export type TitleSegment = any;
|
||||
export type TextboxLayout = any;
|
||||
export type InvertedBackground = any;
|
||||
|
||||
export interface MetricCardOneGridVariant extends GridVariant {}
|
||||
export interface MetricCardTwoGridVariant extends GridVariant {}
|
||||
export interface TeamCardOneGridVariant extends GridVariant {}
|
||||
|
||||
@@ -1,34 +1,61 @@
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useCardAnimation } from '@/components/cardStack/hooks/useCardAnimation';
|
||||
import type { CardAnimationConfig } from '@/components/cardStack/types';
|
||||
import React from 'react';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
interface DashboardProps {
|
||||
animationConfig: CardAnimationConfig;
|
||||
className?: string;
|
||||
export interface DashboardStat {
|
||||
title: string;
|
||||
values: number[];
|
||||
valuePrefix?: string;
|
||||
valueSuffix?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export const Dashboard: React.FC<DashboardProps> = ({
|
||||
animationConfig,
|
||||
className = '',
|
||||
}) => {
|
||||
const cardsRef = useRef<HTMLDivElement[]>([]);
|
||||
export interface DashboardSidebarItem {
|
||||
icon: LucideIcon;
|
||||
active?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
useCardAnimation(cardsRef, animationConfig);
|
||||
export interface DashboardListItem {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
status: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const setCardRef = useCallback((index: number, el: HTMLDivElement | null) => {
|
||||
if (el) {
|
||||
cardsRef.current[index] = el;
|
||||
}
|
||||
}, []);
|
||||
export interface ChartDataItem {
|
||||
value: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface DashboardProps {
|
||||
title: string;
|
||||
stats: [DashboardStat, DashboardStat, DashboardStat];
|
||||
logoIcon: LucideIcon;
|
||||
sidebarItems: DashboardSidebarItem[];
|
||||
buttons: any[];
|
||||
listItems: DashboardListItem[];
|
||||
imageSrc: string;
|
||||
searchPlaceholder?: string;
|
||||
chartTitle?: string;
|
||||
chartData?: ChartDataItem[];
|
||||
listTitle?: string;
|
||||
videoSrc?: string;
|
||||
imageAlt?: string;
|
||||
videoAriaLabel?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
sidebarClassName?: string;
|
||||
statClassName?: string;
|
||||
chartClassName?: string;
|
||||
listClassName?: string;
|
||||
animationConfig?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const Dashboard: React.FC<DashboardProps> = (props) => {
|
||||
return (
|
||||
<div className={`dashboard ${className}`}>
|
||||
<div
|
||||
ref={el => setCardRef(0, el)}
|
||||
className="dashboard-item"
|
||||
>
|
||||
<p>Dashboard content</p>
|
||||
</div>
|
||||
<div className={props.className}>
|
||||
<h2>{props.title}</h2>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user