registry/bases/editframe/ui/checkout-flow/index.tsx
"use client";
import { useFramecnTheme } from "@/lib/framecn-ui";
import type { FramecnTheme } from "@/lib/framecn-ui";
import { BlurIn } from "@/components/framecn/blur-in";
import { useBlurInTransition } from "@/components/framecn/use-blur-in-transition";
import { Button } from "@/components/framecn/button";
import { useButtonTransition } from "@/components/framecn/use-button-transition";
import { Checkbox } from "@/components/framecn/checkbox";
import { useCheckboxTransition } from "@/components/framecn/use-checkbox-transition";
import { Cursor } from "@/components/framecn/cursor";
import { useCursorPath } from "@/components/framecn/use-cursor-path";
import {
Field,
FieldControl,
FieldGroup,
FieldLabel,
} from "@/components/framecn/field";
import { Input } from "@/components/framecn/input";
import { useInputTransition } from "@/components/framecn/use-input-transition";
import { Toast } from "@/components/framecn/toast";
import { useToastTransition } from "@/components/framecn/use-toast-transition";
import { ToggleGroup } from "@/components/framecn/toggle-group";
import type { ToggleGroupItem } from "@/components/framecn/toggle-group";
import { useToggleGroupTransition } from "@/components/framecn/use-toggle-group-transition";
const DEFAULT_PLANS: ToggleGroupItem[] = [
{ label: "Monthly", value: "monthly" },
{ label: "Yearly", value: "yearly" },
];
const STAGE_W = 1280;
const CARD_W = 420;
const CARD_PAD = 28;
const CARD_TOP = 96;
const CARD_LEFT = (STAGE_W - CARD_W) / 2;
const CENTER_X = STAGE_W / 2;
const CONTENT_LEFT = CARD_LEFT + CARD_PAD;
const CONTENT_RIGHT = CARD_LEFT + CARD_W - CARD_PAD;
const TOGGLE_Y = 222;
const YEARLY_X = CONTENT_LEFT + 4 + 88 + 44;
const CARD_X = CENTER_X;
const CARD_Y = 312;
const TERMS_X = CONTENT_LEFT + 12;
const TERMS_Y = 376;
const PAY_X = CONTENT_RIGHT - 48;
const PAY_Y = 442;
export interface CheckoutFlowProps {
title?: string;
description?: string;
plans?: ToggleGroupItem[];
cardLabel?: string;
cardPlaceholder?: string;
termsLabel?: string;
payLabel?: string;
toastTitle?: string;
theme?: Partial<FramecnTheme>;
}
export const CheckoutFlow = ({
title = "Upgrade your plan",
description = "Complete your purchase to unlock every feature.",
plans = DEFAULT_PLANS,
cardLabel = "Card number",
cardPlaceholder = "4242 4242 4242 4242",
termsLabel = "I accept the terms and conditions",
payLabel = "Pay $49",
toastTitle = "Payment successful",
theme,
}: CheckoutFlowProps) => {
const resolved = useFramecnTheme(theme);
const opts = { theme };
const cardEnter = useBlurInTransition(
[{ at: 0, duration: 18, state: "revealed" }],
{ distance: 0 }
);
const enterHeader = useBlurInTransition([
{ at: 18, duration: 16, state: "revealed" },
]);
const enterToggle = useBlurInTransition([
{ at: 24, duration: 16, state: "revealed" },
]);
const enterCard = useBlurInTransition([
{ at: 30, duration: 16, state: "revealed" },
]);
const enterTerms = useBlurInTransition([
{ at: 36, duration: 16, state: "revealed" },
]);
const enterButton = useBlurInTransition([
{ at: 42, duration: 16, state: "revealed" },
]);
const cursorStyle = useCursorPath([
{ at: 0, x: 140, y: 90 },
{ at: 64, click: true, duration: 22, x: YEARLY_X, y: TOGGLE_Y },
{ at: 96, click: true, duration: 24, x: CARD_X, y: CARD_Y },
{ at: 150, click: true, duration: 30, x: TERMS_X, y: TERMS_Y },
{ at: 180, click: true, duration: 26, x: PAY_X, y: PAY_Y },
]);
const toggleStyle = useToggleGroupTransition(
[{ at: 64, duration: 16, state: plans[1]?.value ?? "yearly" }],
{ items: plans, ...opts }
);
const cardStyle = useInputTransition(
[
{ at: 96, duration: 6, state: "active" },
{ at: 100, duration: 40, state: "typing" },
{ at: 150, duration: 8, state: "blur" },
],
opts
);
const checkboxStyle = useCheckboxTransition(
[{ at: 150, duration: 14, state: "checked" }],
opts
);
const payStyle = useButtonTransition(
[
{ at: 172, duration: 8, state: "hover" },
{ at: 180, duration: 6, state: "press" },
{ at: 186, duration: 6, state: "loading" },
{ at: 224, duration: 16, state: "success" },
],
opts
);
const toastStyle = useToastTransition(
[
{ at: 224, duration: 14, state: "visible" },
{ at: 286, duration: 14, state: "hidden" },
],
{}
);
return (
<div
style={{
background: "transparent",
fontFamily:
"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif",
height: "100%",
position: "relative",
width: "100%",
}}
>
<div
style={{
background: resolved.card,
border: `1px solid ${resolved.border}`,
borderRadius: resolved.radius + 4,
boxShadow:
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
boxSizing: "border-box",
color: resolved.cardForeground,
display: "flex",
filter: cardEnter.blur > 0 ? `blur(${cardEnter.blur}px)` : "none",
flexDirection: "column",
gap: 24,
height: 420,
left: CARD_LEFT,
opacity: cardEnter.opacity,
padding: 28,
position: "absolute",
top: CARD_TOP,
width: CARD_W,
}}
>
<BlurIn display="block" style={enterHeader}>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<div
style={{
color: resolved.cardForeground,
fontSize: 16,
fontWeight: 600,
lineHeight: 1,
}}
>
{title}
</div>
<div
style={{
color: resolved.mutedForeground,
fontSize: 14,
lineHeight: "20px",
}}
>
{description}
</div>
</div>
</BlurIn>
<BlurIn display="block" style={enterToggle}>
<FieldControl height={44}>
<ToggleGroup
style={toggleStyle}
items={plans}
align="start"
theme={theme}
/>
</FieldControl>
</BlurIn>
<FieldGroup gap={24}>
<BlurIn display="block" style={enterCard}>
<Field>
<FieldLabel theme={theme}>{cardLabel}</FieldLabel>
<FieldControl>
<Input
placeholder={cardPlaceholder}
value={cardPlaceholder}
style={cardStyle}
fullWidth
theme={theme}
/>
</FieldControl>
</Field>
</BlurIn>
<BlurIn display="block" style={enterTerms}>
<FieldControl>
<Checkbox
label={termsLabel}
style={checkboxStyle}
align="start"
theme={theme}
/>
</FieldControl>
</BlurIn>
</FieldGroup>
<BlurIn display="block" style={enterButton}>
<Field gap={10}>
<FieldControl height={44}>
<Button
label={payLabel}
style={payStyle}
align="end"
theme={theme}
/>
</FieldControl>
</Field>
</BlurIn>
</div>
<div style={{ bottom: 32, position: "absolute", right: 32 }}>
<Toast
title={toastTitle}
variant="success"
style={toastStyle}
theme={theme}
/>
</div>
<Cursor style={cursorStyle} variant="pointer" theme={theme} />
</div>
);
};





























