@nextworks/blocks-sections 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/components/About.d.ts +93 -0
- package/dist/components/About.d.ts.map +1 -0
- package/dist/components/About.js +129 -0
- package/dist/components/About.jsx +153 -0
- package/dist/components/CTA.d.ts +118 -0
- package/dist/components/CTA.d.ts.map +1 -0
- package/dist/components/CTA.js +58 -0
- package/dist/components/CTA.jsx +88 -0
- package/dist/components/Contact.d.ts +111 -0
- package/dist/components/Contact.d.ts.map +1 -0
- package/dist/components/Contact.js +82 -0
- package/dist/components/Contact.jsx +107 -0
- package/dist/components/FAQ.d.ts +89 -0
- package/dist/components/FAQ.d.ts.map +1 -0
- package/dist/components/FAQ.js +78 -0
- package/dist/components/FAQ.jsx +98 -0
- package/dist/components/Features.d.ts +111 -0
- package/dist/components/Features.d.ts.map +1 -0
- package/dist/components/Features.js +109 -0
- package/dist/components/Features.jsx +122 -0
- package/dist/components/Footer.d.ts +120 -0
- package/dist/components/Footer.d.ts.map +1 -0
- package/dist/components/Footer.js +101 -0
- package/dist/components/Footer.jsx +138 -0
- package/dist/components/HeroMotion.d.ts +107 -0
- package/dist/components/HeroMotion.d.ts.map +1 -0
- package/dist/components/HeroMotion.js +55 -0
- package/dist/components/HeroMotion.jsx +95 -0
- package/dist/components/HeroOverlay.d.ts +116 -0
- package/dist/components/HeroOverlay.d.ts.map +1 -0
- package/dist/components/HeroOverlay.js +111 -0
- package/dist/components/HeroOverlay.jsx +141 -0
- package/dist/components/HeroSplit.d.ts +98 -0
- package/dist/components/HeroSplit.d.ts.map +1 -0
- package/dist/components/HeroSplit.js +100 -0
- package/dist/components/HeroSplit.jsx +134 -0
- package/dist/components/Navbar.d.ts +112 -0
- package/dist/components/Navbar.d.ts.map +1 -0
- package/dist/components/Navbar.js +80 -0
- package/dist/components/Navbar.jsx +127 -0
- package/dist/components/Newsletter.d.ts +59 -0
- package/dist/components/Newsletter.d.ts.map +1 -0
- package/dist/components/Newsletter.js +34 -0
- package/dist/components/Newsletter.jsx +54 -0
- package/dist/components/PortfolioSimple.d.ts +137 -0
- package/dist/components/PortfolioSimple.d.ts.map +1 -0
- package/dist/components/PortfolioSimple.js +180 -0
- package/dist/components/PortfolioSimple.jsx +259 -0
- package/dist/components/Pricing.d.ts +96 -0
- package/dist/components/Pricing.d.ts.map +1 -0
- package/dist/components/Pricing.js +91 -0
- package/dist/components/Pricing.jsx +103 -0
- package/dist/components/ProcessTimeline.d.ts +121 -0
- package/dist/components/ProcessTimeline.d.ts.map +1 -0
- package/dist/components/ProcessTimeline.js +88 -0
- package/dist/components/ProcessTimeline.jsx +151 -0
- package/dist/components/ServicesGrid.d.ts +64 -0
- package/dist/components/ServicesGrid.d.ts.map +1 -0
- package/dist/components/ServicesGrid.js +72 -0
- package/dist/components/ServicesGrid.jsx +97 -0
- package/dist/components/Team.d.ts +115 -0
- package/dist/components/Team.d.ts.map +1 -0
- package/dist/components/Team.js +107 -0
- package/dist/components/Team.jsx +135 -0
- package/dist/components/Testimonials.d.ts +81 -0
- package/dist/components/Testimonials.d.ts.map +1 -0
- package/dist/components/Testimonials.js +46 -0
- package/dist/components/Testimonials.jsx +58 -0
- package/dist/components/TrustBadges.d.ts +80 -0
- package/dist/components/TrustBadges.d.ts.map +1 -0
- package/dist/components/TrustBadges.js +46 -0
- package/dist/components/TrustBadges.jsx +64 -0
- package/dist/components/index.d.ts +21 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
5
|
+
import { cn } from "@nextworks/blocks-core";
|
|
6
|
+
import { CTAButton } from "@nextworks/blocks-core";
|
|
7
|
+
import { ExternalLink, TrendingUp } from "lucide-react";
|
|
8
|
+
import Image from "next/image";
|
|
9
|
+
const ProjectImage = ({ project, onClick, imageContainer = {
|
|
10
|
+
className: "relative rounded-lg overflow-hidden bg-muted shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-xl cursor-pointer aspect-[16/10]",
|
|
11
|
+
}, }) => {
|
|
12
|
+
const colorClasses = {
|
|
13
|
+
blue: "bg-blue-100 dark:bg-blue-900",
|
|
14
|
+
green: "bg-green-100 dark:bg-green-900",
|
|
15
|
+
purple: "bg-purple-100 dark:bg-purple-900",
|
|
16
|
+
red: "bg-red-100 dark:bg-red-900",
|
|
17
|
+
yellow: "bg-yellow-100 dark:bg-yellow-900",
|
|
18
|
+
indigo: "bg-indigo-100 dark:bg-indigo-900",
|
|
19
|
+
};
|
|
20
|
+
const fallbackBg = colorClasses[project.color] ||
|
|
21
|
+
colorClasses.blue;
|
|
22
|
+
return (_jsx("div", { className: imageContainer.className, onClick: onClick, children: project.image ? (_jsx(Image, { src: project.image, alt: project.title, fill: true, className: "object-cover", sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" })) : (_jsx("div", { className: `flex h-full w-full items-center justify-center ${fallbackBg}`, children: _jsxs("div", { className: "p-8 text-center", children: [_jsx("div", { className: "bg-background/20 dark:bg-foreground/20 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full", children: _jsx(ExternalLink, { className: "h-8 w-8 text-[var(--card-muted-fg)]" }) }), _jsx("h3", { className: "mb-2 text-lg font-semibold text-[var(--card-title-fg)]", children: project.title }), _jsx("p", { className: "text-sm text-[var(--card-muted-fg)]", children: project.category })] }) })) }));
|
|
23
|
+
};
|
|
24
|
+
const defaultProjects = [
|
|
25
|
+
{
|
|
26
|
+
id: 1,
|
|
27
|
+
title: "TechStartup Growth Campaign",
|
|
28
|
+
category: "Digital Marketing",
|
|
29
|
+
industry: "Technology",
|
|
30
|
+
result: "+300% Leads",
|
|
31
|
+
description: "Comprehensive digital marketing campaign that increased qualified leads by 300% through targeted SEO, social media marketing, and conversion optimization strategies.",
|
|
32
|
+
tags: ["SEO", "Social Media", "PPC", "Analytics"],
|
|
33
|
+
color: "blue",
|
|
34
|
+
url: "#",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 2,
|
|
38
|
+
title: "E-Commerce Brand Launch",
|
|
39
|
+
category: "Brand Marketing",
|
|
40
|
+
industry: "Retail",
|
|
41
|
+
result: "+250% Revenue",
|
|
42
|
+
description: "Complete brand launch and digital marketing strategy for a new e-commerce platform, resulting in 250% revenue growth within the first 6 months.",
|
|
43
|
+
tags: ["Brand Strategy", "Content Marketing", "Influencer", "Email"],
|
|
44
|
+
color: "green",
|
|
45
|
+
url: "#",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 3,
|
|
49
|
+
title: "Healthcare Practice Expansion",
|
|
50
|
+
category: "Local Marketing",
|
|
51
|
+
industry: "Healthcare",
|
|
52
|
+
result: "+180% Patients",
|
|
53
|
+
description: "Local SEO and digital marketing campaign that helped a healthcare practice expand to three new locations and increase patient acquisition by 180%.",
|
|
54
|
+
tags: ["Local SEO", "Google Ads", "Reputation", "Content"],
|
|
55
|
+
color: "purple",
|
|
56
|
+
url: "#",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 4,
|
|
60
|
+
title: "SaaS Product Launch",
|
|
61
|
+
category: "B2B Marketing",
|
|
62
|
+
industry: "Software",
|
|
63
|
+
result: "+400% Signups",
|
|
64
|
+
description: "Strategic B2B marketing campaign for a SaaS product launch, achieving 400% increase in trial signups through targeted content marketing and account-based marketing.",
|
|
65
|
+
tags: ["B2B Strategy", "Content", "LinkedIn Ads", "Webinars"],
|
|
66
|
+
color: "indigo",
|
|
67
|
+
url: "#",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 5,
|
|
71
|
+
title: "Restaurant Chain Rebrand",
|
|
72
|
+
category: "Brand Marketing",
|
|
73
|
+
industry: "Food & Beverage",
|
|
74
|
+
result: "+220% Foot Traffic",
|
|
75
|
+
description: "Complete rebrand and digital marketing transformation for a restaurant chain, resulting in 220% increase in foot traffic and 150% growth in online orders.",
|
|
76
|
+
tags: ["Rebranding", "Social Media", "Local SEO", "Delivery"],
|
|
77
|
+
color: "red",
|
|
78
|
+
url: "#",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 6,
|
|
82
|
+
title: "Fitness App User Acquisition",
|
|
83
|
+
category: "Mobile Marketing",
|
|
84
|
+
industry: "Health & Fitness",
|
|
85
|
+
result: "+500% Downloads",
|
|
86
|
+
description: "Mobile-first marketing campaign that achieved 500% increase in app downloads through app store optimization, influencer partnerships, and targeted social media advertising.",
|
|
87
|
+
tags: ["ASO", "Influencer", "Social Ads", "Retention"],
|
|
88
|
+
color: "yellow",
|
|
89
|
+
url: "#",
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
const defaultFilters = [
|
|
93
|
+
"All Projects",
|
|
94
|
+
"Digital Marketing",
|
|
95
|
+
"Brand Marketing",
|
|
96
|
+
"B2B Marketing",
|
|
97
|
+
"Local Marketing",
|
|
98
|
+
"Mobile Marketing",
|
|
99
|
+
];
|
|
100
|
+
/**
|
|
101
|
+
* Responsive portfolio/gallery grid with simple category filtering and CTA block.
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* - Filtering uses exact category match; include "All Projects" to disable filtering.
|
|
105
|
+
* - Accessibility: Renders a semantic <section> with aria-label; ensure images have informative titles or alt text.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* <PortfolioSimple sectionTitle="Our Work" />
|
|
109
|
+
*/
|
|
110
|
+
export function PortfolioSimple({ id, className, projects = defaultProjects, filters = defaultFilters, enableMotion = true, sectionTitle = "Our Recent Work", sectionSubtitle = "Take a look at some of our successful projects that have helped businesses transform their digital presence and achieve remarkable growth.", ctaTitle = "Ready to See Your Project Here?", ctaDescription = "Let's discuss how we can help transform your business with a custom digital solution.", cta1Label = "Get Free Quote", cta1Href = "#contact", cta2Label, cta2Href, section = {
|
|
111
|
+
className: "py-16 md:py-24 bg-background",
|
|
112
|
+
}, container = {
|
|
113
|
+
className: "max-w-7xl mx-auto px-6",
|
|
114
|
+
}, header = {
|
|
115
|
+
className: "space-y-6 text-center mb-12",
|
|
116
|
+
}, title = {
|
|
117
|
+
className: "text-3xl md:text-4xl font-bold font-poppins text-foreground text-[var(--heading-fg)]",
|
|
118
|
+
}, subtitle = {
|
|
119
|
+
className: "text-xl font-inter text-muted-foreground max-w-2xl mx-auto leading-relaxed text-[var(--subheading-fg)]",
|
|
120
|
+
}, filterContainer = {
|
|
121
|
+
className: "flex gap-4 flex-wrap justify-center mt-8",
|
|
122
|
+
}, filterButton = {
|
|
123
|
+
className: "px-6 py-2 rounded-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md border-[var(--badge-border)] text-[var(--badge-fg)] bg-[var(--badge-bg)]",
|
|
124
|
+
}, activeFilterButton = {
|
|
125
|
+
className: "px-6 py-2 rounded-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md bg-[var(--badge-active-bg)] text-[var(--badge-active-fg)] border-[var(--badge-active-border)]",
|
|
126
|
+
}, grid = {
|
|
127
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-10 mt-12",
|
|
128
|
+
}, projectCard = {
|
|
129
|
+
className: "group cursor-pointer transition-all duration-300",
|
|
130
|
+
}, imageContainer = {
|
|
131
|
+
className: "relative rounded-lg overflow-hidden bg-muted shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-xl cursor-pointer aspect-[16/10]",
|
|
132
|
+
}, projectInfo = {
|
|
133
|
+
className: "space-y-4 px-2 mt-4",
|
|
134
|
+
}, projectTitle = {
|
|
135
|
+
className: "text-xl font-bold font-poppins transition-colors duration-200 text-[var(--card-title-fg)] group-hover:text-[var(--card-title-hover-fg)]",
|
|
136
|
+
}, projectDescription = {
|
|
137
|
+
className: "text-base font-inter text-[var(--card-muted-fg)]",
|
|
138
|
+
}, tagsContainer = {
|
|
139
|
+
className: "flex gap-2 flex-wrap",
|
|
140
|
+
}, tagStyle = {
|
|
141
|
+
className: "px-2 py-1 text-xs border border-border rounded-full text-muted-foreground border-[var(--badge-border)] text-[var(--badge-fg)] bg-[var(--badge-bg)]",
|
|
142
|
+
}, result = {
|
|
143
|
+
className: "font-bold text-[var(--metric-fg)] text-sm whitespace-nowrap ml-4",
|
|
144
|
+
}, ctaSection = {
|
|
145
|
+
className: "space-y-6 text-center mt-16 pt-12 border-t border-border",
|
|
146
|
+
}, ctaTitleStyle = {
|
|
147
|
+
className: "text-xl font-bold text-foreground text-[var(--heading-fg)]",
|
|
148
|
+
}, ctaDescriptionStyle = {
|
|
149
|
+
className: "text-muted-foreground max-w-md mx-auto text-[var(--subheading-fg)]",
|
|
150
|
+
}, ctaButtons = {
|
|
151
|
+
className: "flex gap-4 justify-center",
|
|
152
|
+
}, cta1Button = {
|
|
153
|
+
className: "bg-primary hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary/90 text-primary-foreground font-medium shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
|
|
154
|
+
}, cta2Button = {
|
|
155
|
+
className: "border-primary text-primary hover:bg-primary/10 font-medium shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
|
|
156
|
+
}, onProjectClick, onPrimaryCtaClick, onSecondaryCtaClick, ariaLabel = "Portfolio showcase section", }) {
|
|
157
|
+
const [activeFilter, setActiveFilter] = useState(filters[0] || "All Projects");
|
|
158
|
+
const filteredProjects = projects.filter((project) => activeFilter === "All Projects" || project.category === activeFilter);
|
|
159
|
+
return (_jsx("section", { id: id, className: cn(section.className, className), "aria-label": ariaLabel, children: _jsxs("div", { className: container.className, children: [_jsxs("div", { className: header.className, children: [_jsx("h2", { className: title.className, children: sectionTitle }), _jsx("p", { className: subtitle.className, children: sectionSubtitle }), _jsx("div", { className: filterContainer.className, children: filters.map((filter) => (_jsx("button", { className: activeFilter === filter
|
|
160
|
+
? activeFilterButton.className
|
|
161
|
+
: filterButton.className, onClick: () => setActiveFilter(filter), children: filter }, filter))) })] }), _jsx("div", { className: grid.className, children: filteredProjects.map((project, index) => (_jsx(motion.div, { initial: enableMotion ? { opacity: 0, y: 12 } : { opacity: 1, y: 0 }, whileInView: enableMotion ? { opacity: 1, y: 0 } : { opacity: 1, y: 0 }, viewport: enableMotion
|
|
162
|
+
? { once: true, amount: 0.2 }
|
|
163
|
+
: { once: true, amount: 0 }, transition: enableMotion
|
|
164
|
+
? {
|
|
165
|
+
type: "spring",
|
|
166
|
+
stiffness: 125,
|
|
167
|
+
damping: 50,
|
|
168
|
+
mass: 1,
|
|
169
|
+
delay: 0.1 + index * 0.05,
|
|
170
|
+
}
|
|
171
|
+
: { type: "tween", duration: 0 }, className: "motion-reduce:transform-none motion-reduce:transition-none", children: _jsx("div", { className: cn(projectCard.className, enableMotion
|
|
172
|
+
? "transition-all duration-200 hover:-translate-y-1"
|
|
173
|
+
: "cursor-default transition-none hover:!translate-y-0 hover:shadow-none"), children: _jsxs("div", { className: "space-y-4", children: [_jsx(ProjectImage, { project: project, onClick: () => onProjectClick === null || onProjectClick === void 0 ? void 0 : onProjectClick(project), imageContainer: imageContainer }), _jsxs("div", { className: projectInfo.className, children: [_jsx("div", { className: "flex w-full items-start justify-between", children: _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx("h3", { className: projectTitle.className, children: project.title }), _jsx("p", { className: projectDescription.className, children: project.description })] }) }), _jsxs("div", { className: "flex w-full items-center justify-between", children: [_jsx("div", { className: tagsContainer.className, children: project.tags.map((tag, index) => (_jsx("span", { className: tagStyle.className, children: tag }, index))) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TrendingUp, { className: "h-4 w-4 text-[var(--metric-fg)]" }), _jsx("span", { className: result.className, children: project.result })] })] })] })] }) }) }, project.id))) }), _jsxs("div", { className: ctaSection.className, children: [_jsx("h3", { className: ctaTitleStyle.className, children: ctaTitle }), _jsx("p", { className: ctaDescriptionStyle.className, children: ctaDescription }), _jsxs("div", { className: ctaButtons.className, children: [_jsx(CTAButton, { ctaButtonLabel: cta1Label, ctaButtonHref: cta1Href, button: {
|
|
174
|
+
className: cta1Button.className,
|
|
175
|
+
}, onClick: onPrimaryCtaClick }), cta2Label && (_jsx(CTAButton, { ctaButtonLabel: cta2Label, ctaButtonHref: cta2Href, button: {
|
|
176
|
+
variant: "outline",
|
|
177
|
+
size: "lg",
|
|
178
|
+
className: cta2Button.className,
|
|
179
|
+
}, onClick: onSecondaryCtaClick }))] })] })] }) }));
|
|
180
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
|
+
import { cn } from "@nextworks/blocks-core";
|
|
5
|
+
import { CTAButton } from "@nextworks/blocks-core";
|
|
6
|
+
import { ExternalLink, TrendingUp } from "lucide-react";
|
|
7
|
+
import Image from "next/image";
|
|
8
|
+
const ProjectImage = ({ project, onClick, imageContainer = {
|
|
9
|
+
className: "relative rounded-lg overflow-hidden bg-muted shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-xl cursor-pointer aspect-[16/10]",
|
|
10
|
+
}, }) => {
|
|
11
|
+
const colorClasses = {
|
|
12
|
+
blue: "bg-blue-100 dark:bg-blue-900",
|
|
13
|
+
green: "bg-green-100 dark:bg-green-900",
|
|
14
|
+
purple: "bg-purple-100 dark:bg-purple-900",
|
|
15
|
+
red: "bg-red-100 dark:bg-red-900",
|
|
16
|
+
yellow: "bg-yellow-100 dark:bg-yellow-900",
|
|
17
|
+
indigo: "bg-indigo-100 dark:bg-indigo-900",
|
|
18
|
+
};
|
|
19
|
+
const fallbackBg = colorClasses[project.color] ||
|
|
20
|
+
colorClasses.blue;
|
|
21
|
+
return (<div className={imageContainer.className} onClick={onClick}>
|
|
22
|
+
{project.image ? (<Image src={project.image} alt={project.title} fill className="object-cover" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"/>) : (<div className={`flex h-full w-full items-center justify-center ${fallbackBg}`}>
|
|
23
|
+
<div className="p-8 text-center">
|
|
24
|
+
<div className="bg-background/20 dark:bg-foreground/20 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full">
|
|
25
|
+
<ExternalLink className="h-8 w-8 text-[var(--card-muted-fg)]"/>
|
|
26
|
+
</div>
|
|
27
|
+
<h3 className="mb-2 text-lg font-semibold text-[var(--card-title-fg)]">
|
|
28
|
+
{project.title}
|
|
29
|
+
</h3>
|
|
30
|
+
<p className="text-sm text-[var(--card-muted-fg)]">
|
|
31
|
+
{project.category}
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>)}
|
|
35
|
+
</div>);
|
|
36
|
+
};
|
|
37
|
+
const defaultProjects = [
|
|
38
|
+
{
|
|
39
|
+
id: 1,
|
|
40
|
+
title: "TechStartup Growth Campaign",
|
|
41
|
+
category: "Digital Marketing",
|
|
42
|
+
industry: "Technology",
|
|
43
|
+
result: "+300% Leads",
|
|
44
|
+
description: "Comprehensive digital marketing campaign that increased qualified leads by 300% through targeted SEO, social media marketing, and conversion optimization strategies.",
|
|
45
|
+
tags: ["SEO", "Social Media", "PPC", "Analytics"],
|
|
46
|
+
color: "blue",
|
|
47
|
+
url: "#",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 2,
|
|
51
|
+
title: "E-Commerce Brand Launch",
|
|
52
|
+
category: "Brand Marketing",
|
|
53
|
+
industry: "Retail",
|
|
54
|
+
result: "+250% Revenue",
|
|
55
|
+
description: "Complete brand launch and digital marketing strategy for a new e-commerce platform, resulting in 250% revenue growth within the first 6 months.",
|
|
56
|
+
tags: ["Brand Strategy", "Content Marketing", "Influencer", "Email"],
|
|
57
|
+
color: "green",
|
|
58
|
+
url: "#",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 3,
|
|
62
|
+
title: "Healthcare Practice Expansion",
|
|
63
|
+
category: "Local Marketing",
|
|
64
|
+
industry: "Healthcare",
|
|
65
|
+
result: "+180% Patients",
|
|
66
|
+
description: "Local SEO and digital marketing campaign that helped a healthcare practice expand to three new locations and increase patient acquisition by 180%.",
|
|
67
|
+
tags: ["Local SEO", "Google Ads", "Reputation", "Content"],
|
|
68
|
+
color: "purple",
|
|
69
|
+
url: "#",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 4,
|
|
73
|
+
title: "SaaS Product Launch",
|
|
74
|
+
category: "B2B Marketing",
|
|
75
|
+
industry: "Software",
|
|
76
|
+
result: "+400% Signups",
|
|
77
|
+
description: "Strategic B2B marketing campaign for a SaaS product launch, achieving 400% increase in trial signups through targeted content marketing and account-based marketing.",
|
|
78
|
+
tags: ["B2B Strategy", "Content", "LinkedIn Ads", "Webinars"],
|
|
79
|
+
color: "indigo",
|
|
80
|
+
url: "#",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 5,
|
|
84
|
+
title: "Restaurant Chain Rebrand",
|
|
85
|
+
category: "Brand Marketing",
|
|
86
|
+
industry: "Food & Beverage",
|
|
87
|
+
result: "+220% Foot Traffic",
|
|
88
|
+
description: "Complete rebrand and digital marketing transformation for a restaurant chain, resulting in 220% increase in foot traffic and 150% growth in online orders.",
|
|
89
|
+
tags: ["Rebranding", "Social Media", "Local SEO", "Delivery"],
|
|
90
|
+
color: "red",
|
|
91
|
+
url: "#",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 6,
|
|
95
|
+
title: "Fitness App User Acquisition",
|
|
96
|
+
category: "Mobile Marketing",
|
|
97
|
+
industry: "Health & Fitness",
|
|
98
|
+
result: "+500% Downloads",
|
|
99
|
+
description: "Mobile-first marketing campaign that achieved 500% increase in app downloads through app store optimization, influencer partnerships, and targeted social media advertising.",
|
|
100
|
+
tags: ["ASO", "Influencer", "Social Ads", "Retention"],
|
|
101
|
+
color: "yellow",
|
|
102
|
+
url: "#",
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
const defaultFilters = [
|
|
106
|
+
"All Projects",
|
|
107
|
+
"Digital Marketing",
|
|
108
|
+
"Brand Marketing",
|
|
109
|
+
"B2B Marketing",
|
|
110
|
+
"Local Marketing",
|
|
111
|
+
"Mobile Marketing",
|
|
112
|
+
];
|
|
113
|
+
/**
|
|
114
|
+
* Responsive portfolio/gallery grid with simple category filtering and CTA block.
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
* - Filtering uses exact category match; include "All Projects" to disable filtering.
|
|
118
|
+
* - Accessibility: Renders a semantic <section> with aria-label; ensure images have informative titles or alt text.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* <PortfolioSimple sectionTitle="Our Work" />
|
|
122
|
+
*/
|
|
123
|
+
export function PortfolioSimple({ id, className, projects = defaultProjects, filters = defaultFilters, enableMotion = true, sectionTitle = "Our Recent Work", sectionSubtitle = "Take a look at some of our successful projects that have helped businesses transform their digital presence and achieve remarkable growth.", ctaTitle = "Ready to See Your Project Here?", ctaDescription = "Let's discuss how we can help transform your business with a custom digital solution.", cta1Label = "Get Free Quote", cta1Href = "#contact", cta2Label, cta2Href, section = {
|
|
124
|
+
className: "py-16 md:py-24 bg-background",
|
|
125
|
+
}, container = {
|
|
126
|
+
className: "max-w-7xl mx-auto px-6",
|
|
127
|
+
}, header = {
|
|
128
|
+
className: "space-y-6 text-center mb-12",
|
|
129
|
+
}, title = {
|
|
130
|
+
className: "text-3xl md:text-4xl font-bold font-poppins text-foreground text-[var(--heading-fg)]",
|
|
131
|
+
}, subtitle = {
|
|
132
|
+
className: "text-xl font-inter text-muted-foreground max-w-2xl mx-auto leading-relaxed text-[var(--subheading-fg)]",
|
|
133
|
+
}, filterContainer = {
|
|
134
|
+
className: "flex gap-4 flex-wrap justify-center mt-8",
|
|
135
|
+
}, filterButton = {
|
|
136
|
+
className: "px-6 py-2 rounded-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md border-[var(--badge-border)] text-[var(--badge-fg)] bg-[var(--badge-bg)]",
|
|
137
|
+
}, activeFilterButton = {
|
|
138
|
+
className: "px-6 py-2 rounded-full transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md bg-[var(--badge-active-bg)] text-[var(--badge-active-fg)] border-[var(--badge-active-border)]",
|
|
139
|
+
}, grid = {
|
|
140
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-10 mt-12",
|
|
141
|
+
}, projectCard = {
|
|
142
|
+
className: "group cursor-pointer transition-all duration-300",
|
|
143
|
+
}, imageContainer = {
|
|
144
|
+
className: "relative rounded-lg overflow-hidden bg-muted shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-xl cursor-pointer aspect-[16/10]",
|
|
145
|
+
}, projectInfo = {
|
|
146
|
+
className: "space-y-4 px-2 mt-4",
|
|
147
|
+
}, projectTitle = {
|
|
148
|
+
className: "text-xl font-bold font-poppins transition-colors duration-200 text-[var(--card-title-fg)] group-hover:text-[var(--card-title-hover-fg)]",
|
|
149
|
+
}, projectDescription = {
|
|
150
|
+
className: "text-base font-inter text-[var(--card-muted-fg)]",
|
|
151
|
+
}, tagsContainer = {
|
|
152
|
+
className: "flex gap-2 flex-wrap",
|
|
153
|
+
}, tagStyle = {
|
|
154
|
+
className: "px-2 py-1 text-xs border border-border rounded-full text-muted-foreground border-[var(--badge-border)] text-[var(--badge-fg)] bg-[var(--badge-bg)]",
|
|
155
|
+
}, result = {
|
|
156
|
+
className: "font-bold text-[var(--metric-fg)] text-sm whitespace-nowrap ml-4",
|
|
157
|
+
}, ctaSection = {
|
|
158
|
+
className: "space-y-6 text-center mt-16 pt-12 border-t border-border",
|
|
159
|
+
}, ctaTitleStyle = {
|
|
160
|
+
className: "text-xl font-bold text-foreground text-[var(--heading-fg)]",
|
|
161
|
+
}, ctaDescriptionStyle = {
|
|
162
|
+
className: "text-muted-foreground max-w-md mx-auto text-[var(--subheading-fg)]",
|
|
163
|
+
}, ctaButtons = {
|
|
164
|
+
className: "flex gap-4 justify-center",
|
|
165
|
+
}, cta1Button = {
|
|
166
|
+
className: "bg-primary hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary/90 text-primary-foreground font-medium shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
|
|
167
|
+
}, cta2Button = {
|
|
168
|
+
className: "border-primary text-primary hover:bg-primary/10 font-medium shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
|
|
169
|
+
}, onProjectClick, onPrimaryCtaClick, onSecondaryCtaClick, ariaLabel = "Portfolio showcase section", }) {
|
|
170
|
+
const [activeFilter, setActiveFilter] = useState(filters[0] || "All Projects");
|
|
171
|
+
const filteredProjects = projects.filter((project) => activeFilter === "All Projects" || project.category === activeFilter);
|
|
172
|
+
return (<section id={id} className={cn(section.className, className)} aria-label={ariaLabel}>
|
|
173
|
+
<div className={container.className}>
|
|
174
|
+
{/* Section Header */}
|
|
175
|
+
<div className={header.className}>
|
|
176
|
+
<h2 className={title.className}>{sectionTitle}</h2>
|
|
177
|
+
<p className={subtitle.className}>{sectionSubtitle}</p>
|
|
178
|
+
|
|
179
|
+
{/* Filter Buttons */}
|
|
180
|
+
<div className={filterContainer.className}>
|
|
181
|
+
{filters.map((filter) => (<button key={filter} className={activeFilter === filter
|
|
182
|
+
? activeFilterButton.className
|
|
183
|
+
: filterButton.className} onClick={() => setActiveFilter(filter)}>
|
|
184
|
+
{filter}
|
|
185
|
+
</button>))}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Portfolio Grid */}
|
|
190
|
+
<div className={grid.className}>
|
|
191
|
+
{filteredProjects.map((project, index) => (<motion.div key={project.id} initial={enableMotion ? { opacity: 0, y: 12 } : { opacity: 1, y: 0 }} whileInView={enableMotion ? { opacity: 1, y: 0 } : { opacity: 1, y: 0 }} viewport={enableMotion
|
|
192
|
+
? { once: true, amount: 0.2 }
|
|
193
|
+
: { once: true, amount: 0 }} transition={enableMotion
|
|
194
|
+
? {
|
|
195
|
+
type: "spring",
|
|
196
|
+
stiffness: 125,
|
|
197
|
+
damping: 50,
|
|
198
|
+
mass: 1,
|
|
199
|
+
delay: 0.1 + index * 0.05,
|
|
200
|
+
}
|
|
201
|
+
: { type: "tween", duration: 0 }} className="motion-reduce:transform-none motion-reduce:transition-none">
|
|
202
|
+
<div className={cn(projectCard.className, enableMotion
|
|
203
|
+
? "transition-all duration-200 hover:-translate-y-1"
|
|
204
|
+
: "cursor-default transition-none hover:!translate-y-0 hover:shadow-none")}>
|
|
205
|
+
<div className="space-y-4">
|
|
206
|
+
{/* Project Image */}
|
|
207
|
+
<ProjectImage project={project} onClick={() => onProjectClick === null || onProjectClick === void 0 ? void 0 : onProjectClick(project)} imageContainer={imageContainer}/>
|
|
208
|
+
|
|
209
|
+
{/* Project Info */}
|
|
210
|
+
<div className={projectInfo.className}>
|
|
211
|
+
<div className="flex w-full items-start justify-between">
|
|
212
|
+
<div className="flex-1 space-y-2">
|
|
213
|
+
<h3 className={projectTitle.className}>
|
|
214
|
+
{project.title}
|
|
215
|
+
</h3>
|
|
216
|
+
<p className={projectDescription.className}>
|
|
217
|
+
{project.description}
|
|
218
|
+
</p>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* Tags & Result */}
|
|
223
|
+
<div className="flex w-full items-center justify-between">
|
|
224
|
+
<div className={tagsContainer.className}>
|
|
225
|
+
{project.tags.map((tag, index) => (<span key={index} className={tagStyle.className}>
|
|
226
|
+
{tag}
|
|
227
|
+
</span>))}
|
|
228
|
+
</div>
|
|
229
|
+
<div className="flex items-center gap-2">
|
|
230
|
+
<TrendingUp className="h-4 w-4 text-[var(--metric-fg)]"/>
|
|
231
|
+
<span className={result.className}>
|
|
232
|
+
{project.result}
|
|
233
|
+
</span>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</motion.div>))}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* CTA Section */}
|
|
243
|
+
<div className={ctaSection.className}>
|
|
244
|
+
<h3 className={ctaTitleStyle.className}>{ctaTitle}</h3>
|
|
245
|
+
<p className={ctaDescriptionStyle.className}>{ctaDescription}</p>
|
|
246
|
+
<div className={ctaButtons.className}>
|
|
247
|
+
<CTAButton ctaButtonLabel={cta1Label} ctaButtonHref={cta1Href} button={{
|
|
248
|
+
className: cta1Button.className,
|
|
249
|
+
}} onClick={onPrimaryCtaClick}/>
|
|
250
|
+
{cta2Label && (<CTAButton ctaButtonLabel={cta2Label} ctaButtonHref={cta2Href} button={{
|
|
251
|
+
variant: "outline",
|
|
252
|
+
size: "lg",
|
|
253
|
+
className: cta2Button.className,
|
|
254
|
+
}} onClick={onSecondaryCtaClick}/>)}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</section>);
|
|
259
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Represents individual pricing plan data.
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
export interface PricingData {
|
|
7
|
+
/** Title of the plan (e.g., "Pro") */
|
|
8
|
+
pricingPlanHeaderText?: string;
|
|
9
|
+
/** Display price (e.g., "$19.99") */
|
|
10
|
+
pricingPlanPrice?: string;
|
|
11
|
+
/** Feature list bullets */
|
|
12
|
+
pricingPlanFeatures?: string[];
|
|
13
|
+
/** CTA button label */
|
|
14
|
+
pricingPlanCTALabel?: string;
|
|
15
|
+
/** CTA button href */
|
|
16
|
+
pricingPlanCTAHref?: string;
|
|
17
|
+
/** Whether this plan should be highlighted */
|
|
18
|
+
isPopular?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Props for the Pricing section component.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* - Styling: slot-style className overrides are merged after defaults via cn().
|
|
25
|
+
* - Motion: enableMotion toggles entrance animations and hover transitions.
|
|
26
|
+
* - Accessibility: uses a semantic <section> with an aria-label.
|
|
27
|
+
*/
|
|
28
|
+
interface PricingProps {
|
|
29
|
+
/** Optional id on root. @defaultValue "pricing" */
|
|
30
|
+
id?: string;
|
|
31
|
+
/** Merge-in class for root */
|
|
32
|
+
className?: string;
|
|
33
|
+
/** Heading text displayed at the top of the pricing section. @defaultValue "Choose Your Plan" */
|
|
34
|
+
pricingHeadingText?: string;
|
|
35
|
+
/** Array of individual pricing plans to display. @defaultValue defaultPricingData */
|
|
36
|
+
pricingPlans?: PricingData[];
|
|
37
|
+
/** When false, disables entrance animations and hover transitions */
|
|
38
|
+
enableMotion?: boolean;
|
|
39
|
+
/** Styling configuration objects */
|
|
40
|
+
section?: {
|
|
41
|
+
className?: string;
|
|
42
|
+
};
|
|
43
|
+
container?: {
|
|
44
|
+
className?: string;
|
|
45
|
+
};
|
|
46
|
+
heading?: {
|
|
47
|
+
className?: string;
|
|
48
|
+
};
|
|
49
|
+
grid?: {
|
|
50
|
+
className?: string;
|
|
51
|
+
};
|
|
52
|
+
card?: {
|
|
53
|
+
className?: string;
|
|
54
|
+
};
|
|
55
|
+
header?: {
|
|
56
|
+
className?: string;
|
|
57
|
+
};
|
|
58
|
+
title?: {
|
|
59
|
+
className?: string;
|
|
60
|
+
};
|
|
61
|
+
price?: {
|
|
62
|
+
className?: string;
|
|
63
|
+
};
|
|
64
|
+
features?: {
|
|
65
|
+
className?: string;
|
|
66
|
+
};
|
|
67
|
+
featureItem?: {
|
|
68
|
+
className?: string;
|
|
69
|
+
};
|
|
70
|
+
cta?: {
|
|
71
|
+
unstyled?: boolean;
|
|
72
|
+
style?: React.CSSProperties;
|
|
73
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
74
|
+
size?: "default" | "sm" | "lg" | "icon";
|
|
75
|
+
className?: string;
|
|
76
|
+
};
|
|
77
|
+
popularBadge?: {
|
|
78
|
+
className?: string;
|
|
79
|
+
};
|
|
80
|
+
/** ARIA label for the pricing section */
|
|
81
|
+
ariaLabel?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Responsive pricing grid using PricingCard with optional motion.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* - Styling: exposes slot-style overrides (card, header, title, price, etc.).
|
|
88
|
+
* - Motion: enableMotion controls entrance animations and hover transitions.
|
|
89
|
+
* - Accessibility: semantic <section> with aria-label.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* <Pricing pricingPlans={[{ pricingPlanHeaderText: 'Pro', pricingPlanPrice: '$19' }]} />
|
|
93
|
+
*/
|
|
94
|
+
export declare function Pricing({ id, className, pricingPlans, pricingHeadingText, enableMotion, section, container, heading, grid, card, header, title, price, features, featureItem, cta, popularBadge, ariaLabel, }: PricingProps): React.JSX.Element;
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=Pricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pricing.d.ts","sourceRoot":"","sources":["../../src/components/Pricing.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qCAAqC;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2BAA2B;IAC3B,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,uBAAuB;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,UAAU,YAAY;IACpB,mDAAmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qFAAqF;IACrF,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAE7B,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,oCAAoC;IACpC,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,SAAS,CAAC,EAAE;QACV,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,KAAK,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,KAAK,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAC5B,OAAO,CAAC,EACJ,SAAS,GACT,aAAa,GACb,SAAS,GACT,WAAW,GACX,OAAO,GACP,MAAM,CAAC;QACX,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;QACxC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,YAAY,CAAC,EAAE;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmCD;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,EACtB,EAAE,EACF,SAAS,EACT,YAAiC,EACjC,kBAAuC,EACvC,YAAmB,EACnB,OAEC,EACD,SAEC,EACD,OAGC,EACD,IAEC,EACD,IAGC,EACD,MAEC,EACD,KAGC,EACD,KAGC,EACD,QAEC,EACD,WAGC,EACD,GAKC,EACD,YAGC,EACD,SAA6B,GAC9B,EAAE,YAAY,qBAmEd"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
|
+
import { cn } from "@nextworks/blocks-core";
|
|
5
|
+
import { PricingCard } from "@nextworks/blocks-core";
|
|
6
|
+
/**
|
|
7
|
+
* Default pricing plans used if none are passed in.
|
|
8
|
+
*/
|
|
9
|
+
const defaultPricingData = [
|
|
10
|
+
{
|
|
11
|
+
pricingPlanHeaderText: "Basic",
|
|
12
|
+
pricingPlanPrice: "$9.99",
|
|
13
|
+
pricingPlanFeatures: ["Feature 1", "Feature 2", "Feature 3"],
|
|
14
|
+
pricingPlanCTALabel: "Select Plan",
|
|
15
|
+
pricingPlanCTAHref: "#contact",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
pricingPlanHeaderText: "Pro",
|
|
19
|
+
pricingPlanPrice: "$19.99",
|
|
20
|
+
pricingPlanFeatures: ["Feature 1", "Feature 2", "Feature 3", "Feature 4"],
|
|
21
|
+
pricingPlanCTALabel: "Select Plan",
|
|
22
|
+
pricingPlanCTAHref: "#contact",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pricingPlanHeaderText: "Enterprise",
|
|
26
|
+
pricingPlanPrice: "$49.99",
|
|
27
|
+
pricingPlanFeatures: [
|
|
28
|
+
"Feature 1",
|
|
29
|
+
"Feature 2",
|
|
30
|
+
"Feature 3",
|
|
31
|
+
"Feature 4",
|
|
32
|
+
"Feature 5",
|
|
33
|
+
],
|
|
34
|
+
pricingPlanCTALabel: "Select Plan",
|
|
35
|
+
pricingPlanCTAHref: "#contact",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* Responsive pricing grid using PricingCard with optional motion.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* - Styling: exposes slot-style overrides (card, header, title, price, etc.).
|
|
43
|
+
* - Motion: enableMotion controls entrance animations and hover transitions.
|
|
44
|
+
* - Accessibility: semantic <section> with aria-label.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* <Pricing pricingPlans={[{ pricingPlanHeaderText: 'Pro', pricingPlanPrice: '$19' }]} />
|
|
48
|
+
*/
|
|
49
|
+
export function Pricing({ id, className, pricingPlans = defaultPricingData, pricingHeadingText = "Choose Your Plan", enableMotion = true, section = {
|
|
50
|
+
className: "pt-20 pb-5 bg-background text-foreground",
|
|
51
|
+
}, container = {
|
|
52
|
+
className: "max-w-7xl mx-auto px-6",
|
|
53
|
+
}, heading = {
|
|
54
|
+
className: "text-3xl font-bold font-poppins text-center text-foreground mb-8",
|
|
55
|
+
}, grid = {
|
|
56
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-8",
|
|
57
|
+
}, card = {
|
|
58
|
+
className: "relative bg-card border border-border rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 bg-[var(--card-bg)] text-[var(--card-fg)] border-[var(--card-border)] shadow-[var(--card-shadow)]",
|
|
59
|
+
}, header = {
|
|
60
|
+
className: "p-6 text-center border-b border-[var(--card-border)]",
|
|
61
|
+
}, title = {
|
|
62
|
+
className: "text-xl font-bold font-poppins text-card-foreground mb-2 text-[var(--card-title-fg)]",
|
|
63
|
+
}, price = {
|
|
64
|
+
className: "text-3xl font-bold font-poppins text-card-foreground mb-4 text-[var(--card-title-fg)]",
|
|
65
|
+
}, features = {
|
|
66
|
+
className: "p-6 space-y-3 font-inter",
|
|
67
|
+
}, featureItem = {
|
|
68
|
+
className: "flex items-center text-muted-foreground text-sm text-[var(--card-muted-fg)]",
|
|
69
|
+
}, cta = {
|
|
70
|
+
variant: "default",
|
|
71
|
+
size: "lg",
|
|
72
|
+
className: "w-full bg-primary hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary/90 text-primary-foreground font-medium shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
|
|
73
|
+
}, popularBadge = {
|
|
74
|
+
className: "absolute -top-3 left-1/2 transform -translate-x-1/2 bg-primary text-primary-foreground px-4 py-1 rounded-full text-sm font-medium bg-[var(--badge-bg)] text-[var(--badge-fg)] border-[var(--badge-border)]",
|
|
75
|
+
}, ariaLabel = "Pricing section", }) {
|
|
76
|
+
return (_jsx("section", { id: id || "pricing", className: cn(section.className, className), "aria-label": ariaLabel, children: _jsxs("div", { className: container.className, children: [_jsx("h2", { className: heading.className, children: pricingHeadingText }), _jsx("div", { className: grid.className, children: pricingPlans.map((plan, index) => (_jsx(motion.div, { initial: enableMotion ? { opacity: 0, y: 12 } : { opacity: 1, y: 0 }, whileInView: { opacity: 1, y: 0 }, viewport: enableMotion
|
|
77
|
+
? { once: true, amount: 0.2 }
|
|
78
|
+
: { once: true, amount: 0 }, transition: enableMotion
|
|
79
|
+
? {
|
|
80
|
+
type: "spring",
|
|
81
|
+
stiffness: 125,
|
|
82
|
+
damping: 50,
|
|
83
|
+
mass: 1,
|
|
84
|
+
delay: 0.12 + index * 0.06,
|
|
85
|
+
}
|
|
86
|
+
: { type: "tween", duration: 0 }, className: "motion-reduce:transform-none motion-reduce:transition-none", children: _jsx(PricingCard, { pricingCardTitle: plan.pricingPlanHeaderText, pricingCardPrice: plan.pricingPlanPrice, pricingCardFeatures: plan.pricingPlanFeatures, pricingCardCTALabel: plan.pricingPlanCTALabel, pricingCardCTAHref: plan.pricingPlanCTAHref, isPopular: plan.isPopular, card: {
|
|
87
|
+
className: cn(card.className, enableMotion
|
|
88
|
+
? "transition-all duration-200 hover:-translate-y-1 motion-reduce:transform-none motion-reduce:transition-none"
|
|
89
|
+
: "transition-none hover:!translate-y-0 hover:shadow-none"),
|
|
90
|
+
}, header: header, title: title, price: price, features: features, featureItem: featureItem, cta: cta, popularBadge: popularBadge }) }, index))) })] }) }));
|
|
91
|
+
}
|