@web-my-money/blocks 1.0.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/dist/app.d.mts +454 -0
- package/dist/app.d.ts +454 -0
- package/dist/app.js +1293 -0
- package/dist/app.mjs +53 -0
- package/dist/chunk-L6OIX4DG.mjs +70 -0
- package/dist/chunk-PJO7WJ4G.mjs +10 -0
- package/dist/chunk-UM2RDUPI.mjs +1183 -0
- package/dist/chunk-X6SF4TT2.mjs +2205 -0
- package/dist/funnel.d.mts +80 -0
- package/dist/funnel.d.ts +80 -0
- package/dist/funnel.js +281 -0
- package/dist/funnel.mjs +235 -0
- package/dist/fx.d.mts +314 -0
- package/dist/fx.d.ts +314 -0
- package/dist/fx.js +1142 -0
- package/dist/fx.mjs +1084 -0
- package/dist/index-D5sr_PKq.d.mts +29 -0
- package/dist/index-D5sr_PKq.d.ts +29 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3525 -0
- package/dist/index.mjs +121 -0
- package/dist/next.d.mts +32 -0
- package/dist/next.d.ts +32 -0
- package/dist/next.js +65 -0
- package/dist/next.mjs +28 -0
- package/dist/site.d.mts +532 -0
- package/dist/site.d.ts +532 -0
- package/dist/site.js +2338 -0
- package/dist/site.mjs +77 -0
- package/package.json +93 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface FormStep {
|
|
4
|
+
label: string;
|
|
5
|
+
/** step body */
|
|
6
|
+
content: React.ReactNode;
|
|
7
|
+
/** gate the Next button (e.g. wire to your RHF isValid for this step) */
|
|
8
|
+
canProceed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface MultiStepFormProps {
|
|
11
|
+
steps: FormStep[];
|
|
12
|
+
onComplete?: () => void;
|
|
13
|
+
/** labels */
|
|
14
|
+
nextLabel?: string;
|
|
15
|
+
backLabel?: string;
|
|
16
|
+
submitLabel?: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Headless-ish multi-step form shell (dep-free). Manages step index + progress; you own
|
|
21
|
+
* the fields and validation (wire `canProceed` to your react-hook-form per-step isValid).
|
|
22
|
+
*/
|
|
23
|
+
declare function MultiStepForm({ steps, onComplete, nextLabel, backLabel, submitLabel, className, }: MultiStepFormProps): React.JSX.Element;
|
|
24
|
+
|
|
25
|
+
interface OfferItem {
|
|
26
|
+
label: string;
|
|
27
|
+
value: string;
|
|
28
|
+
/** show as bonus (accent) */
|
|
29
|
+
bonus?: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface OfferStackProps {
|
|
32
|
+
items: OfferItem[];
|
|
33
|
+
totalLabel?: string;
|
|
34
|
+
totalValue: string;
|
|
35
|
+
priceLabel?: string;
|
|
36
|
+
price: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Value stack — itemized deliverables with values, total, and today's price (funnel offer). */
|
|
40
|
+
declare function OfferStack({ items, totalLabel, totalValue, priceLabel, price, className, }: OfferStackProps): React.JSX.Element;
|
|
41
|
+
|
|
42
|
+
interface GuaranteeBadgeProps {
|
|
43
|
+
title?: string;
|
|
44
|
+
text: string;
|
|
45
|
+
icon?: React.ReactNode;
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Risk-reversal / money-back guarantee badge for offers + funnels. */
|
|
49
|
+
declare function GuaranteeBadge({ title, text, icon, className }: GuaranteeBadgeProps): React.JSX.Element;
|
|
50
|
+
|
|
51
|
+
interface CountdownCTAProps {
|
|
52
|
+
/** deadline as an ISO string or ms timestamp */
|
|
53
|
+
deadline: string | number;
|
|
54
|
+
heading: string;
|
|
55
|
+
subheading?: string;
|
|
56
|
+
ctaLabel: string;
|
|
57
|
+
onCta?: () => void;
|
|
58
|
+
ctaHref?: string;
|
|
59
|
+
expiredLabel?: string;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Urgency block — live countdown + CTA. Great for funnel offers / cart deadlines. */
|
|
63
|
+
declare function CountdownCTA({ deadline, heading, subheading, ctaLabel, onCta, ctaHref, expiredLabel, className, }: CountdownCTAProps): React.JSX.Element;
|
|
64
|
+
|
|
65
|
+
interface FunnelStep {
|
|
66
|
+
label: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
icon?: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
interface FunnelStepsProps {
|
|
71
|
+
steps: FunnelStep[];
|
|
72
|
+
/** highlight up to this index (inclusive) as done/active */
|
|
73
|
+
activeIndex?: number;
|
|
74
|
+
orientation?: "horizontal" | "vertical";
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
/** Visual conversion path — WMM Find → Attract → Convert → Retain style. */
|
|
78
|
+
declare function FunnelSteps({ steps, activeIndex, orientation, className }: FunnelStepsProps): React.JSX.Element;
|
|
79
|
+
|
|
80
|
+
export { CountdownCTA, type CountdownCTAProps, type FormStep, type FunnelStep, FunnelSteps, type FunnelStepsProps, GuaranteeBadge, type GuaranteeBadgeProps, MultiStepForm, type MultiStepFormProps, type OfferItem, OfferStack, type OfferStackProps };
|
package/dist/funnel.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface FormStep {
|
|
4
|
+
label: string;
|
|
5
|
+
/** step body */
|
|
6
|
+
content: React.ReactNode;
|
|
7
|
+
/** gate the Next button (e.g. wire to your RHF isValid for this step) */
|
|
8
|
+
canProceed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface MultiStepFormProps {
|
|
11
|
+
steps: FormStep[];
|
|
12
|
+
onComplete?: () => void;
|
|
13
|
+
/** labels */
|
|
14
|
+
nextLabel?: string;
|
|
15
|
+
backLabel?: string;
|
|
16
|
+
submitLabel?: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Headless-ish multi-step form shell (dep-free). Manages step index + progress; you own
|
|
21
|
+
* the fields and validation (wire `canProceed` to your react-hook-form per-step isValid).
|
|
22
|
+
*/
|
|
23
|
+
declare function MultiStepForm({ steps, onComplete, nextLabel, backLabel, submitLabel, className, }: MultiStepFormProps): React.JSX.Element;
|
|
24
|
+
|
|
25
|
+
interface OfferItem {
|
|
26
|
+
label: string;
|
|
27
|
+
value: string;
|
|
28
|
+
/** show as bonus (accent) */
|
|
29
|
+
bonus?: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface OfferStackProps {
|
|
32
|
+
items: OfferItem[];
|
|
33
|
+
totalLabel?: string;
|
|
34
|
+
totalValue: string;
|
|
35
|
+
priceLabel?: string;
|
|
36
|
+
price: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Value stack — itemized deliverables with values, total, and today's price (funnel offer). */
|
|
40
|
+
declare function OfferStack({ items, totalLabel, totalValue, priceLabel, price, className, }: OfferStackProps): React.JSX.Element;
|
|
41
|
+
|
|
42
|
+
interface GuaranteeBadgeProps {
|
|
43
|
+
title?: string;
|
|
44
|
+
text: string;
|
|
45
|
+
icon?: React.ReactNode;
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Risk-reversal / money-back guarantee badge for offers + funnels. */
|
|
49
|
+
declare function GuaranteeBadge({ title, text, icon, className }: GuaranteeBadgeProps): React.JSX.Element;
|
|
50
|
+
|
|
51
|
+
interface CountdownCTAProps {
|
|
52
|
+
/** deadline as an ISO string or ms timestamp */
|
|
53
|
+
deadline: string | number;
|
|
54
|
+
heading: string;
|
|
55
|
+
subheading?: string;
|
|
56
|
+
ctaLabel: string;
|
|
57
|
+
onCta?: () => void;
|
|
58
|
+
ctaHref?: string;
|
|
59
|
+
expiredLabel?: string;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Urgency block — live countdown + CTA. Great for funnel offers / cart deadlines. */
|
|
63
|
+
declare function CountdownCTA({ deadline, heading, subheading, ctaLabel, onCta, ctaHref, expiredLabel, className, }: CountdownCTAProps): React.JSX.Element;
|
|
64
|
+
|
|
65
|
+
interface FunnelStep {
|
|
66
|
+
label: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
icon?: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
interface FunnelStepsProps {
|
|
71
|
+
steps: FunnelStep[];
|
|
72
|
+
/** highlight up to this index (inclusive) as done/active */
|
|
73
|
+
activeIndex?: number;
|
|
74
|
+
orientation?: "horizontal" | "vertical";
|
|
75
|
+
className?: string;
|
|
76
|
+
}
|
|
77
|
+
/** Visual conversion path — WMM Find → Attract → Convert → Retain style. */
|
|
78
|
+
declare function FunnelSteps({ steps, activeIndex, orientation, className }: FunnelStepsProps): React.JSX.Element;
|
|
79
|
+
|
|
80
|
+
export { CountdownCTA, type CountdownCTAProps, type FormStep, type FunnelStep, FunnelSteps, type FunnelStepsProps, GuaranteeBadge, type GuaranteeBadgeProps, MultiStepForm, type MultiStepFormProps, type OfferItem, OfferStack, type OfferStackProps };
|
package/dist/funnel.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/funnel.ts
|
|
31
|
+
var funnel_exports = {};
|
|
32
|
+
__export(funnel_exports, {
|
|
33
|
+
CountdownCTA: () => CountdownCTA,
|
|
34
|
+
FunnelSteps: () => FunnelSteps,
|
|
35
|
+
GuaranteeBadge: () => GuaranteeBadge,
|
|
36
|
+
MultiStepForm: () => MultiStepForm,
|
|
37
|
+
OfferStack: () => OfferStack
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(funnel_exports);
|
|
40
|
+
|
|
41
|
+
// src/multi-step-form/index.tsx
|
|
42
|
+
var React = __toESM(require("react"));
|
|
43
|
+
var import_lucide_react = require("lucide-react");
|
|
44
|
+
|
|
45
|
+
// src/utils.ts
|
|
46
|
+
var import_clsx = require("clsx");
|
|
47
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
48
|
+
function cn(...inputs) {
|
|
49
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/multi-step-form/index.tsx
|
|
53
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
54
|
+
function MultiStepForm({
|
|
55
|
+
steps,
|
|
56
|
+
onComplete,
|
|
57
|
+
nextLabel = "Continue",
|
|
58
|
+
backLabel = "Back",
|
|
59
|
+
submitLabel = "Submit",
|
|
60
|
+
className
|
|
61
|
+
}) {
|
|
62
|
+
const [i, setI] = React.useState(0);
|
|
63
|
+
const last = i === steps.length - 1;
|
|
64
|
+
const step = steps[i];
|
|
65
|
+
const pct = (i + 1) / steps.length * 100;
|
|
66
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("flex flex-col gap-6", className), children: [
|
|
67
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2", children: steps.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(React.Fragment, { children: [
|
|
68
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
69
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
70
|
+
"span",
|
|
71
|
+
{
|
|
72
|
+
className: cn(
|
|
73
|
+
"flex size-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold transition-colors",
|
|
74
|
+
idx < i && "bg-primary text-primary-foreground",
|
|
75
|
+
idx === i && "bg-primary text-primary-foreground ring-4 ring-primary/20",
|
|
76
|
+
idx > i && "bg-muted text-muted-foreground"
|
|
77
|
+
),
|
|
78
|
+
children: idx < i ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: "size-3.5" }) : idx + 1
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cn("hidden text-sm sm:inline", idx === i ? "font-medium text-foreground" : "text-muted-foreground"), children: s.label })
|
|
82
|
+
] }),
|
|
83
|
+
idx < steps.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-px flex-1 bg-border" })
|
|
84
|
+
] }, idx)) }),
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-1 w-full overflow-hidden rounded-full bg-muted sm:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-full rounded-full bg-primary transition-[width] duration-300", style: { width: `${pct}%` } }) }),
|
|
86
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-24", children: step.content }),
|
|
87
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
|
|
88
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
89
|
+
"button",
|
|
90
|
+
{
|
|
91
|
+
type: "button",
|
|
92
|
+
onClick: () => setI((x) => Math.max(0, x - 1)),
|
|
93
|
+
disabled: i === 0,
|
|
94
|
+
className: "rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted disabled:opacity-40 disabled:pointer-events-none",
|
|
95
|
+
children: backLabel
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
onClick: () => last ? onComplete?.() : setI((x) => Math.min(steps.length - 1, x + 1)),
|
|
103
|
+
disabled: step.canProceed === false,
|
|
104
|
+
className: "rounded-lg bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 disabled:pointer-events-none",
|
|
105
|
+
children: last ? submitLabel : nextLabel
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
] })
|
|
109
|
+
] });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/offer-stack/index.tsx
|
|
113
|
+
var import_lucide_react2 = require("lucide-react");
|
|
114
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
115
|
+
function OfferStack({
|
|
116
|
+
items,
|
|
117
|
+
totalLabel = "Total value",
|
|
118
|
+
totalValue,
|
|
119
|
+
priceLabel = "Today only",
|
|
120
|
+
price,
|
|
121
|
+
className
|
|
122
|
+
}) {
|
|
123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("rounded-2xl border border-border bg-card p-6", className), style: { boxShadow: "var(--shadow-card)" }, children: [
|
|
124
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "flex flex-col gap-3", children: items.map((it, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "flex items-center justify-between gap-4 text-sm", children: [
|
|
125
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex items-center gap-2 text-foreground", children: [
|
|
126
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Check, { className: cn("size-4 shrink-0", it.bonus ? "text-accent" : "text-primary") }),
|
|
127
|
+
it.label
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: cn("shrink-0 tabular-nums", it.bonus ? "font-semibold text-accent" : "text-muted-foreground"), children: it.value })
|
|
130
|
+
] }, i)) }),
|
|
131
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-4 flex items-center justify-between border-t border-border pt-4 text-sm", children: [
|
|
132
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: totalLabel }),
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground line-through tabular-nums", children: totalValue })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-2 flex items-end justify-between", children: [
|
|
136
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs font-medium uppercase tracking-wider text-primary", children: priceLabel }),
|
|
137
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-3xl font-bold tabular-nums text-foreground", children: price })
|
|
138
|
+
] })
|
|
139
|
+
] });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/guarantee-badge/index.tsx
|
|
143
|
+
var import_lucide_react3 = require("lucide-react");
|
|
144
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
145
|
+
function GuaranteeBadge({ title = "100% Guarantee", text, icon, className }) {
|
|
146
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
147
|
+
"div",
|
|
148
|
+
{
|
|
149
|
+
className: cn(
|
|
150
|
+
"flex items-center gap-4 rounded-xl border border-primary/25 bg-primary/5 p-4",
|
|
151
|
+
className
|
|
152
|
+
),
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex size-12 shrink-0 items-center justify-center rounded-full bg-primary/15 text-primary", children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.ShieldCheck, { className: "size-6" }) }),
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
156
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-semibold text-foreground", children: title }),
|
|
157
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-0.5 text-sm text-muted-foreground", children: text })
|
|
158
|
+
] })
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/countdown-cta/index.tsx
|
|
165
|
+
var React2 = __toESM(require("react"));
|
|
166
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
167
|
+
function useCountdown(deadline) {
|
|
168
|
+
const target = React2.useMemo(() => new Date(deadline).getTime(), [deadline]);
|
|
169
|
+
const [now, setNow] = React2.useState(() => Date.now());
|
|
170
|
+
React2.useEffect(() => {
|
|
171
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
172
|
+
return () => clearInterval(id);
|
|
173
|
+
}, []);
|
|
174
|
+
const diff = Math.max(0, target - now);
|
|
175
|
+
const s = Math.floor(diff / 1e3);
|
|
176
|
+
return {
|
|
177
|
+
expired: diff === 0,
|
|
178
|
+
days: Math.floor(s / 86400),
|
|
179
|
+
hours: Math.floor(s % 86400 / 3600),
|
|
180
|
+
minutes: Math.floor(s % 3600 / 60),
|
|
181
|
+
seconds: s % 60
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function Unit({ value, label }) {
|
|
185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col items-center", children: [
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-12 rounded-lg bg-background/10 px-2 py-1.5 text-center text-2xl font-bold tabular-nums text-primary-foreground", children: String(value).padStart(2, "0") }),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mt-1 text-[10px] uppercase tracking-wider text-primary-foreground/70", children: label })
|
|
188
|
+
] });
|
|
189
|
+
}
|
|
190
|
+
function CountdownCTA({
|
|
191
|
+
deadline,
|
|
192
|
+
heading,
|
|
193
|
+
subheading,
|
|
194
|
+
ctaLabel,
|
|
195
|
+
onCta,
|
|
196
|
+
ctaHref,
|
|
197
|
+
expiredLabel = "Offer ended",
|
|
198
|
+
className
|
|
199
|
+
}) {
|
|
200
|
+
const t = useCountdown(deadline);
|
|
201
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
202
|
+
"div",
|
|
203
|
+
{
|
|
204
|
+
className: cn("flex flex-col items-center gap-4 rounded-2xl p-6 text-center", className),
|
|
205
|
+
style: { background: "var(--gradient-primary, var(--primary))" },
|
|
206
|
+
children: [
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-xl font-bold text-primary-foreground", children: heading }),
|
|
209
|
+
subheading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "mt-1 text-sm text-primary-foreground/80", children: subheading })
|
|
210
|
+
] }),
|
|
211
|
+
t.expired ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-lg font-semibold text-primary-foreground", children: expiredLabel }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
212
|
+
t.days > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Unit, { value: t.days, label: "days" }),
|
|
213
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Unit, { value: t.hours, label: "hrs" }),
|
|
214
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Unit, { value: t.minutes, label: "min" }),
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Unit, { value: t.seconds, label: "sec" })
|
|
216
|
+
] }),
|
|
217
|
+
!t.expired && (ctaHref ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
218
|
+
"a",
|
|
219
|
+
{
|
|
220
|
+
href: ctaHref,
|
|
221
|
+
className: "rounded-lg bg-background px-6 py-2.5 text-sm font-semibold text-foreground transition-transform hover:scale-[1.02]",
|
|
222
|
+
children: ctaLabel
|
|
223
|
+
}
|
|
224
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
225
|
+
"button",
|
|
226
|
+
{
|
|
227
|
+
onClick: onCta,
|
|
228
|
+
className: "rounded-lg bg-background px-6 py-2.5 text-sm font-semibold text-foreground transition-transform hover:scale-[1.02]",
|
|
229
|
+
children: ctaLabel
|
|
230
|
+
}
|
|
231
|
+
))
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/funnel-steps/index.tsx
|
|
238
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
239
|
+
function FunnelSteps({ steps, activeIndex = -1, orientation = "horizontal", className }) {
|
|
240
|
+
const horizontal = orientation === "horizontal";
|
|
241
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
242
|
+
"ol",
|
|
243
|
+
{
|
|
244
|
+
className: cn(
|
|
245
|
+
"flex gap-4",
|
|
246
|
+
horizontal ? "flex-col sm:flex-row sm:items-start" : "flex-col",
|
|
247
|
+
className
|
|
248
|
+
),
|
|
249
|
+
children: steps.map((s, i) => {
|
|
250
|
+
const active = i <= activeIndex;
|
|
251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: cn("flex flex-1 gap-3", horizontal && "sm:flex-col sm:items-center sm:text-center"), children: [
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("flex items-center gap-3", horizontal && "sm:flex-col"), children: [
|
|
253
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
254
|
+
"span",
|
|
255
|
+
{
|
|
256
|
+
className: cn(
|
|
257
|
+
"flex size-10 shrink-0 items-center justify-center rounded-xl text-sm font-bold transition-colors",
|
|
258
|
+
active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
|
|
259
|
+
),
|
|
260
|
+
children: s.icon ?? i + 1
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
i < steps.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: cn("bg-border", horizontal ? "hidden h-px w-full sm:block" : "ml-5 h-6 w-px") })
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
266
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: cn("text-sm font-semibold", active ? "text-foreground" : "text-muted-foreground"), children: s.label }),
|
|
267
|
+
s.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: s.description })
|
|
268
|
+
] })
|
|
269
|
+
] }, i);
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
275
|
+
0 && (module.exports = {
|
|
276
|
+
CountdownCTA,
|
|
277
|
+
FunnelSteps,
|
|
278
|
+
GuaranteeBadge,
|
|
279
|
+
MultiStepForm,
|
|
280
|
+
OfferStack
|
|
281
|
+
});
|
package/dist/funnel.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-PJO7WJ4G.mjs";
|
|
4
|
+
|
|
5
|
+
// src/multi-step-form/index.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { Check } from "lucide-react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
function MultiStepForm({
|
|
10
|
+
steps,
|
|
11
|
+
onComplete,
|
|
12
|
+
nextLabel = "Continue",
|
|
13
|
+
backLabel = "Back",
|
|
14
|
+
submitLabel = "Submit",
|
|
15
|
+
className
|
|
16
|
+
}) {
|
|
17
|
+
const [i, setI] = React.useState(0);
|
|
18
|
+
const last = i === steps.length - 1;
|
|
19
|
+
const step = steps[i];
|
|
20
|
+
const pct = (i + 1) / steps.length * 100;
|
|
21
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-6", className), children: [
|
|
22
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: steps.map((s, idx) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
23
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
24
|
+
/* @__PURE__ */ jsx(
|
|
25
|
+
"span",
|
|
26
|
+
{
|
|
27
|
+
className: cn(
|
|
28
|
+
"flex size-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold transition-colors",
|
|
29
|
+
idx < i && "bg-primary text-primary-foreground",
|
|
30
|
+
idx === i && "bg-primary text-primary-foreground ring-4 ring-primary/20",
|
|
31
|
+
idx > i && "bg-muted text-muted-foreground"
|
|
32
|
+
),
|
|
33
|
+
children: idx < i ? /* @__PURE__ */ jsx(Check, { className: "size-3.5" }) : idx + 1
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ jsx("span", { className: cn("hidden text-sm sm:inline", idx === i ? "font-medium text-foreground" : "text-muted-foreground"), children: s.label })
|
|
37
|
+
] }),
|
|
38
|
+
idx < steps.length - 1 && /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
|
|
39
|
+
] }, idx)) }),
|
|
40
|
+
/* @__PURE__ */ jsx("div", { className: "h-1 w-full overflow-hidden rounded-full bg-muted sm:hidden", children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-primary transition-[width] duration-300", style: { width: `${pct}%` } }) }),
|
|
41
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-24", children: step.content }),
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
type: "button",
|
|
47
|
+
onClick: () => setI((x) => Math.max(0, x - 1)),
|
|
48
|
+
disabled: i === 0,
|
|
49
|
+
className: "rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted disabled:opacity-40 disabled:pointer-events-none",
|
|
50
|
+
children: backLabel
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
"button",
|
|
55
|
+
{
|
|
56
|
+
type: "button",
|
|
57
|
+
onClick: () => last ? onComplete?.() : setI((x) => Math.min(steps.length - 1, x + 1)),
|
|
58
|
+
disabled: step.canProceed === false,
|
|
59
|
+
className: "rounded-lg bg-primary px-5 py-2 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40 disabled:pointer-events-none",
|
|
60
|
+
children: last ? submitLabel : nextLabel
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
] })
|
|
64
|
+
] });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/offer-stack/index.tsx
|
|
68
|
+
import { Check as Check2 } from "lucide-react";
|
|
69
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
70
|
+
function OfferStack({
|
|
71
|
+
items,
|
|
72
|
+
totalLabel = "Total value",
|
|
73
|
+
totalValue,
|
|
74
|
+
priceLabel = "Today only",
|
|
75
|
+
price,
|
|
76
|
+
className
|
|
77
|
+
}) {
|
|
78
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("rounded-2xl border border-border bg-card p-6", className), style: { boxShadow: "var(--shadow-card)" }, children: [
|
|
79
|
+
/* @__PURE__ */ jsx2("ul", { className: "flex flex-col gap-3", children: items.map((it, i) => /* @__PURE__ */ jsxs2("li", { className: "flex items-center justify-between gap-4 text-sm", children: [
|
|
80
|
+
/* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-2 text-foreground", children: [
|
|
81
|
+
/* @__PURE__ */ jsx2(Check2, { className: cn("size-4 shrink-0", it.bonus ? "text-accent" : "text-primary") }),
|
|
82
|
+
it.label
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx2("span", { className: cn("shrink-0 tabular-nums", it.bonus ? "font-semibold text-accent" : "text-muted-foreground"), children: it.value })
|
|
85
|
+
] }, i)) }),
|
|
86
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-4 flex items-center justify-between border-t border-border pt-4 text-sm", children: [
|
|
87
|
+
/* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: totalLabel }),
|
|
88
|
+
/* @__PURE__ */ jsx2("span", { className: "text-muted-foreground line-through tabular-nums", children: totalValue })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-2 flex items-end justify-between", children: [
|
|
91
|
+
/* @__PURE__ */ jsx2("span", { className: "text-xs font-medium uppercase tracking-wider text-primary", children: priceLabel }),
|
|
92
|
+
/* @__PURE__ */ jsx2("span", { className: "text-3xl font-bold tabular-nums text-foreground", children: price })
|
|
93
|
+
] })
|
|
94
|
+
] });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/guarantee-badge/index.tsx
|
|
98
|
+
import { ShieldCheck } from "lucide-react";
|
|
99
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
100
|
+
function GuaranteeBadge({ title = "100% Guarantee", text, icon, className }) {
|
|
101
|
+
return /* @__PURE__ */ jsxs3(
|
|
102
|
+
"div",
|
|
103
|
+
{
|
|
104
|
+
className: cn(
|
|
105
|
+
"flex items-center gap-4 rounded-xl border border-primary/25 bg-primary/5 p-4",
|
|
106
|
+
className
|
|
107
|
+
),
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsx3("span", { className: "flex size-12 shrink-0 items-center justify-center rounded-full bg-primary/15 text-primary", children: icon ?? /* @__PURE__ */ jsx3(ShieldCheck, { className: "size-6" }) }),
|
|
110
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
111
|
+
/* @__PURE__ */ jsx3("p", { className: "text-sm font-semibold text-foreground", children: title }),
|
|
112
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-0.5 text-sm text-muted-foreground", children: text })
|
|
113
|
+
] })
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/countdown-cta/index.tsx
|
|
120
|
+
import * as React2 from "react";
|
|
121
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
122
|
+
function useCountdown(deadline) {
|
|
123
|
+
const target = React2.useMemo(() => new Date(deadline).getTime(), [deadline]);
|
|
124
|
+
const [now, setNow] = React2.useState(() => Date.now());
|
|
125
|
+
React2.useEffect(() => {
|
|
126
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
127
|
+
return () => clearInterval(id);
|
|
128
|
+
}, []);
|
|
129
|
+
const diff = Math.max(0, target - now);
|
|
130
|
+
const s = Math.floor(diff / 1e3);
|
|
131
|
+
return {
|
|
132
|
+
expired: diff === 0,
|
|
133
|
+
days: Math.floor(s / 86400),
|
|
134
|
+
hours: Math.floor(s % 86400 / 3600),
|
|
135
|
+
minutes: Math.floor(s % 3600 / 60),
|
|
136
|
+
seconds: s % 60
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function Unit({ value, label }) {
|
|
140
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center", children: [
|
|
141
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-12 rounded-lg bg-background/10 px-2 py-1.5 text-center text-2xl font-bold tabular-nums text-primary-foreground", children: String(value).padStart(2, "0") }),
|
|
142
|
+
/* @__PURE__ */ jsx4("span", { className: "mt-1 text-[10px] uppercase tracking-wider text-primary-foreground/70", children: label })
|
|
143
|
+
] });
|
|
144
|
+
}
|
|
145
|
+
function CountdownCTA({
|
|
146
|
+
deadline,
|
|
147
|
+
heading,
|
|
148
|
+
subheading,
|
|
149
|
+
ctaLabel,
|
|
150
|
+
onCta,
|
|
151
|
+
ctaHref,
|
|
152
|
+
expiredLabel = "Offer ended",
|
|
153
|
+
className
|
|
154
|
+
}) {
|
|
155
|
+
const t = useCountdown(deadline);
|
|
156
|
+
return /* @__PURE__ */ jsxs4(
|
|
157
|
+
"div",
|
|
158
|
+
{
|
|
159
|
+
className: cn("flex flex-col items-center gap-4 rounded-2xl p-6 text-center", className),
|
|
160
|
+
style: { background: "var(--gradient-primary, var(--primary))" },
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
163
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-xl font-bold text-primary-foreground", children: heading }),
|
|
164
|
+
subheading && /* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm text-primary-foreground/80", children: subheading })
|
|
165
|
+
] }),
|
|
166
|
+
t.expired ? /* @__PURE__ */ jsx4("p", { className: "text-lg font-semibold text-primary-foreground", children: expiredLabel }) : /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
167
|
+
t.days > 0 && /* @__PURE__ */ jsx4(Unit, { value: t.days, label: "days" }),
|
|
168
|
+
/* @__PURE__ */ jsx4(Unit, { value: t.hours, label: "hrs" }),
|
|
169
|
+
/* @__PURE__ */ jsx4(Unit, { value: t.minutes, label: "min" }),
|
|
170
|
+
/* @__PURE__ */ jsx4(Unit, { value: t.seconds, label: "sec" })
|
|
171
|
+
] }),
|
|
172
|
+
!t.expired && (ctaHref ? /* @__PURE__ */ jsx4(
|
|
173
|
+
"a",
|
|
174
|
+
{
|
|
175
|
+
href: ctaHref,
|
|
176
|
+
className: "rounded-lg bg-background px-6 py-2.5 text-sm font-semibold text-foreground transition-transform hover:scale-[1.02]",
|
|
177
|
+
children: ctaLabel
|
|
178
|
+
}
|
|
179
|
+
) : /* @__PURE__ */ jsx4(
|
|
180
|
+
"button",
|
|
181
|
+
{
|
|
182
|
+
onClick: onCta,
|
|
183
|
+
className: "rounded-lg bg-background px-6 py-2.5 text-sm font-semibold text-foreground transition-transform hover:scale-[1.02]",
|
|
184
|
+
children: ctaLabel
|
|
185
|
+
}
|
|
186
|
+
))
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/funnel-steps/index.tsx
|
|
193
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
194
|
+
function FunnelSteps({ steps, activeIndex = -1, orientation = "horizontal", className }) {
|
|
195
|
+
const horizontal = orientation === "horizontal";
|
|
196
|
+
return /* @__PURE__ */ jsx5(
|
|
197
|
+
"ol",
|
|
198
|
+
{
|
|
199
|
+
className: cn(
|
|
200
|
+
"flex gap-4",
|
|
201
|
+
horizontal ? "flex-col sm:flex-row sm:items-start" : "flex-col",
|
|
202
|
+
className
|
|
203
|
+
),
|
|
204
|
+
children: steps.map((s, i) => {
|
|
205
|
+
const active = i <= activeIndex;
|
|
206
|
+
return /* @__PURE__ */ jsxs5("li", { className: cn("flex flex-1 gap-3", horizontal && "sm:flex-col sm:items-center sm:text-center"), children: [
|
|
207
|
+
/* @__PURE__ */ jsxs5("div", { className: cn("flex items-center gap-3", horizontal && "sm:flex-col"), children: [
|
|
208
|
+
/* @__PURE__ */ jsx5(
|
|
209
|
+
"span",
|
|
210
|
+
{
|
|
211
|
+
className: cn(
|
|
212
|
+
"flex size-10 shrink-0 items-center justify-center rounded-xl text-sm font-bold transition-colors",
|
|
213
|
+
active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
|
|
214
|
+
),
|
|
215
|
+
children: s.icon ?? i + 1
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
i < steps.length - 1 && /* @__PURE__ */ jsx5("span", { className: cn("bg-border", horizontal ? "hidden h-px w-full sm:block" : "ml-5 h-6 w-px") })
|
|
219
|
+
] }),
|
|
220
|
+
/* @__PURE__ */ jsxs5("div", { children: [
|
|
221
|
+
/* @__PURE__ */ jsx5("p", { className: cn("text-sm font-semibold", active ? "text-foreground" : "text-muted-foreground"), children: s.label }),
|
|
222
|
+
s.description && /* @__PURE__ */ jsx5("p", { className: "mt-0.5 text-xs text-muted-foreground", children: s.description })
|
|
223
|
+
] })
|
|
224
|
+
] }, i);
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
CountdownCTA,
|
|
231
|
+
FunnelSteps,
|
|
232
|
+
GuaranteeBadge,
|
|
233
|
+
MultiStepForm,
|
|
234
|
+
OfferStack
|
|
235
|
+
};
|