@olwiba/ui 0.2.7 → 0.2.8
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/index.d.ts +35 -1
- package/dist/index.js +59 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/marketing/PricingSection.tsx +172 -29
package/dist/index.d.ts
CHANGED
|
@@ -703,10 +703,36 @@ interface PricingCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
703
703
|
}
|
|
704
704
|
declare function PricingCard({ name, price, period, description, features, cta, highlighted, disabled, ctaDisabled, badge, footer, onSelect, className, ...props }: PricingCardProps): react_jsx_runtime.JSX.Element;
|
|
705
705
|
|
|
706
|
+
/**
|
|
707
|
+
* One billing period a plan can be bought at.
|
|
708
|
+
*
|
|
709
|
+
* Products are not all sold monthly-or-annually: weekly, quarterly, and
|
|
710
|
+
* one-off all exist. Pass `cadences` to describe whatever this product
|
|
711
|
+
* actually sells and the section renders a tab per entry, rather than the
|
|
712
|
+
* fixed Monthly/Annual pair it assumes otherwise.
|
|
713
|
+
*/
|
|
714
|
+
interface PricingCadence {
|
|
715
|
+
/** Matches the keys of `PricingPlan.prices`. */
|
|
716
|
+
key: string;
|
|
717
|
+
label: string;
|
|
718
|
+
/**
|
|
719
|
+
* Billing periods in a year — 52 weekly, 12 monthly, 1 annual. Used only to
|
|
720
|
+
* compare cadences for the savings badge; omit it and no badge is computed
|
|
721
|
+
* for this cadence.
|
|
722
|
+
*/
|
|
723
|
+
periodsPerYear?: number;
|
|
724
|
+
/** Price suffix, e.g. "/ week". Falls back to `PricingPlan.periodDisplay`. */
|
|
725
|
+
suffix?: string;
|
|
726
|
+
}
|
|
706
727
|
interface PricingPlan {
|
|
707
728
|
name: string;
|
|
708
729
|
monthly: number;
|
|
709
730
|
annual: number;
|
|
731
|
+
/**
|
|
732
|
+
* Price per cadence key, for products using `cadences`. The `monthly` and
|
|
733
|
+
* `annual` fields above stay as the fallback for callers that don't.
|
|
734
|
+
*/
|
|
735
|
+
prices?: Record<string, number>;
|
|
710
736
|
description: string;
|
|
711
737
|
cta: string;
|
|
712
738
|
highlighted?: boolean;
|
|
@@ -736,8 +762,16 @@ interface PricingSectionProps {
|
|
|
736
762
|
highlightedBadgeLabel?: string;
|
|
737
763
|
/** Rendered below each plan's CTA button (e.g. a "Get notified" link). */
|
|
738
764
|
renderPlanFooter?: (plan: PricingPlan) => React.ReactNode;
|
|
765
|
+
/**
|
|
766
|
+
* Billing periods this product sells at. One entry renders no toggle at all;
|
|
767
|
+
* two or more render a tab each, with savings badges computed from
|
|
768
|
+
* `periodsPerYear`. Omit to keep the built-in Monthly/Annual pair.
|
|
769
|
+
*/
|
|
770
|
+
cadences?: PricingCadence[];
|
|
771
|
+
/** Which cadence opens selected. Defaults to the first in `cadences`. */
|
|
772
|
+
defaultCadence?: string;
|
|
739
773
|
}
|
|
740
|
-
declare function PricingSection({ title, description, badge, plans, saveBadge, isAuthenticated, renderLink, footnote, mode, foundingDeadline, currency, highlightedBadgeLabel, renderPlanFooter, }: PricingSectionProps): react_jsx_runtime.JSX.Element;
|
|
774
|
+
declare function PricingSection({ title, description, badge, plans, saveBadge, isAuthenticated, renderLink, footnote, mode, foundingDeadline, currency, highlightedBadgeLabel, renderPlanFooter, cadences, defaultCadence, }: PricingSectionProps): react_jsx_runtime.JSX.Element;
|
|
741
775
|
|
|
742
776
|
interface TestimonialsSectionProps {
|
|
743
777
|
title?: string;
|
package/dist/index.js
CHANGED
|
@@ -2282,6 +2282,32 @@ function CountdownTimer({
|
|
|
2282
2282
|
] }, unit)) })
|
|
2283
2283
|
] });
|
|
2284
2284
|
}
|
|
2285
|
+
function computeSaveBadges(plans, cadences, defaultKey) {
|
|
2286
|
+
const annualised = (key) => {
|
|
2287
|
+
const cadence = cadences.find((c) => c.key === key);
|
|
2288
|
+
if (!cadence?.periodsPerYear) return null;
|
|
2289
|
+
const totals = plans.filter((plan) => plan.priceDisplay === void 0 && plan.prices?.[key] !== void 0).map((plan) => plan.prices[key] * cadence.periodsPerYear);
|
|
2290
|
+
return totals.length > 0 ? totals.reduce((sum, n) => sum + n, 0) : null;
|
|
2291
|
+
};
|
|
2292
|
+
const baseline = annualised(defaultKey);
|
|
2293
|
+
if (!baseline) return {};
|
|
2294
|
+
const badges = {};
|
|
2295
|
+
for (const cadence of cadences) {
|
|
2296
|
+
if (cadence.key === defaultKey) continue;
|
|
2297
|
+
const total = annualised(cadence.key);
|
|
2298
|
+
if (!total || total >= baseline) continue;
|
|
2299
|
+
const percent = Math.round((baseline - total) / baseline * 100);
|
|
2300
|
+
if (percent > 0) badges[cadence.key] = `Save ${percent}%`;
|
|
2301
|
+
}
|
|
2302
|
+
return badges;
|
|
2303
|
+
}
|
|
2304
|
+
function gridClassesFor(count) {
|
|
2305
|
+
if (count <= 1) return "mx-auto max-w-sm";
|
|
2306
|
+
if (count === 2) return "mx-auto max-w-3xl sm:grid-cols-2";
|
|
2307
|
+
if (count === 3) return "mx-auto max-w-5xl lg:grid-cols-3";
|
|
2308
|
+
if (count === 4) return "mx-auto max-w-6xl sm:grid-cols-2 lg:grid-cols-4";
|
|
2309
|
+
return "mx-auto max-w-5xl sm:grid-cols-2 lg:grid-cols-3";
|
|
2310
|
+
}
|
|
2285
2311
|
var defaultRenderLink6 = ({ href, children, className }) => /* @__PURE__ */ jsx("a", { href, className, children });
|
|
2286
2312
|
function PricingSection({
|
|
2287
2313
|
title = "Simple, transparent pricing",
|
|
@@ -2296,10 +2322,21 @@ function PricingSection({
|
|
|
2296
2322
|
foundingDeadline,
|
|
2297
2323
|
currency = "$",
|
|
2298
2324
|
highlightedBadgeLabel = "Founding member",
|
|
2299
|
-
renderPlanFooter
|
|
2325
|
+
renderPlanFooter,
|
|
2326
|
+
cadences,
|
|
2327
|
+
defaultCadence
|
|
2300
2328
|
}) {
|
|
2301
2329
|
const [annual, setAnnual] = React35.useState(false);
|
|
2302
2330
|
const isOneTime = mode === "one-time";
|
|
2331
|
+
const useCadences = !!cadences?.length;
|
|
2332
|
+
const initialCadence = (defaultCadence && cadences?.some((c) => c.key === defaultCadence) ? defaultCadence : null) ?? cadences?.[0]?.key ?? "";
|
|
2333
|
+
const [activeCadence, setActiveCadence] = React35.useState(initialCadence);
|
|
2334
|
+
const cadence = cadences?.find((c) => c.key === activeCadence);
|
|
2335
|
+
const saveBadges = React35.useMemo(
|
|
2336
|
+
() => useCadences ? computeSaveBadges(plans, cadences, initialCadence) : {},
|
|
2337
|
+
[useCadences, plans, cadences, initialCadence]
|
|
2338
|
+
);
|
|
2339
|
+
const showToggle = useCadences ? cadences.length > 1 : !isOneTime;
|
|
2303
2340
|
const uiMode = useUIVariant();
|
|
2304
2341
|
const sectionClasses = cn$1(
|
|
2305
2342
|
"overflow-hidden bg-card",
|
|
@@ -2312,7 +2349,23 @@ function PricingSection({
|
|
|
2312
2349
|
badge && (typeof badge === "string" ? /* @__PURE__ */ jsx(Badge$1, { variant: "secondary", className: "mb-4", children: badge }) : /* @__PURE__ */ jsx("div", { className: "mb-4", children: badge })),
|
|
2313
2350
|
/* @__PURE__ */ jsx("h2", { className: "text-balance text-3xl font-semibold tracking-tight sm:text-4xl", children: title }),
|
|
2314
2351
|
description && /* @__PURE__ */ jsx("p", { className: "mx-auto mt-4 max-w-xl text-pretty text-muted-foreground", children: description }),
|
|
2315
|
-
|
|
2352
|
+
showToggle && /* @__PURE__ */ jsx("div", { className: "mt-6 inline-flex items-center gap-3 rounded-full border bg-muted p-1", children: useCadences ? cadences.map((entry) => /* @__PURE__ */ jsxs(
|
|
2353
|
+
Button$1,
|
|
2354
|
+
{
|
|
2355
|
+
variant: "ghost",
|
|
2356
|
+
size: "sm",
|
|
2357
|
+
onClick: () => setActiveCadence(entry.key),
|
|
2358
|
+
className: cn$1(
|
|
2359
|
+
"flex items-center gap-2 rounded-full px-4 py-1.5 text-sm font-medium transition-colors",
|
|
2360
|
+
entry.key === activeCadence ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
2361
|
+
),
|
|
2362
|
+
children: [
|
|
2363
|
+
entry.label,
|
|
2364
|
+
saveBadges[entry.key] && /* @__PURE__ */ jsx(Badge$1, { variant: "secondary", className: "text-xs", children: saveBadges[entry.key] })
|
|
2365
|
+
]
|
|
2366
|
+
},
|
|
2367
|
+
entry.key
|
|
2368
|
+
)) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2316
2369
|
/* @__PURE__ */ jsx(
|
|
2317
2370
|
Button$1,
|
|
2318
2371
|
{
|
|
@@ -2342,12 +2395,12 @@ function PricingSection({
|
|
|
2342
2395
|
]
|
|
2343
2396
|
}
|
|
2344
2397
|
)
|
|
2345
|
-
] })
|
|
2398
|
+
] }) })
|
|
2346
2399
|
] }),
|
|
2347
|
-
/* @__PURE__ */ jsx(StaggerChildren, { className: "mt-10 grid gap-4
|
|
2348
|
-
const rawPrice = isOneTime ? plan.monthly : annual ? plan.annual : plan.monthly;
|
|
2400
|
+
/* @__PURE__ */ jsx(StaggerChildren, { className: cn$1("mt-10 grid gap-4", gridClassesFor(plans.length)), children: plans.map((plan) => {
|
|
2401
|
+
const rawPrice = useCadences ? plan.prices?.[activeCadence] ?? plan.monthly : isOneTime ? plan.monthly : annual ? plan.annual : plan.monthly;
|
|
2349
2402
|
const price = plan.priceDisplay ?? `${currency}${rawPrice}`;
|
|
2350
|
-
const period = plan.periodDisplay ?? (isOneTime ? "one-time" : rawPrice > 0 ? "/mo" : "");
|
|
2403
|
+
const period = useCadences ? cadence?.suffix ?? plan.periodDisplay ?? "" : plan.periodDisplay ?? (isOneTime ? "one-time" : rawPrice > 0 ? "/mo" : "");
|
|
2351
2404
|
const badge2 = plan.highlighted && foundingDeadline ? /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full border bg-secondary px-2.5 py-0.5 text-xs font-semibold text-secondary-foreground", children: /* @__PURE__ */ jsx(CountdownTimer, { deadline: foundingDeadline, compact: true }) }) : plan.highlighted && highlightedBadgeLabel ? highlightedBadgeLabel : void 0;
|
|
2352
2405
|
return /* @__PURE__ */ jsx(
|
|
2353
2406
|
PricingCard,
|