"use client";
import { Label } from "@zexon/ui/components/label";
import { Switch } from "@zexon/ui/components/switch";
import { cn } from "@zexon/ui/lib/utils";
import * as React from "react";
export interface PricingFeature {
label: string;
included: boolean;
}
export interface PricingPlan {
name: string;
description: string;
price: string;
period?: string;
yearlyPrice?: string;
yearlySavings?: string;
credits?: string;
popular?: boolean;
features: (string | PricingFeature)[];
cta: {
label: string;
href: string;
};
}
export interface PricingSectionProps extends React.ComponentProps<"section"> {
title?: string;
description?: string;
plans: PricingPlan[];
showYearlyToggle?: boolean;
}
export function PricingSection({
title = "Preços",
description,
plans,
showYearlyToggle = true,
className,
...props
}: PricingSectionProps) {
const [yearly, setYearly] = React.useState(false);
const hasYearlyPrices = plans.some((p) => p.yearlyPrice);
return (
<section
data-slot="pricing-section"
className={cn("px-4 py-16", className)}
{...props}
>
<div className="mx-auto mb-10 max-w-2xl text-center">
{title && (
<h2 className="text-3xl font-bold tracking-tight text-foreground">
{title}
</h2>
)}
{description && (
<p className="mt-3 text-base text-muted-foreground">
{description}
</p>
)}
</div>
<div
className={cn(
"mx-auto grid max-w-5xl gap-4",
plans.length === 1
? "max-w-sm"
: plans.length === 2
? "md:grid-cols-2"
: plans.length === 3
? "md:grid-cols-3"
: "md:grid-cols-2 lg:grid-cols-4",
)}
>
{plans.map((plan) => (
<PricingCard
key={plan.name}
plan={plan}
yearly={yearly}
showYearlyToggle={showYearlyToggle && hasYearlyPrices}
onToggleYearly={() => setYearly((v) => !v)}
/>
))}
</div>
</section>
);
}
function PricingCard({
plan,
yearly,
showYearlyToggle,
onToggleYearly,
}: {
plan: PricingPlan;
yearly: boolean;
showYearlyToggle: boolean;
onToggleYearly: () => void;
}) {
const yearlyToggleId = React.useId();
const price =
yearly && plan.yearlyPrice ? plan.yearlyPrice : plan.price;
const period = plan.period ?? (yearly ? "/ano" : "/mês");
return (
<div
data-slot="pricing-card"
data-popular={plan.popular || undefined}
className={cn(
"relative flex flex-col rounded border border-border bg-card p-6 transition-all hover:-translate-y-0.5 hover:shadow-sm",
plan.popular && "border-primary shadow-sm",
)}
>
<div className="mb-4 flex items-start justify-between gap-2">
<div>
<h3 className="text-sm font-semibold text-foreground">
{plan.name}
</h3>
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
{plan.description}
</p>
</div>
{plan.popular && (
<span className="shrink-0 rounded bg-primary px-2 py-0.5 text-[10px] font-semibold text-primary-foreground">
Popular
</span>
)}
</div>
{plan.credits && (
<p className="mb-3 text-xs text-muted-foreground">
{plan.credits}
</p>
)}
<div className="mb-1 flex items-baseline gap-1">
<span className="text-3xl font-bold tracking-tight text-foreground">
{price}
</span>
<span className="text-xs text-muted-foreground">{period}</span>
</div>
{showYearlyToggle && plan.yearlyPrice && (
<div className="mb-4 flex flex-wrap items-center gap-2">
<div className="flex items-center gap-2">
<Switch
id={yearlyToggleId}
size="sm"
checked={yearly}
onCheckedChange={(next) => {
if (next !== yearly) onToggleYearly();
}}
aria-label="Cobrança anual"
/>
<Label
htmlFor={yearlyToggleId}
className="cursor-pointer text-xs font-normal text-muted-foreground"
>
Anual
</Label>
</div>
{plan.yearlySavings && (
<span className="text-xs font-medium text-primary">
{plan.yearlySavings}
</span>
)}
</div>
)}
<a
href={plan.cta.href}
className={cn(
"mb-5 inline-flex h-9 w-full cursor-pointer items-center justify-center rounded text-sm font-medium transition-colors",
plan.popular
? "bg-primary text-primary-foreground hover:bg-primary/90"
: "border border-border bg-background text-foreground hover:bg-muted",
)}
>
{plan.cta.label}
</a>
<ul className="flex flex-col gap-2.5">
{plan.features.map((feature) => {
const label =
typeof feature === "string" ? feature : feature.label;
const included =
typeof feature === "string" ? true : feature.included;
return (
<li
key={label}
className="flex items-start gap-2 text-xs text-muted-foreground"
>
<svg
className={cn(
"mt-0.5 size-3.5 shrink-0",
included
? "text-primary"
: "text-muted-foreground/40",
)}
viewBox="0 0 16 16"
fill="none"
aria-hidden="true"
>
<path
d="M3.5 8.5L6.5 11.5L12.5 4.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span>{label}</span>
</li>
);
})}
</ul>
</div>
);
}