@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.
Files changed (80) hide show
  1. package/README.md +44 -0
  2. package/dist/components/About.d.ts +93 -0
  3. package/dist/components/About.d.ts.map +1 -0
  4. package/dist/components/About.js +129 -0
  5. package/dist/components/About.jsx +153 -0
  6. package/dist/components/CTA.d.ts +118 -0
  7. package/dist/components/CTA.d.ts.map +1 -0
  8. package/dist/components/CTA.js +58 -0
  9. package/dist/components/CTA.jsx +88 -0
  10. package/dist/components/Contact.d.ts +111 -0
  11. package/dist/components/Contact.d.ts.map +1 -0
  12. package/dist/components/Contact.js +82 -0
  13. package/dist/components/Contact.jsx +107 -0
  14. package/dist/components/FAQ.d.ts +89 -0
  15. package/dist/components/FAQ.d.ts.map +1 -0
  16. package/dist/components/FAQ.js +78 -0
  17. package/dist/components/FAQ.jsx +98 -0
  18. package/dist/components/Features.d.ts +111 -0
  19. package/dist/components/Features.d.ts.map +1 -0
  20. package/dist/components/Features.js +109 -0
  21. package/dist/components/Features.jsx +122 -0
  22. package/dist/components/Footer.d.ts +120 -0
  23. package/dist/components/Footer.d.ts.map +1 -0
  24. package/dist/components/Footer.js +101 -0
  25. package/dist/components/Footer.jsx +138 -0
  26. package/dist/components/HeroMotion.d.ts +107 -0
  27. package/dist/components/HeroMotion.d.ts.map +1 -0
  28. package/dist/components/HeroMotion.js +55 -0
  29. package/dist/components/HeroMotion.jsx +95 -0
  30. package/dist/components/HeroOverlay.d.ts +116 -0
  31. package/dist/components/HeroOverlay.d.ts.map +1 -0
  32. package/dist/components/HeroOverlay.js +111 -0
  33. package/dist/components/HeroOverlay.jsx +141 -0
  34. package/dist/components/HeroSplit.d.ts +98 -0
  35. package/dist/components/HeroSplit.d.ts.map +1 -0
  36. package/dist/components/HeroSplit.js +100 -0
  37. package/dist/components/HeroSplit.jsx +134 -0
  38. package/dist/components/Navbar.d.ts +112 -0
  39. package/dist/components/Navbar.d.ts.map +1 -0
  40. package/dist/components/Navbar.js +80 -0
  41. package/dist/components/Navbar.jsx +127 -0
  42. package/dist/components/Newsletter.d.ts +59 -0
  43. package/dist/components/Newsletter.d.ts.map +1 -0
  44. package/dist/components/Newsletter.js +34 -0
  45. package/dist/components/Newsletter.jsx +54 -0
  46. package/dist/components/PortfolioSimple.d.ts +137 -0
  47. package/dist/components/PortfolioSimple.d.ts.map +1 -0
  48. package/dist/components/PortfolioSimple.js +180 -0
  49. package/dist/components/PortfolioSimple.jsx +259 -0
  50. package/dist/components/Pricing.d.ts +96 -0
  51. package/dist/components/Pricing.d.ts.map +1 -0
  52. package/dist/components/Pricing.js +91 -0
  53. package/dist/components/Pricing.jsx +103 -0
  54. package/dist/components/ProcessTimeline.d.ts +121 -0
  55. package/dist/components/ProcessTimeline.d.ts.map +1 -0
  56. package/dist/components/ProcessTimeline.js +88 -0
  57. package/dist/components/ProcessTimeline.jsx +151 -0
  58. package/dist/components/ServicesGrid.d.ts +64 -0
  59. package/dist/components/ServicesGrid.d.ts.map +1 -0
  60. package/dist/components/ServicesGrid.js +72 -0
  61. package/dist/components/ServicesGrid.jsx +97 -0
  62. package/dist/components/Team.d.ts +115 -0
  63. package/dist/components/Team.d.ts.map +1 -0
  64. package/dist/components/Team.js +107 -0
  65. package/dist/components/Team.jsx +135 -0
  66. package/dist/components/Testimonials.d.ts +81 -0
  67. package/dist/components/Testimonials.d.ts.map +1 -0
  68. package/dist/components/Testimonials.js +46 -0
  69. package/dist/components/Testimonials.jsx +58 -0
  70. package/dist/components/TrustBadges.d.ts +80 -0
  71. package/dist/components/TrustBadges.d.ts.map +1 -0
  72. package/dist/components/TrustBadges.js +46 -0
  73. package/dist/components/TrustBadges.jsx +64 -0
  74. package/dist/components/index.d.ts +21 -0
  75. package/dist/components/index.d.ts.map +1 -0
  76. package/dist/components/index.js +18 -0
  77. package/dist/index.d.ts +2 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +1 -0
  80. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @nextworks/blocks-sections
2
+
3
+ Reusable page sections for marketing / SaaS sites, built on top of `@nextworks/blocks-core`.
4
+
5
+ Examples include:
6
+
7
+ - Navbar, Hero, Features, Pricing, Testimonials
8
+ - FAQ, Contact, Newsletter
9
+ - Portfolio, Services, Team, Trust badges
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @nextworks/blocks-sections @nextworks/blocks-core
15
+ # or
16
+ pnpm add @nextworks/blocks-sections @nextworks/blocks-core
17
+ # or
18
+ yarn add @nextworks/blocks-sections @nextworks/blocks-core
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```tsx
24
+ import { Navbar, Hero, Features, Pricing } from "@nextworks/blocks-sections";
25
+
26
+ export default function Page() {
27
+ return (
28
+ <>
29
+ <Navbar />
30
+ <Hero />
31
+ <Features />
32
+ <Pricing />
33
+ </>
34
+ );
35
+ }
36
+ ```
37
+
38
+ These components assume you have a Tailwind + React + Next.js setup and that you wrap your app with the appropriate theme provider from `@nextworks/blocks-core`.
39
+
40
+ For a copy-based workflow (files under `components/` and `app/` in your own app), use the CLI:
41
+
42
+ ```bash
43
+ npx nextworks add blocks
44
+ ```
@@ -0,0 +1,93 @@
1
+ import React from "react";
2
+ /**
3
+ * Data used to render a single statistic in the About section.
4
+ *
5
+ * @remarks
6
+ * Typical examples include counts like projects completed or years of
7
+ * experience. The suffix lets you append symbols such as "+", "%", or "/7".
8
+ *
9
+ * @public
10
+ */
11
+ export interface AboutStatData {
12
+ /** The main numeric/text value (e.g., "50") */
13
+ value?: string;
14
+ /** Short label describing the stat (e.g., "Successful Projects") */
15
+ label?: string;
16
+ /** Optional suffix appended to the value (e.g., "+") */
17
+ suffix?: string;
18
+ }
19
+ /**
20
+ * Props for the About section component.
21
+ *
22
+ * @remarks
23
+ * - Styling: exposes slot-style className overrides (section, container,
24
+ * inner, contentContainer, contentStack, subheading, heading, content,
25
+ * statsSection, statsGrid, statItem, statNumber, statLabel). Consumer
26
+ * classes are merged after defaults via cn().
27
+ * - Layout: text alignment can be set via aboutTextAlign.
28
+ * - Motion: animateStats enables a count-up animation for numeric stats on first viewport visibility.
29
+ * - Accessibility: rendered as a semantic <section> with aria-label.
30
+ */
31
+ interface AboutProps {
32
+ /** Main heading text displayed in the section header. @defaultValue "Your Success Is Our Mission" */
33
+ aboutHeadingText?: string;
34
+ /** Optional subheading text displayed above the heading. */
35
+ aboutSubheadingText?: string;
36
+ /** Primary paragraph/content body. @defaultValue a short marketing blurb */
37
+ aboutContentText?: string;
38
+ /** Optional list of statistics to render below the content. @defaultValue defaultStatsData */
39
+ aboutStats?: AboutStatData[];
40
+ /** Optional top-level class to override the section root */
41
+ className?: string;
42
+ /** Slot-style overrides */
43
+ section?: {
44
+ className?: string;
45
+ };
46
+ container?: {
47
+ className?: string;
48
+ };
49
+ inner?: {
50
+ className?: string;
51
+ };
52
+ contentContainer?: {
53
+ className?: string;
54
+ };
55
+ contentStack?: {
56
+ className?: string;
57
+ };
58
+ subheading?: {
59
+ className?: string;
60
+ };
61
+ heading?: {
62
+ className?: string;
63
+ };
64
+ content?: {
65
+ className?: string;
66
+ };
67
+ statsSection?: {
68
+ className?: string;
69
+ };
70
+ statsGrid?: {
71
+ className?: string;
72
+ };
73
+ statItem?: {
74
+ className?: string;
75
+ };
76
+ statNumber?: {
77
+ className?: string;
78
+ };
79
+ statLabel?: {
80
+ className?: string;
81
+ };
82
+ /** Controls text alignment at various breakpoints. @defaultValue "center" */
83
+ aboutTextAlign?: "left" | "center" | "right";
84
+ /** Whether to render the stats block at the bottom. @defaultValue true */
85
+ showStats?: boolean;
86
+ /** Enable count-up animation for numeric stats when they enter the viewport. @defaultValue false */
87
+ animateStats?: boolean;
88
+ /** ARIA label for the section. @defaultValue "About section" */
89
+ ariaLabel?: string;
90
+ }
91
+ export declare function About({ aboutHeadingText, aboutSubheadingText, aboutContentText, aboutStats, className, section, container, inner, contentContainer, contentStack, subheading, heading, content, statsSection, statsGrid, statItem, statNumber, statLabel, aboutTextAlign, showStats, animateStats, ariaLabel, }: AboutProps): React.JSX.Element;
92
+ export {};
93
+ //# sourceMappingURL=About.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"About.d.ts","sourceRoot":"","sources":["../../src/components/About.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,UAAU,UAAU;IAClB,qGAAqG;IACrG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8FAA8F;IAC9F,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;IAE7B,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2BAA2B;IAC3B,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,KAAK,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,YAAY,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,UAAU,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,YAAY,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,QAAQ,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,UAAU,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEnC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC7C,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oGAAoG;IACpG,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6FD,wBAAgB,KAAK,CAAC,EACpB,gBAAgD,EAChD,mBAAmB,EACnB,gBAAyJ,EACzJ,UAA6B,EAC7B,SAAS,EACT,OAAyD,EACzD,SAAmD,EACnD,KAA6C,EAC7C,gBAAqD,EACrD,YAAmD,EACnD,UAGC,EACD,OAEC,EACD,OAGC,EACD,YAGC,EACD,SAEC,EACD,QAA4D,EAC5D,UAEC,EACD,SAGC,EACD,cAAyB,EACzB,SAAgB,EAChB,YAAoB,EACpB,SAA2B,GAC5B,EAAE,UAAU,qBAoFZ"}
@@ -0,0 +1,129 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { cn } from "@nextworks/blocks-core";
5
+ /**
6
+ * Default statistics displayed if no aboutStats are provided.
7
+ */
8
+ const defaultStatsData = [
9
+ { value: "50", label: "Successful Projects", suffix: "+" },
10
+ { value: "5", label: "Years Experience", suffix: "+" },
11
+ { value: "100", label: "Happy Clients", suffix: "%" },
12
+ { value: "24", label: "Support Hours", suffix: "/7" },
13
+ ];
14
+ /**
15
+ * About section with a heading, optional subheading/content, and an optional
16
+ * stats block.
17
+ *
18
+ * @remarks
19
+ * - Styling: exposes slot-style className overrides. Consumer classes are
20
+ * merged after defaults via cn().
21
+ * - Layout: control text alignment with aboutTextAlign.
22
+ * - Accessibility: rendered as a semantic <section> with aria-label.
23
+ *
24
+ * @example
25
+ * <About
26
+ * aboutHeadingText="Your Success Is Our Mission"
27
+ * aboutContentText="We build digital products that convert."
28
+ * aboutStats={[{ value: "50", label: "Successful Projects", suffix: "+" }]}
29
+ * />
30
+ */
31
+ /**
32
+ * About section with a heading, optional subheading/content, and an optional
33
+ * stats block.
34
+ *
35
+ * @remarks
36
+ * - Styling: exposes slot-style className overrides. Consumer classes are
37
+ * merged after defaults via cn().
38
+ * - Layout: control text alignment with aboutTextAlign.
39
+ * - Accessibility: rendered as a semantic <section> with aria-label.
40
+ *
41
+ * @example
42
+ * <About
43
+ * aboutHeadingText="Your Success Is Our Mission"
44
+ * aboutContentText="We build digital products that convert."
45
+ * aboutStats={[{ value: "50", label: "Successful Projects", suffix: "+" }]}
46
+ * />
47
+ */
48
+ /**
49
+ * Animated count-up for integers using requestAnimationFrame.
50
+ * Eases out and runs once when shouldStart becomes true.
51
+ */
52
+ function AnimatedCount({ end, shouldStart, duration = 1500, }) {
53
+ const [value, setValue] = React.useState(0);
54
+ const [done, setDone] = React.useState(false);
55
+ const prefersReduced = React.useMemo(() => {
56
+ if (typeof window === "undefined")
57
+ return false;
58
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
59
+ }, []);
60
+ React.useEffect(() => {
61
+ if (prefersReduced)
62
+ return; // show end value immediately below
63
+ if (!shouldStart || done)
64
+ return;
65
+ let raf;
66
+ const startTs = performance.now();
67
+ const tick = (now) => {
68
+ const progress = Math.min((now - startTs) / duration, 1);
69
+ const eased = 1 - Math.pow(1 - progress, 3);
70
+ const next = Math.round(eased * end);
71
+ setValue(next);
72
+ if (progress < 1) {
73
+ raf = requestAnimationFrame(tick);
74
+ }
75
+ else {
76
+ setDone(true);
77
+ }
78
+ };
79
+ raf = requestAnimationFrame(tick);
80
+ return () => cancelAnimationFrame(raf);
81
+ }, [shouldStart, end, duration, done, prefersReduced]);
82
+ if (prefersReduced)
83
+ return _jsx(_Fragment, { children: end });
84
+ return _jsx(_Fragment, { children: value });
85
+ }
86
+ export function About({ aboutHeadingText = "Your Success Is Our Mission", aboutSubheadingText, aboutContentText = "With 50+ successful projects and 5 years of experience, we specialize in creating digital solutions that drive real business growth.", aboutStats = defaultStatsData, className, section = { className: "py-20 bg-muted text-foreground" }, container = { className: "max-w-7xl mx-auto px-6" }, inner = { className: "flex flex-col gap-12" }, contentContainer = { className: "max-w-4xl mx-auto" }, contentStack = { className: "flex flex-col gap-6" }, subheading = {
87
+ className: "text-sm font-semibold font-poppins text-primary uppercase tracking-wider",
88
+ }, heading = {
89
+ className: "text-3xl font-bold font-poppins text-foreground leading-tight",
90
+ }, content = {
91
+ className: "text-lg font-inter text-foreground leading-relaxed opacity-90 max-w-3xl",
92
+ }, statsSection = {
93
+ className: "bg-card p-8 rounded-xl shadow-lg mx-auto max-w-5xl w-full border border-border",
94
+ }, statsGrid = {
95
+ className: "grid grid-cols-2 md:grid-cols-4 gap-8 justify-items-center",
96
+ }, statItem = { className: "flex flex-col items-center gap-2" }, statNumber = {
97
+ className: "text-4xl font-bold font-poppins text-foreground leading-none",
98
+ }, statLabel = {
99
+ className: "text-sm font-medium font-inter text-foreground text-center opacity-80",
100
+ }, aboutTextAlign = "center", showStats = true, animateStats = false, ariaLabel = "About section", }) {
101
+ const textAlignClasses = {
102
+ left: "text-left",
103
+ right: "text-right",
104
+ center: "text-center",
105
+ };
106
+ const statsSectionRef = React.useRef(null);
107
+ const [shouldStartCount, setShouldStartCount] = React.useState(false);
108
+ React.useEffect(() => {
109
+ if (!animateStats)
110
+ return;
111
+ if (!statsSectionRef.current)
112
+ return;
113
+ const observer = new IntersectionObserver((entries) => {
114
+ for (const entry of entries) {
115
+ if (entry.isIntersecting) {
116
+ setShouldStartCount(true);
117
+ observer.disconnect();
118
+ break;
119
+ }
120
+ }
121
+ }, { threshold: 0.3 });
122
+ observer.observe(statsSectionRef.current);
123
+ return () => observer.disconnect();
124
+ }, [animateStats]);
125
+ return (_jsx("section", { className: cn(section.className, className), "aria-label": ariaLabel, children: _jsx("div", { className: cn(container.className), children: _jsxs("div", { className: cn(inner.className), children: [_jsx("div", { className: cn(contentContainer.className, textAlignClasses[aboutTextAlign]), children: _jsxs("div", { className: cn(contentStack.className), children: [aboutSubheadingText && (_jsx("div", { className: cn(subheading.className), children: aboutSubheadingText })), _jsx("h2", { className: cn(heading.className), children: aboutHeadingText }), _jsx("p", { className: cn(content.className), children: aboutContentText })] }) }), showStats && (aboutStats === null || aboutStats === void 0 ? void 0 : aboutStats.length) ? (_jsx("div", { ref: statsSectionRef, className: cn(statsSection.className), children: _jsx("div", { className: cn(statsGrid.className), children: aboutStats.map((s, i) => {
126
+ var _a;
127
+ return (_jsxs("div", { className: cn(statItem.className), children: [_jsxs("div", { className: cn(statNumber.className), children: [animateStats && Number.isFinite(Number(s.value)) ? (_jsx(AnimatedCount, { end: Number(s.value), shouldStart: shouldStartCount })) : (s.value), s.suffix] }), _jsx("div", { className: cn(statLabel.className), children: s.label })] }, `${(_a = s.label) !== null && _a !== void 0 ? _a : "stat"}-${i}`));
128
+ }) }) })) : null] }) }) }));
129
+ }
@@ -0,0 +1,153 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { cn } from "@nextworks/blocks-core";
4
+ /**
5
+ * Default statistics displayed if no aboutStats are provided.
6
+ */
7
+ const defaultStatsData = [
8
+ { value: "50", label: "Successful Projects", suffix: "+" },
9
+ { value: "5", label: "Years Experience", suffix: "+" },
10
+ { value: "100", label: "Happy Clients", suffix: "%" },
11
+ { value: "24", label: "Support Hours", suffix: "/7" },
12
+ ];
13
+ /**
14
+ * About section with a heading, optional subheading/content, and an optional
15
+ * stats block.
16
+ *
17
+ * @remarks
18
+ * - Styling: exposes slot-style className overrides. Consumer classes are
19
+ * merged after defaults via cn().
20
+ * - Layout: control text alignment with aboutTextAlign.
21
+ * - Accessibility: rendered as a semantic <section> with aria-label.
22
+ *
23
+ * @example
24
+ * <About
25
+ * aboutHeadingText="Your Success Is Our Mission"
26
+ * aboutContentText="We build digital products that convert."
27
+ * aboutStats={[{ value: "50", label: "Successful Projects", suffix: "+" }]}
28
+ * />
29
+ */
30
+ /**
31
+ * About section with a heading, optional subheading/content, and an optional
32
+ * stats block.
33
+ *
34
+ * @remarks
35
+ * - Styling: exposes slot-style className overrides. Consumer classes are
36
+ * merged after defaults via cn().
37
+ * - Layout: control text alignment with aboutTextAlign.
38
+ * - Accessibility: rendered as a semantic <section> with aria-label.
39
+ *
40
+ * @example
41
+ * <About
42
+ * aboutHeadingText="Your Success Is Our Mission"
43
+ * aboutContentText="We build digital products that convert."
44
+ * aboutStats={[{ value: "50", label: "Successful Projects", suffix: "+" }]}
45
+ * />
46
+ */
47
+ /**
48
+ * Animated count-up for integers using requestAnimationFrame.
49
+ * Eases out and runs once when shouldStart becomes true.
50
+ */
51
+ function AnimatedCount({ end, shouldStart, duration = 1500, }) {
52
+ const [value, setValue] = React.useState(0);
53
+ const [done, setDone] = React.useState(false);
54
+ const prefersReduced = React.useMemo(() => {
55
+ if (typeof window === "undefined")
56
+ return false;
57
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
58
+ }, []);
59
+ React.useEffect(() => {
60
+ if (prefersReduced)
61
+ return; // show end value immediately below
62
+ if (!shouldStart || done)
63
+ return;
64
+ let raf;
65
+ const startTs = performance.now();
66
+ const tick = (now) => {
67
+ const progress = Math.min((now - startTs) / duration, 1);
68
+ const eased = 1 - Math.pow(1 - progress, 3);
69
+ const next = Math.round(eased * end);
70
+ setValue(next);
71
+ if (progress < 1) {
72
+ raf = requestAnimationFrame(tick);
73
+ }
74
+ else {
75
+ setDone(true);
76
+ }
77
+ };
78
+ raf = requestAnimationFrame(tick);
79
+ return () => cancelAnimationFrame(raf);
80
+ }, [shouldStart, end, duration, done, prefersReduced]);
81
+ if (prefersReduced)
82
+ return <>{end}</>;
83
+ return <>{value}</>;
84
+ }
85
+ export function About({ aboutHeadingText = "Your Success Is Our Mission", aboutSubheadingText, aboutContentText = "With 50+ successful projects and 5 years of experience, we specialize in creating digital solutions that drive real business growth.", aboutStats = defaultStatsData, className, section = { className: "py-20 bg-muted text-foreground" }, container = { className: "max-w-7xl mx-auto px-6" }, inner = { className: "flex flex-col gap-12" }, contentContainer = { className: "max-w-4xl mx-auto" }, contentStack = { className: "flex flex-col gap-6" }, subheading = {
86
+ className: "text-sm font-semibold font-poppins text-primary uppercase tracking-wider",
87
+ }, heading = {
88
+ className: "text-3xl font-bold font-poppins text-foreground leading-tight",
89
+ }, content = {
90
+ className: "text-lg font-inter text-foreground leading-relaxed opacity-90 max-w-3xl",
91
+ }, statsSection = {
92
+ className: "bg-card p-8 rounded-xl shadow-lg mx-auto max-w-5xl w-full border border-border",
93
+ }, statsGrid = {
94
+ className: "grid grid-cols-2 md:grid-cols-4 gap-8 justify-items-center",
95
+ }, statItem = { className: "flex flex-col items-center gap-2" }, statNumber = {
96
+ className: "text-4xl font-bold font-poppins text-foreground leading-none",
97
+ }, statLabel = {
98
+ className: "text-sm font-medium font-inter text-foreground text-center opacity-80",
99
+ }, aboutTextAlign = "center", showStats = true, animateStats = false, ariaLabel = "About section", }) {
100
+ const textAlignClasses = {
101
+ left: "text-left",
102
+ right: "text-right",
103
+ center: "text-center",
104
+ };
105
+ const statsSectionRef = React.useRef(null);
106
+ const [shouldStartCount, setShouldStartCount] = React.useState(false);
107
+ React.useEffect(() => {
108
+ if (!animateStats)
109
+ return;
110
+ if (!statsSectionRef.current)
111
+ return;
112
+ const observer = new IntersectionObserver((entries) => {
113
+ for (const entry of entries) {
114
+ if (entry.isIntersecting) {
115
+ setShouldStartCount(true);
116
+ observer.disconnect();
117
+ break;
118
+ }
119
+ }
120
+ }, { threshold: 0.3 });
121
+ observer.observe(statsSectionRef.current);
122
+ return () => observer.disconnect();
123
+ }, [animateStats]);
124
+ return (<section className={cn(section.className, className)} aria-label={ariaLabel}>
125
+ <div className={cn(container.className)}>
126
+ <div className={cn(inner.className)}>
127
+ <div className={cn(contentContainer.className, textAlignClasses[aboutTextAlign])}>
128
+ <div className={cn(contentStack.className)}>
129
+ {aboutSubheadingText && (<div className={cn(subheading.className)}>
130
+ {aboutSubheadingText}
131
+ </div>)}
132
+ <h2 className={cn(heading.className)}>{aboutHeadingText}</h2>
133
+ <p className={cn(content.className)}>{aboutContentText}</p>
134
+ </div>
135
+ </div>
136
+ {showStats && (aboutStats === null || aboutStats === void 0 ? void 0 : aboutStats.length) ? (<div ref={statsSectionRef} className={cn(statsSection.className)}>
137
+ <div className={cn(statsGrid.className)}>
138
+ {aboutStats.map((s, i) => {
139
+ var _a;
140
+ return (<div key={`${(_a = s.label) !== null && _a !== void 0 ? _a : "stat"}-${i}`} className={cn(statItem.className)}>
141
+ <div className={cn(statNumber.className)}>
142
+ {animateStats && Number.isFinite(Number(s.value)) ? (<AnimatedCount end={Number(s.value)} shouldStart={shouldStartCount}/>) : (s.value)}
143
+ {s.suffix}
144
+ </div>
145
+ <div className={cn(statLabel.className)}>{s.label}</div>
146
+ </div>);
147
+ })}
148
+ </div>
149
+ </div>) : null}
150
+ </div>
151
+ </div>
152
+ </section>);
153
+ }
@@ -0,0 +1,118 @@
1
+ import React from "react";
2
+ /**
3
+ * Props for the CTA section component.
4
+ *
5
+ * @remarks
6
+ * Uses a slot-style API for Tailwind className overrides. Each slot's
7
+ * className is merged with component defaults via cn().
8
+ *
9
+ * Prefer customizing spacing via headingText.className and other slot
10
+ * classNames rather than legacy spacing props.
11
+ *
12
+ * @public
13
+ */
14
+ interface CTAProps {
15
+ /**
16
+ * Optional id for the section.
17
+ * @defaultValue "cta"
18
+ */
19
+ id?: string;
20
+ /** Optional top-level class to override the section root */
21
+ className?: string;
22
+ /** Styling configuration objects (slot-style pattern like Navbar) */
23
+ section?: {
24
+ className?: string;
25
+ };
26
+ container?: {
27
+ className?: string;
28
+ };
29
+ contentWrapper?: {
30
+ className?: string;
31
+ };
32
+ headingText?: {
33
+ text?: string;
34
+ className?: string;
35
+ };
36
+ subheadingText?: {
37
+ text?: string;
38
+ className?: string;
39
+ };
40
+ descriptionText?: {
41
+ text?: string;
42
+ className?: string;
43
+ };
44
+ actionsWrapper?: {
45
+ className?: string;
46
+ };
47
+ /**
48
+ * Primary CTA config or null to hide it (mirrors Navbar ctaButton pattern)
49
+ * Example: { label: "Get Started", href: "#contact" }
50
+ */
51
+ ctaButton?: {
52
+ label: string;
53
+ href: string;
54
+ } | null;
55
+ /** Primary CTA button styles */
56
+ ctaButtonStyle?: {
57
+ unstyled?: boolean;
58
+ style?: React.CSSProperties;
59
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
60
+ size?: "default" | "sm" | "lg" | "icon";
61
+ className?: string;
62
+ };
63
+ /** Optional wrapper slot for the primary CTA */
64
+ ctaButtonWrapper?: {
65
+ className?: string;
66
+ };
67
+ /** Optional secondary action */
68
+ secondaryButton?: {
69
+ label: string;
70
+ href: string;
71
+ } | null;
72
+ /** Secondary CTA button styles */
73
+ secondaryButtonStyle?: {
74
+ unstyled?: boolean;
75
+ style?: React.CSSProperties;
76
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
77
+ size?: "default" | "sm" | "lg" | "icon";
78
+ className?: string;
79
+ };
80
+ /** Optional wrapper slot for the secondary CTA */
81
+ secondaryButtonWrapper?: {
82
+ className?: string;
83
+ };
84
+ /**
85
+ * Legacy spacing hook applied to the heading.
86
+ * @deprecated Prefer margin utilities via headingText.className
87
+ */
88
+ spacing?: {
89
+ topMargin?: string;
90
+ };
91
+ /** Accessibility */
92
+ ariaLabel?: string;
93
+ role?: string;
94
+ }
95
+ /**
96
+ * Call-to-Action section with a heading, optional subheading/description,
97
+ * and up to two actions (primary and secondary).
98
+ *
99
+ * @remarks
100
+ * - Styling: exposes slot-style className overrides (section, container,
101
+ * contentWrapper, headingText, subheadingText, descriptionText, actionsWrapper,
102
+ * ctaButtonStyle, secondaryButtonStyle). Consumer classes are merged after
103
+ * defaults via cn().
104
+ * - Accessibility: rendered as a semantic <section> with aria-label. The role
105
+ * defaults to "region" but can be customized with the role prop.
106
+ * - Motion: subtle hover lift effects on buttons are included by default.
107
+ *
108
+ * @example
109
+ * <CTA
110
+ * headingText={{ text: "Join The Launch Today!" }}
111
+ * descriptionText={{ text: "Start your free trial in minutes." }}
112
+ * ctaButton={{ label: "Sign Up", href: "#contact" }}
113
+ * secondaryButton={{ label: "Learn More", href: "#features" }}
114
+ * />
115
+ */
116
+ export declare function CTA({ id, className, section, container, contentWrapper, headingText, subheadingText, descriptionText, actionsWrapper, ctaButton, ctaButtonStyle, ctaButtonWrapper, secondaryButton, secondaryButtonStyle, secondaryButtonWrapper, spacing, ariaLabel, role, }: CTAProps): React.JSX.Element;
117
+ export {};
118
+ //# sourceMappingURL=CTA.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CTA.d.ts","sourceRoot":"","sources":["../../src/components/CTA.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { Button } from "@nextworks/blocks-core";
5
+ import { cn } from "@nextworks/blocks-core";
6
+ /**
7
+ * Call-to-Action section with a heading, optional subheading/description,
8
+ * and up to two actions (primary and secondary).
9
+ *
10
+ * @remarks
11
+ * - Styling: exposes slot-style className overrides (section, container,
12
+ * contentWrapper, headingText, subheadingText, descriptionText, actionsWrapper,
13
+ * ctaButtonStyle, secondaryButtonStyle). Consumer classes are merged after
14
+ * defaults via cn().
15
+ * - Accessibility: rendered as a semantic <section> with aria-label. The role
16
+ * defaults to "region" but can be customized with the role prop.
17
+ * - Motion: subtle hover lift effects on buttons are included by default.
18
+ *
19
+ * @example
20
+ * <CTA
21
+ * headingText={{ text: "Join The Launch Today!" }}
22
+ * descriptionText={{ text: "Start your free trial in minutes." }}
23
+ * ctaButton={{ label: "Sign Up", href: "#contact" }}
24
+ * secondaryButton={{ label: "Learn More", href: "#features" }}
25
+ * />
26
+ */
27
+ export function CTA({ id = "cta", className, section = {
28
+ className: "bg-background text-foreground",
29
+ }, container = {
30
+ className: "mx-auto flex min-h-[42vh] w-full max-w-6xl flex-col items-center justify-center overflow-hidden px-4 pb-7",
31
+ }, contentWrapper = {
32
+ className: "flex w-full flex-col items-center text-center",
33
+ }, headingText = {
34
+ text: "Join The Launch Today!",
35
+ className: "text-3xl font-bold leading-[1.1] text-primary sm:text-4xl md:text-5xl text-[var(--heading-fg)]",
36
+ }, subheadingText = {
37
+ text: "",
38
+ className: "mt-2 text-lg font-medium text-muted-foreground sm:text-xl text-[var(--subheading-fg)]",
39
+ }, descriptionText = {
40
+ text: "",
41
+ className: "mt-3 max-w-2xl text-base text-foreground/80 sm:text-lg text-[var(--description-fg)]",
42
+ }, actionsWrapper = {
43
+ className: "mt-6 flex flex-col items-center gap-3 sm:flex-row",
44
+ }, ctaButton = { label: "Sign Up Now", href: "#contact" }, ctaButtonStyle = {
45
+ variant: "default",
46
+ size: "default",
47
+ className: "shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:shadow-xl border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
48
+ }, ctaButtonWrapper = { className: "" }, secondaryButton = null, secondaryButtonStyle = {
49
+ variant: "outline",
50
+ size: "default",
51
+ className: "transition-transform duration-200 hover:-translate-y-0.5 border-[var(--btn-border)] focus-visible:ring-[var(--btn-ring)]",
52
+ }, secondaryButtonWrapper = { className: "" }, spacing = { topMargin: "mt-0 sm:mt-12" },
53
+ // spacing = { topMargin: "mt-[17vh]" },
54
+ ariaLabel = "Call to action section", role = "region", }) {
55
+ // default class for actions wrapper (keeps a gap and responsive row layout)
56
+ const actionsWrapperDefault = "mt-6 flex flex-col items-center gap-3 sm:flex-row";
57
+ return (_jsx("section", { id: id, role: role, "aria-label": ariaLabel, className: cn("w-full", section.className, className), children: _jsx("div", { className: cn(container.className), children: _jsxs("div", { className: cn(contentWrapper.className), children: [(headingText === null || headingText === void 0 ? void 0 : headingText.text) ? (_jsx("h2", { className: cn("text-center", spacing === null || spacing === void 0 ? void 0 : spacing.topMargin, headingText.className), children: headingText.text })) : null, (subheadingText === null || subheadingText === void 0 ? void 0 : subheadingText.text) ? (_jsx("p", { className: cn(subheadingText.className), children: subheadingText.text })) : null, (descriptionText === null || descriptionText === void 0 ? void 0 : descriptionText.text) ? (_jsx("p", { className: cn(descriptionText.className), children: descriptionText.text })) : null, _jsxs("div", { className: cn(actionsWrapperDefault, actionsWrapper.className), children: [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 }) })), secondaryButton && (_jsx(Button, { asChild: true, unstyled: secondaryButtonStyle.unstyled, variant: secondaryButtonStyle.variant, size: secondaryButtonStyle.size, className: cn(secondaryButtonWrapper.className, secondaryButtonStyle.className), style: secondaryButtonStyle.style, children: _jsx(Link, { href: secondaryButton.href, "aria-label": secondaryButton.label, children: secondaryButton.label }) }))] })] }) }) }));
58
+ }