@nextworks/blocks-sections 0.1.0-alpha.11 → 0.1.0-alpha.14
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/dist-types/components/About.d.ts +93 -0
- package/dist-types/components/About.d.ts.map +1 -0
- package/dist-types/components/CTA.d.ts +118 -0
- package/dist-types/components/CTA.d.ts.map +1 -0
- package/dist-types/components/Contact.d.ts +111 -0
- package/dist-types/components/Contact.d.ts.map +1 -0
- package/dist-types/components/FAQ.d.ts +89 -0
- package/dist-types/components/FAQ.d.ts.map +1 -0
- package/dist-types/components/Features.d.ts +111 -0
- package/dist-types/components/Features.d.ts.map +1 -0
- package/dist-types/components/Footer.d.ts +120 -0
- package/dist-types/components/Footer.d.ts.map +1 -0
- package/dist-types/components/HeroMotion.d.ts +107 -0
- package/dist-types/components/HeroMotion.d.ts.map +1 -0
- package/dist-types/components/HeroOverlay.d.ts +116 -0
- package/dist-types/components/HeroOverlay.d.ts.map +1 -0
- package/dist-types/components/HeroSplit.d.ts +98 -0
- package/dist-types/components/HeroSplit.d.ts.map +1 -0
- package/dist-types/components/Navbar.d.ts +112 -0
- package/dist-types/components/Navbar.d.ts.map +1 -0
- package/dist-types/components/Newsletter.d.ts +59 -0
- package/dist-types/components/Newsletter.d.ts.map +1 -0
- package/dist-types/components/PortfolioSimple.d.ts +137 -0
- package/dist-types/components/PortfolioSimple.d.ts.map +1 -0
- package/dist-types/components/Pricing.d.ts +96 -0
- package/dist-types/components/Pricing.d.ts.map +1 -0
- package/dist-types/components/ProcessTimeline.d.ts +121 -0
- package/dist-types/components/ProcessTimeline.d.ts.map +1 -0
- package/dist-types/components/ServicesGrid.d.ts +64 -0
- package/dist-types/components/ServicesGrid.d.ts.map +1 -0
- package/dist-types/components/Team.d.ts +115 -0
- package/dist-types/components/Team.d.ts.map +1 -0
- package/dist-types/components/Testimonials.d.ts +81 -0
- package/dist-types/components/Testimonials.d.ts.map +1 -0
- package/dist-types/components/TrustBadges.d.ts +80 -0
- package/dist-types/components/TrustBadges.d.ts.map +1 -0
- package/dist-types/components/index.d.ts +21 -0
- package/dist-types/components/index.d.ts.map +1 -0
- package/dist-types/index.d.ts +2 -0
- package/dist-types/index.d.ts.map +1 -0
- package/package.json +6 -4
- package/dist/components/About.js +0 -129
- package/dist/components/CTA.js +0 -58
- package/dist/components/Contact.js +0 -82
- package/dist/components/FAQ.js +0 -78
- package/dist/components/Features.js +0 -109
- package/dist/components/Footer.js +0 -101
- package/dist/components/HeroMotion.js +0 -55
- package/dist/components/HeroOverlay.js +0 -111
- package/dist/components/HeroSplit.js +0 -100
- package/dist/components/Navbar.js +0 -80
- package/dist/components/Newsletter.js +0 -34
- package/dist/components/PortfolioSimple.js +0 -180
- package/dist/components/Pricing.js +0 -91
- package/dist/components/ProcessTimeline.js +0 -88
- package/dist/components/ServicesGrid.js +0 -72
- package/dist/components/Team.js +0 -107
- package/dist/components/Testimonials.js +0 -46
- package/dist/components/TrustBadges.js +0 -46
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import Image from "next/image";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { Button } from "@nextworks/blocks-core";
|
|
6
|
-
import { cn } from "@nextworks/blocks-core";
|
|
7
|
-
/**
|
|
8
|
-
* Full-bleed image hero with color overlay, heading/subheading, and up to two CTAs.
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* - Styling: slot-style overrides for container, imageContainer, overlay, content, textContainer.
|
|
12
|
-
* - Motion: enableMotion applies hover-lift transitions to CTA buttons only.
|
|
13
|
-
* - Accessibility: uses semantic <section> with aria-label; background image is
|
|
14
|
-
* decorative with overlay and text content provided separately.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* <HeroOverlay
|
|
18
|
-
* image={{ src: "/hero.png", alt: "" }}
|
|
19
|
-
* heading="Welcome"
|
|
20
|
-
* subheading="Build faster with our blocks"
|
|
21
|
-
* cta1={{ label: "Get Started", href: "#getstarted" }}
|
|
22
|
-
* />
|
|
23
|
-
*/
|
|
24
|
-
export function HeroOverlay({ id, className,
|
|
25
|
-
// Deprecated flat image defaults (still supported)
|
|
26
|
-
imageSrc = "/hero.png", imageAlt = "Hero background image", imageObjectFit = "cover", imageObjectPosition = "center",
|
|
27
|
-
// New structured image (optional)
|
|
28
|
-
image,
|
|
29
|
-
// Keep string defaults; object form supported
|
|
30
|
-
heading = "Lorem ipsum dolor sit amet, consectetur", subheading = "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", overlayRGBA = "rgba(255, 255, 255, 0.8)", overlayDarkRGBA = "rgba(0, 0, 0, 0.8)",
|
|
31
|
-
// Deprecated CTA label/href (still supported)
|
|
32
|
-
ctaBtn1Label = "Get Started", ctaBtn1Href = "#getstarted", ctaBtn2Label, ctaBtn2Href,
|
|
33
|
-
// Layout containers
|
|
34
|
-
container = { className: "relative h-[440px] md:h-[500px] w-full" },
|
|
35
|
-
// container = { className: "relative h-[500px] md:h-[600px] w-full" },
|
|
36
|
-
section, imageContainer = { className: "absolute inset-0" }, overlay = { className: "absolute inset-0 z-10 pointer-events-none" }, content = {
|
|
37
|
-
className: "relative z-20 h-full w-full flex flex-col justify-start items-center pt-14 pb-8 px-8",
|
|
38
|
-
}, textContainer = { className: "max-w-4xl space-y-6 text-center" },
|
|
39
|
-
// Typography defaults (deprecated props still merged)
|
|
40
|
-
headingStyle = { className: "" }, subheadingStyle = { className: "" },
|
|
41
|
-
// CTA containers and variants
|
|
42
|
-
ctaContainer = {
|
|
43
|
-
className: "flex flex-col sm:flex-row gap-4 mt-6 justify-center items-center",
|
|
44
|
-
}, cta1, cta2, ariaLabel = "Hero section", enableMotion = true, }) {
|
|
45
|
-
var _a, _b, _c, _d, _e, _f;
|
|
46
|
-
// Merge new structured image with deprecated flat props
|
|
47
|
-
const effImage = {
|
|
48
|
-
src: (_a = image === null || image === void 0 ? void 0 : image.src) !== null && _a !== void 0 ? _a : imageSrc,
|
|
49
|
-
alt: (_b = image === null || image === void 0 ? void 0 : image.alt) !== null && _b !== void 0 ? _b : imageAlt,
|
|
50
|
-
objectFit: (_c = image === null || image === void 0 ? void 0 : image.objectFit) !== null && _c !== void 0 ? _c : imageObjectFit,
|
|
51
|
-
objectPosition: (_d = image === null || image === void 0 ? void 0 : image.objectPosition) !== null && _d !== void 0 ? _d : imageObjectPosition,
|
|
52
|
-
className: image === null || image === void 0 ? void 0 : image.className,
|
|
53
|
-
};
|
|
54
|
-
// Defaults and normalization like HeroSplit
|
|
55
|
-
const defaultHeading = {
|
|
56
|
-
text: "Lorem ipsum dolor sit amet, consectetur",
|
|
57
|
-
className: "text-4xl md:text-5xl font-bold text-center leading-tight text-foreground",
|
|
58
|
-
};
|
|
59
|
-
const defaultSubheading = {
|
|
60
|
-
text: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
61
|
-
className: "text-xl md:text-2xl text-center text-foreground leading-relaxed",
|
|
62
|
-
};
|
|
63
|
-
const normalizedHeading = typeof heading === "string"
|
|
64
|
-
? {
|
|
65
|
-
text: heading,
|
|
66
|
-
className: cn(defaultHeading.className, headingStyle === null || headingStyle === void 0 ? void 0 : headingStyle.className),
|
|
67
|
-
}
|
|
68
|
-
: {
|
|
69
|
-
text: (_e = heading === null || heading === void 0 ? void 0 : heading.text) !== null && _e !== void 0 ? _e : defaultHeading.text,
|
|
70
|
-
className: cn(defaultHeading.className, headingStyle === null || headingStyle === void 0 ? void 0 : headingStyle.className, heading === null || heading === void 0 ? void 0 : heading.className),
|
|
71
|
-
};
|
|
72
|
-
const normalizedSubheading = typeof subheading === "string"
|
|
73
|
-
? {
|
|
74
|
-
text: subheading,
|
|
75
|
-
className: cn(defaultSubheading.className, subheadingStyle === null || subheadingStyle === void 0 ? void 0 : subheadingStyle.className),
|
|
76
|
-
}
|
|
77
|
-
: {
|
|
78
|
-
text: (_f = subheading === null || subheading === void 0 ? void 0 : subheading.text) !== null && _f !== void 0 ? _f : defaultSubheading.text,
|
|
79
|
-
className: cn(defaultSubheading.className, subheadingStyle === null || subheadingStyle === void 0 ? void 0 : subheadingStyle.className, subheading === null || subheading === void 0 ? void 0 : subheading.className),
|
|
80
|
-
};
|
|
81
|
-
// Motion class for CTA buttons
|
|
82
|
-
const buttonLift = enableMotion
|
|
83
|
-
? "transition-all duration-200 hover:-translate-y-0.5"
|
|
84
|
-
: "transition-none hover:!translate-y-0";
|
|
85
|
-
// CTA defaults and normalization (merge, don't replace).
|
|
86
|
-
const defaultCta1 = {
|
|
87
|
-
label: ctaBtn1Label,
|
|
88
|
-
href: ctaBtn1Href,
|
|
89
|
-
variant: "default",
|
|
90
|
-
size: "lg",
|
|
91
|
-
className: "",
|
|
92
|
-
};
|
|
93
|
-
const defaultCta2 = {
|
|
94
|
-
label: ctaBtn2Label,
|
|
95
|
-
href: ctaBtn2Href,
|
|
96
|
-
variant: "outline",
|
|
97
|
-
size: "lg",
|
|
98
|
-
className: "",
|
|
99
|
-
};
|
|
100
|
-
const mergedCta1 = Object.assign(Object.assign(Object.assign({}, defaultCta1), (cta1 !== null && cta1 !== void 0 ? cta1 : {})), { className: cn(defaultCta1.className, cta1 === null || cta1 === void 0 ? void 0 : cta1.className, buttonLift) });
|
|
101
|
-
const mergedCta2 = cta2 || ctaBtn2Label || ctaBtn2Href
|
|
102
|
-
? Object.assign(Object.assign(Object.assign({}, defaultCta2), (cta2 !== null && cta2 !== void 0 ? cta2 : {})), { className: cn(defaultCta2.className, cta2 === null || cta2 === void 0 ? void 0 : cta2.className, buttonLift) }) : undefined;
|
|
103
|
-
// buttonLift defined above
|
|
104
|
-
// Merge alias section.className with container.className
|
|
105
|
-
const finalContainerClass = cn(container.className, section === null || section === void 0 ? void 0 : section.className);
|
|
106
|
-
const showCta2 = !!(mergedCta2 === null || mergedCta2 === void 0 ? void 0 : mergedCta2.label);
|
|
107
|
-
return (_jsxs("section", { id: id, className: cn(finalContainerClass, className), "aria-label": ariaLabel, children: [_jsx("div", { className: imageContainer.className, children: _jsx(Image, { src: effImage.src, alt: effImage.alt, fill: true, sizes: "100vw", quality: 75, priority: true, className: effImage.className, style: {
|
|
108
|
-
objectFit: effImage.objectFit,
|
|
109
|
-
objectPosition: effImage.objectPosition,
|
|
110
|
-
} }) }), _jsx("div", { className: overlay.className, style: { backgroundColor: overlayRGBA }, "aria-hidden": true }), _jsx("div", { className: cn(overlay.className, "hidden dark:block"), style: { backgroundColor: overlayDarkRGBA }, "aria-hidden": true }), _jsx("div", { className: content.className, children: _jsxs("div", { className: textContainer.className, children: [_jsx("h1", { className: normalizedHeading.className, children: normalizedHeading.text }), _jsx("p", { className: normalizedSubheading.className, children: normalizedSubheading.text }), _jsxs("div", { className: ctaContainer.className, children: [mergedCta1.label && (_jsx(Button, { asChild: true, variant: mergedCta1.variant, size: mergedCta1.size, className: mergedCta1.className, unstyled: mergedCta1.unstyled, style: mergedCta1.style, children: _jsx(Link, { href: mergedCta1.href || "#", "aria-label": mergedCta1.label, children: mergedCta1.label }) })), showCta2 && (_jsx(Button, { asChild: true, variant: mergedCta2.variant, size: mergedCta2.size, className: mergedCta2.className, unstyled: mergedCta2.unstyled, style: mergedCta2.style, children: _jsx(Link, { href: mergedCta2.href || "#", "aria-label": mergedCta2.label, children: mergedCta2.label }) }))] })] }) })] }));
|
|
111
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import Image from "next/image";
|
|
4
|
-
import Link from "next/link";
|
|
5
|
-
import { Button } from "@nextworks/blocks-core";
|
|
6
|
-
import { cn } from "@nextworks/blocks-core";
|
|
7
|
-
/**
|
|
8
|
-
* Split hero with text on one side and an image on the other.
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* - Text props accept string or object forms to easily override classes.
|
|
12
|
-
* - Full-bleed layout absolutely positions the image on larger screens.
|
|
13
|
-
* - Motion only affects the subtle hover lift on CTA buttons.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* <HeroSplit heading="Build faster" subheading="Launch confidently" />
|
|
17
|
-
*/
|
|
18
|
-
export function HeroSplit({ id, className, heading, subheading, section, cta1, cta2, image = {
|
|
19
|
-
src: "/placeholders/gallery/hero-pexels-broken-9945014.avif",
|
|
20
|
-
alt: "Hero image",
|
|
21
|
-
className: "object-contain",
|
|
22
|
-
}, imageContainer, textContainer = { className: "flex-1 p-5" }, buttonsContainer = { className: "flex flex-col md:flex-row gap-4 mt-6" }, textAlign = "center", fallback, ariaLabel = "Hero section", enableMotion = true, imageLayout = "full-bleed", }) {
|
|
23
|
-
var _a, _b;
|
|
24
|
-
const lgTextAlignClasses = {
|
|
25
|
-
left: "lg:text-left",
|
|
26
|
-
right: "lg:text-right",
|
|
27
|
-
center: "lg:text-center",
|
|
28
|
-
};
|
|
29
|
-
const lgItemsAlignClasses = {
|
|
30
|
-
left: "lg:items-start",
|
|
31
|
-
right: "lg:items-end",
|
|
32
|
-
center: "lg:items-center",
|
|
33
|
-
};
|
|
34
|
-
const lgJustifyAlignClasses = {
|
|
35
|
-
left: "lg:justify-start",
|
|
36
|
-
right: "lg:justify-end",
|
|
37
|
-
center: "lg:justify-center",
|
|
38
|
-
};
|
|
39
|
-
const buttonLift = enableMotion
|
|
40
|
-
? "transition-all duration-200 hover:-translate-y-0.5"
|
|
41
|
-
: "transition-none hover:!translate-y-0";
|
|
42
|
-
// Defaults for text props (normalized below)
|
|
43
|
-
const defaultHeading = {
|
|
44
|
-
text: "Lorem ipsum dolor sit amet",
|
|
45
|
-
className: "text-3xl md:text-4xl font-bold leading-tight text-foreground",
|
|
46
|
-
};
|
|
47
|
-
const defaultSubheading = {
|
|
48
|
-
text: "Consectetur adipiscing elit, sed do eiusmod tempor.",
|
|
49
|
-
className: "text-lg md:text-xl text-foreground mt-4 mb-6",
|
|
50
|
-
};
|
|
51
|
-
const normalizedHeading = typeof heading === "string"
|
|
52
|
-
? { text: heading, className: defaultHeading.className }
|
|
53
|
-
: {
|
|
54
|
-
text: (_a = heading === null || heading === void 0 ? void 0 : heading.text) !== null && _a !== void 0 ? _a : defaultHeading.text,
|
|
55
|
-
className: cn(defaultHeading.className, heading === null || heading === void 0 ? void 0 : heading.className),
|
|
56
|
-
};
|
|
57
|
-
const normalizedSubheading = typeof subheading === "string"
|
|
58
|
-
? { text: subheading, className: defaultSubheading.className }
|
|
59
|
-
: {
|
|
60
|
-
text: (_b = subheading === null || subheading === void 0 ? void 0 : subheading.text) !== null && _b !== void 0 ? _b : defaultSubheading.text,
|
|
61
|
-
className: cn(defaultSubheading.className, subheading === null || subheading === void 0 ? void 0 : subheading.className),
|
|
62
|
-
};
|
|
63
|
-
// CTA defaults and normalization (merge, don't replace)
|
|
64
|
-
const defaultCta1 = {
|
|
65
|
-
label: "Get Started",
|
|
66
|
-
href: "#contact",
|
|
67
|
-
variant: "default",
|
|
68
|
-
size: "default",
|
|
69
|
-
className: "shadow-lg hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5",
|
|
70
|
-
};
|
|
71
|
-
const mergedCta1 = Object.assign(Object.assign(Object.assign({}, defaultCta1), (cta1 !== null && cta1 !== void 0 ? cta1 : {})), { className: cn(defaultCta1.className, cta1 === null || cta1 === void 0 ? void 0 : cta1.className, buttonLift) });
|
|
72
|
-
const defaultCta2 = {
|
|
73
|
-
label: "Learn More",
|
|
74
|
-
href: "#",
|
|
75
|
-
variant: "outline",
|
|
76
|
-
size: "default",
|
|
77
|
-
className: "",
|
|
78
|
-
};
|
|
79
|
-
const mergedCta2 = cta2
|
|
80
|
-
? Object.assign(Object.assign(Object.assign({}, defaultCta2), cta2), { className: cn(defaultCta2.className, cta2.className, buttonLift) }) : undefined;
|
|
81
|
-
const defaultSectionPadded = cn("px-8 pt-8 pb-8 md:min-h-[60vh] lg:min-h-[70vh]");
|
|
82
|
-
const defaultSectionFull = cn("relative overflow-hidden pt-0 pb-0 md:min-h-[60vh] lg:min-h-[70vh]");
|
|
83
|
-
const defaultImageContainerPadded = cn("relative mb-4 h-48 min-h-[12rem] w-full md:mb-0 md:h-72 md:w-1/2");
|
|
84
|
-
// For full-bleed: absolute positioning on large screens to break out completely
|
|
85
|
-
const defaultImageContainerFull = cn("relative h-48 w-full md:absolute md:inset-y-0 md:right-0 md:h-full md:w-1/2");
|
|
86
|
-
const baseSectionClass = imageLayout === "full-bleed" ? defaultSectionFull : defaultSectionPadded;
|
|
87
|
-
const finalSectionClass = cn(baseSectionClass, section === null || section === void 0 ? void 0 : section.className);
|
|
88
|
-
const baseImageContainerClass = imageLayout === "full-bleed"
|
|
89
|
-
? defaultImageContainerFull
|
|
90
|
-
: defaultImageContainerPadded;
|
|
91
|
-
const finalImageContainerClass = cn(baseImageContainerClass, imageContainer === null || imageContainer === void 0 ? void 0 : imageContainer.className);
|
|
92
|
-
// -- This overrides the image.className that is passed from importing components ... devs should be able to decide on cover/contain from the importing component.
|
|
93
|
-
const finalImageClass = cn(image === null || image === void 0 ? void 0 : image.className, imageLayout === "full-bleed"
|
|
94
|
-
? "object-cover object-center md:object-right"
|
|
95
|
-
: "object-contain object-center");
|
|
96
|
-
return (_jsx("section", { id: id, className: cn(finalSectionClass, className), "aria-label": ariaLabel, children: _jsxs("div", { className: cn("flex flex-col items-stretch", imageLayout === "full-bleed"
|
|
97
|
-
? "md:block"
|
|
98
|
-
: "md:flex-row md:items-start"), children: [_jsx("div", { className: cn("w-full", imageLayout === "full-bleed" &&
|
|
99
|
-
"relative z-10 md:w-1/2 md:px-8 md:py-12", textContainer.className), children: _jsxs("div", { className: cn("flex flex-col items-center", lgItemsAlignClasses[textAlign]), children: [_jsx("h1", { className: cn("text-center", lgTextAlignClasses[textAlign], normalizedHeading.className), children: normalizedHeading.text }), _jsx("p", { className: cn("text-center", lgTextAlignClasses[textAlign], normalizedSubheading.className), children: normalizedSubheading.text }), _jsxs("div", { className: cn("flex flex-col items-center justify-center gap-4 sm:flex-row", lgJustifyAlignClasses[textAlign], lgItemsAlignClasses[textAlign], buttonsContainer.className), children: [mergedCta1.label && (_jsx(Button, { asChild: true, variant: mergedCta1.variant, size: mergedCta1.size, className: mergedCta1.className, unstyled: mergedCta1.unstyled, style: mergedCta1.style, children: _jsx(Link, { href: mergedCta1.href || "#", "aria-label": mergedCta1.label, children: mergedCta1.label }) })), (mergedCta2 === null || mergedCta2 === void 0 ? void 0 : mergedCta2.label) && (_jsx(Button, { asChild: true, variant: mergedCta2.variant, size: mergedCta2.size, className: mergedCta2.className, unstyled: mergedCta2.unstyled, style: mergedCta2.style, children: _jsx(Link, { href: mergedCta2.href || "#", "aria-label": mergedCta2.label, children: mergedCta2.label }) }))] })] }) }), _jsx("div", { className: cn(finalImageContainerClass), children: (image === null || image === void 0 ? void 0 : image.src) ? (_jsx(Image, { priority: true, fetchPriority: "high", src: image.src, alt: image.alt || "Hero image", className: finalImageClass, fill: true, sizes: "(min-width: 768px) 50vw, 100vw", quality: 75 })) : (_jsx("div", { className: "absolute inset-0", children: fallback || null })) })] }) }));
|
|
100
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// components/sections/Navbar.tsx
|
|
2
|
-
"use client";
|
|
3
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
import Link from "next/link";
|
|
6
|
-
import { Button } from "@nextworks/blocks-core";
|
|
7
|
-
import { ThemeToggle } from "@nextworks/blocks-core";
|
|
8
|
-
import { cn } from "@nextworks/blocks-core";
|
|
9
|
-
import { Menu, X } from "lucide-react";
|
|
10
|
-
const defaultMenuItems = [
|
|
11
|
-
{ label: "Home", href: "#home" },
|
|
12
|
-
{ label: "Features", href: "#features" },
|
|
13
|
-
{ label: "Pricing", href: "#pricing" },
|
|
14
|
-
{ label: "FAQ", href: "#faq" },
|
|
15
|
-
{ label: "Contact", href: "#contact" },
|
|
16
|
-
];
|
|
17
|
-
/**
|
|
18
|
-
* Responsive navbar with brand, menu links, color mode toggle, and optional CTA.
|
|
19
|
-
*
|
|
20
|
-
* @remarks
|
|
21
|
-
* - Sticky behavior is enabled by default (sticky top-0 z-50).
|
|
22
|
-
* - Mobile menu toggles with a button and traps body scroll while open.
|
|
23
|
-
* - Accessibility: Focus rings are applied to interactive elements; aria
|
|
24
|
-
* attributes are set on the toggle button for state.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* <Navbar brand="Acme" />
|
|
28
|
-
*/
|
|
29
|
-
export function Navbar({ id, navHeight = "h-16", brand = "Brand Name", brandNode, menuItems = defaultMenuItems, ctaButton = { label: "Get Started", href: "#contact" }, showColorModeToggle = true, sticky = true, className, nav = {
|
|
30
|
-
className: "bg-background text-foreground border-b border-border",
|
|
31
|
-
}, brandText = {
|
|
32
|
-
className: "text-2xl font-bold text-primary",
|
|
33
|
-
}, links = {
|
|
34
|
-
className: "text-base font-normal text-foreground hover:text-gray-500 dark:hover-text-gray-400 transition-colors",
|
|
35
|
-
}, ctaButtonStyle = {
|
|
36
|
-
variant: "default",
|
|
37
|
-
size: "default",
|
|
38
|
-
className: "shadow-lg hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5",
|
|
39
|
-
}, mobileMenu = {
|
|
40
|
-
className: "border-t border-border",
|
|
41
|
-
},
|
|
42
|
-
/* new slot defaults */
|
|
43
|
-
container = { className: "" }, brandWrapper = { className: "" }, desktopMenu = {
|
|
44
|
-
className: "hidden items-center space-x-1 lg:flex lg:space-x-6",
|
|
45
|
-
}, toggleButton = {
|
|
46
|
-
className: "hover:bg-accent flex items-center justify-center rounded-md p-2 transition-colors",
|
|
47
|
-
}, colorModeWrapper = { className: "ml-2" }, ctaButtonWrapper = { className: "ml-2" }, mobileMenuInner = { className: "space-y-2 px-4 pt-2 pb-4" }, mobileLinks = { className: "hover:bg-accent" }, themeToggle = {}, ariaLabel = "Main navigation", }) {
|
|
48
|
-
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
49
|
-
const mobileMenuId = "navbar-mobile-menu";
|
|
50
|
-
// Measure header height for reliable max height on mobile menu
|
|
51
|
-
const headerRef = useRef(null);
|
|
52
|
-
const [headerHeightPx, setHeaderHeightPx] = useState(0);
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
const update = () => { var _a, _b; return setHeaderHeightPx((_b = (_a = headerRef.current) === null || _a === void 0 ? void 0 : _a.offsetHeight) !== null && _b !== void 0 ? _b : 0); };
|
|
55
|
-
update();
|
|
56
|
-
window.addEventListener("resize", update);
|
|
57
|
-
return () => window.removeEventListener("resize", update);
|
|
58
|
-
}, []);
|
|
59
|
-
// Lock body scroll when mobile menu is open
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
const originalOverflow = document.body.style.overflow;
|
|
62
|
-
if (isMobileMenuOpen) {
|
|
63
|
-
document.body.style.overflow = "hidden";
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
document.body.style.overflow = originalOverflow || "";
|
|
67
|
-
}
|
|
68
|
-
return () => {
|
|
69
|
-
document.body.style.overflow = originalOverflow || "";
|
|
70
|
-
};
|
|
71
|
-
}, [isMobileMenuOpen]);
|
|
72
|
-
return (_jsxs("nav", { id: id, className: cn("w-full", sticky && "sticky top-0 z-50", nav.className, className), "aria-label": ariaLabel, children: [_jsxs("div", { ref: headerRef, className: cn("flex items-center justify-between px-4 py-2", navHeight, container.className), children: [_jsxs("div", { className: cn("mr-2 flex items-center gap-2 pl-2", brandWrapper.className), children: [brandNode, brand && _jsx("h1", { className: cn(brandText.className), children: brand })] }), _jsx("button", { "aria-label": "Toggle mobile menu", "aria-expanded": isMobileMenuOpen, "aria-controls": mobileMenuId, className: cn("lg:hidden", toggleButton.className),
|
|
73
|
-
// className={cn("lg:hidden", toggleButton.className)}
|
|
74
|
-
onClick: () => setIsMobileMenuOpen(!isMobileMenuOpen), children: isMobileMenuOpen ? (_jsx(X, { className: "h-6 w-6" })) : (_jsx(Menu, { className: "h-6 w-6" })) }), _jsxs("div", { className: cn("hidden md:flex", desktopMenu.className), children: [menuItems.map((item) => (_jsx(Link, { href: item.href, className: cn("rounded-md px-2 py-2 transition-colors duration-200 lg:px-4", "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none", "focus-visible:ring-[var(--navbar-ring)]", links.className), children: item.label }, item.label))), showColorModeToggle && (_jsx("div", { className: cn(colorModeWrapper.className), children: _jsx(ThemeToggle, Object.assign({}, themeToggle)) })), ctaButton && (_jsx(Button, { asChild: true, unstyled: ctaButtonStyle.unstyled, variant: ctaButtonStyle.variant, size: ctaButtonStyle.size, className: cn(ctaButtonWrapper.className, ctaButtonStyle.className), style: ctaButtonStyle.style, children: _jsx(Link, { href: ctaButton.href, "aria-label": ctaButton.label, children: ctaButton.label }) }))] })] }), isMobileMenuOpen && (_jsx("div", { id: mobileMenuId, className: cn("md:hidden",
|
|
75
|
-
// "lg:hidden",
|
|
76
|
-
"overflow-y-auto overscroll-y-contain", nav.className, mobileMenu.className), style: {
|
|
77
|
-
maxHeight: `calc(100dvh - ${headerHeightPx}px)`,
|
|
78
|
-
WebkitOverflowScrolling: "touch",
|
|
79
|
-
}, children: _jsxs("div", { className: cn(mobileMenuInner.className), children: [menuItems.map((item) => (_jsx(Link, { href: item.href, className: cn("block w-full rounded-md px-2 py-4 text-center transition-colors duration-200", mobileLinks.className, "focus:ring-ring focus:ring-2 focus:ring-offset-2 focus:outline-none", "focus-visible:ring-[var(--navbar-ring)]", links.className), onClick: () => setIsMobileMenuOpen(false), children: item.label }, item.label))), showColorModeToggle && (_jsx("div", { className: "flex w-full justify-center pt-4", children: _jsx(ThemeToggle, Object.assign({}, themeToggle)) })), ctaButton && (_jsx("div", { className: "flex w-full justify-center pt-6", children: _jsx(Button, { asChild: true, unstyled: ctaButtonStyle.unstyled, variant: ctaButtonStyle.variant, size: ctaButtonStyle.size, className: cn("w-full max-w-xs", ctaButtonStyle.className), style: ctaButtonStyle.style, children: _jsx(Link, { href: ctaButton.href, "aria-label": ctaButton.label, children: ctaButton.label }) }) }))] }) }))] }));
|
|
80
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { cn } from "@nextworks/blocks-core";
|
|
5
|
-
import { Input, Button } from "@nextworks/blocks-core";
|
|
6
|
-
// Default classes used for slot merging. Consumers' slot.className will be merged after these.
|
|
7
|
-
const DEFAULT_SECTION = "py-12 bg-[var(--card-bg)] text-[var(--card-fg)]";
|
|
8
|
-
const DEFAULT_CONTAINER = "max-w-3xl mx-auto px-4";
|
|
9
|
-
const DEFAULT_HEADER = "text-center mb-6";
|
|
10
|
-
const DEFAULT_HEADING = "text-2xl font-bold font-poppins text-foreground text-[var(--heading-fg)]";
|
|
11
|
-
const DEFAULT_SUBHEADING = "text-sm font-inter text-muted-foreground max-w-xl mx-auto leading-relaxed text-[var(--subheading-fg)]";
|
|
12
|
-
const DEFAULT_FORM = "w-full";
|
|
13
|
-
const DEFAULT_FORM_ROW = "mt-6 flex flex-col sm:flex-row items-center gap-3 justify-center";
|
|
14
|
-
const DEFAULT_INPUT = "w-full sm:flex-1 p-3 rounded-md border border-[var(--input-border)] bg-[var(--input-bg)] text-[var(--input-fg)] placeholder:text-[var(--input-placeholder)]";
|
|
15
|
-
const DEFAULT_BUTTON = "w-full sm:w-auto px-5 py-3 rounded-md bg-primary hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary text-primary-foreground font-medium shadow-lg hover:shadow-xl transition-all duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]";
|
|
16
|
-
/**
|
|
17
|
-
* Compact newsletter/signup block.
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* <Newsletter section={{ className: "bg-gradient-to-r ..." }} />
|
|
21
|
-
*
|
|
22
|
-
* Slots exposed: section, container, header, heading, subheading, form, formRow, input, button
|
|
23
|
-
*/
|
|
24
|
-
export function Newsletter({ id, className, headingText = "Stay in the loop", subheadingText = "Subscribe for product news, tips, and updates.", onSubmit, section, container, header, heading, subheading, form, formRow, input, button, enableMotion = true, ariaLabel = "Newsletter signup section", }) {
|
|
25
|
-
const [email, setEmail] = React.useState("");
|
|
26
|
-
const handleSubmit = (e) => {
|
|
27
|
-
e.preventDefault();
|
|
28
|
-
onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(e, email);
|
|
29
|
-
// Optimistic clear; consumer can override behavior via onSubmit
|
|
30
|
-
setEmail("");
|
|
31
|
-
};
|
|
32
|
-
return (_jsx("section", { id: id, className: cn(DEFAULT_SECTION, section === null || section === void 0 ? void 0 : section.className, className), "aria-label": ariaLabel, children: _jsxs("div", { className: cn(DEFAULT_CONTAINER, container === null || container === void 0 ? void 0 : container.className), children: [_jsxs("div", { className: cn(DEFAULT_HEADER, header === null || header === void 0 ? void 0 : header.className), children: [_jsx("h2", { className: cn(DEFAULT_HEADING, heading === null || heading === void 0 ? void 0 : heading.className), children: headingText }), _jsx("p", { className: cn(DEFAULT_SUBHEADING, subheading === null || subheading === void 0 ? void 0 : subheading.className), children: subheadingText })] }), _jsx("form", { className: cn(DEFAULT_FORM, form === null || form === void 0 ? void 0 : form.className), onSubmit: handleSubmit, "aria-label": "Newsletter signup form", children: _jsxs("div", { className: cn(DEFAULT_FORM_ROW, formRow === null || formRow === void 0 ? void 0 : formRow.className), children: [_jsx(Input, { id: "newsletter-email", name: "email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "Your email address", required: true, "aria-label": "Email address", className: cn(DEFAULT_INPUT, input === null || input === void 0 ? void 0 : input.className) }), _jsx(Button, { type: "submit", unstyled: button === null || button === void 0 ? void 0 : button.unstyled, variant: button === null || button === void 0 ? void 0 : button.variant, size: button === null || button === void 0 ? void 0 : button.size, className: cn(DEFAULT_BUTTON, button === null || button === void 0 ? void 0 : button.className, !enableMotion &&
|
|
33
|
-
"transition-none hover:!translate-y-0 hover:shadow-none"), children: "Subscribe" })] }) })] }) }));
|
|
34
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
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
|
-
}
|