From 29587f66bd76281447e07d1459031620ab427ef3 Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Sun, 5 Jan 2025 22:15:24 +0100 Subject: [PATCH] =?UTF-8?q?update=20code=20pricing=20component=20?= =?UTF-8?q?=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __registry__/index.tsx | 11 + public/r/index.json | 17 + public/r/styles/default/pricing.json | 12 +- src/components/prismui/pricing.tsx | 142 +++++++ src/content/docs/sections/pricing.mdx | 40 +- src/registry/example/pricing-basic.tsx | 507 +++++++++++++++++++++++++ src/registry/registry-components.ts | 23 ++ src/registry/registry-examples.ts | 18 + 8 files changed, 763 insertions(+), 7 deletions(-) create mode 100644 src/components/prismui/pricing.tsx create mode 100644 src/registry/example/pricing-basic.tsx diff --git a/__registry__/index.tsx b/__registry__/index.tsx index 2881145..08c994d 100644 --- a/__registry__/index.tsx +++ b/__registry__/index.tsx @@ -181,5 +181,16 @@ export const Index: Record = { subcategory: "overlay", chunks: [] }, + "pricing": { + name: "pricing", + type: "registry:ui", + registryDependencies: undefined, + files: ["src/components/prismui/pricing.tsx"], + component: React.lazy(() => import("@/components/prismui/pricing.tsx")), + source: "", + category: "sections", + subcategory: "marketing", + chunks: [] + }, }, } diff --git a/public/r/index.json b/public/r/index.json index 69e5d61..bab02ed 100644 --- a/public/r/index.json +++ b/public/r/index.json @@ -211,5 +211,22 @@ ], "category": "components", "subcategory": "overlay" + }, + { + "name": "pricing", + "type": "registry:ui", + "code": "\"use client\";\n\n// ... (paste the entire component code here)\n", + "files": [ + { + "path": "components/prismui/pricing.tsx", + "type": "registry:ui" + } + ], + "dependencies": [ + "framer-motion", + "canvas-confetti" + ], + "category": "sections", + "subcategory": "marketing" } ] \ No newline at end of file diff --git a/public/r/styles/default/pricing.json b/public/r/styles/default/pricing.json index dba8310..96f5599 100644 --- a/public/r/styles/default/pricing.json +++ b/public/r/styles/default/pricing.json @@ -1,14 +1,16 @@ { "name": "pricing", - "type": "registry:block", + "type": "registry:ui", + "code": "\"use client\";\n\n// ... (paste the entire component code here)\n", "files": [ { - "path": "section/pricing.tsx", - "type": "registry:block", - "content": "\"use client\";\n\nimport Section from \"@/components/section\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { siteConfig } from \"@/lib/config\";\nimport useWindowSize from \"@/lib/hooks/use-window-size\";\nimport { cn } from \"@/lib/utils\";\nimport { motion } from \"framer-motion\";\nimport { Check } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useState, useRef } from \"react\";\nimport { FaStar } from \"react-icons/fa\";\nimport confetti from \"canvas-confetti\";\nimport NumberFlow from \"@number-flow/react\";\n\nexport default function Pricing() {\n const [isMonthly, setIsMonthly] = useState(true);\n const { isDesktop } = useWindowSize();\n const switchRef = useRef(null);\n\n const handleToggle = (checked: boolean) => {\n setIsMonthly(!checked);\n if (checked && switchRef.current) {\n const rect = switchRef.current.getBoundingClientRect();\n const x = rect.left + rect.width / 2;\n const y = rect.top + rect.height / 2;\n\n confetti({\n particleCount: 50,\n spread: 60,\n origin: {\n x: x / window.innerWidth,\n y: y / window.innerHeight,\n },\n colors: [\n \"hsl(var(--primary))\",\n \"hsl(var(--accent))\",\n \"hsl(var(--secondary))\",\n \"hsl(var(--muted))\",\n ],\n ticks: 200,\n gravity: 1.2,\n decay: 0.94,\n startVelocity: 30,\n shapes: [\"circle\"],\n });\n }\n };\n\n return (\n
\n
\n

\n Simple, Transparent Pricing\n

\n

\n Choose the plan that works for you\n
\n All plans include access to our platform, lead generation tools, and\n dedicated support.\n

\n
\n\n
\n \n \n Annual billing (Save 20%)\n \n
\n\n
\n {[\n {\n name: \"STARTER\",\n price: \"50\",\n yearlyPrice: \"40\",\n period: \"per month\",\n features: [\n \"Up to 10 projects\",\n \"Basic analytics\",\n \"48-hour support response time\",\n \"Limited API access\",\n \"Community support\",\n ],\n description: \"Perfect for individuals and small projects\",\n buttonText: \"Start Free Trial\",\n href: \"/sign-up\",\n isPopular: false,\n },\n {\n name: \"PROFESSIONAL\",\n price: \"99\",\n yearlyPrice: \"79\",\n period: \"per month\",\n features: [\n \"Unlimited projects\",\n \"Advanced analytics\",\n \"24-hour support response time\",\n \"Full API access\",\n \"Priority support\",\n \"Team collaboration\",\n \"Custom integrations\",\n ],\n description: \"Ideal for growing teams and businesses\",\n buttonText: \"Get Started\",\n href: \"/sign-up\",\n isPopular: true,\n },\n {\n name: \"ENTERPRISE\",\n price: \"299\",\n yearlyPrice: \"239\",\n period: \"per month\",\n features: [\n \"Everything in Professional\",\n \"Custom solutions\",\n \"Dedicated account manager\",\n \"1-hour support response time\",\n \"SSO Authentication\",\n \"Advanced security\",\n \"Custom contracts\",\n \"SLA agreement\",\n ],\n description: \"For large organizations with specific needs\",\n buttonText: \"Contact Sales\",\n href: \"/contact\",\n isPopular: false,\n },\n ].map((plan, index) => (\n \n {plan.isPopular && (\n
\n \n \n Popular\n \n
\n )}\n
\n

\n {plan.name}\n

\n
\n \n `$${value}`}\n transformTiming={{\n duration: 500,\n easing: \"ease-out\",\n }}\n willChange\n className=\"font-variant-numeric: tabular-nums\"\n />\n \n {plan.period !== \"Next 3 months\" && (\n \n / {plan.period}\n \n )}\n
\n\n

\n {isMonthly ? \"billed monthly\" : \"billed annually\"}\n

\n\n
    \n {plan.features.map((feature, idx) => (\n
  • \n \n {feature}\n
  • \n ))}\n
\n\n
\n\n \n {plan.buttonText}\n \n

\n {plan.description}\n

\n
\n \n ))}\n
\n
\n );\n}\n" + "path": "components/prismui/pricing.tsx", + "type": "registry:ui", + "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport NumberFlow from \"@/components/prismui/number-flow\";\nimport confetti from \"canvas-confetti\";\n\ninterface PricingPlan {\n name: string;\n description: string;\n price: {\n monthly: number;\n yearly: number;\n };\n features: string[];\n isPopular?: boolean;\n buttonText?: string;\n buttonVariant?: \"default\" | \"outline\";\n}\n\ninterface PricingProps {\n title?: string;\n description?: string;\n plans: PricingPlan[];\n className?: string;\n}\n\nexport default function Pricing({\n title = \"Simple, transparent pricing\",\n description = \"Choose the plan that's right for you\",\n plans,\n className,\n}: PricingProps) {\n const [isYearly, setIsYearly] = useState(false);\n\n const handleToggle = () => {\n setIsYearly(!isYearly);\n if (!isYearly) {\n confetti({\n particleCount: 100,\n spread: 70,\n origin: { y: 0.6 },\n });\n }\n };\n\n return (\n
\n
\n
\n

\n {title}\n

\n

\n {description}\n

\n
\n Monthly\n \n Yearly\n {isYearly && (\n \n Save 20%\n \n )}\n
\n
\n
\n {plans.map((plan, index) => (\n \n \n {plan.isPopular && (\n \n Most Popular\n \n )}\n
\n

{plan.name}

\n

\n {plan.description}\n

\n
\n $\n \n \n \n \n /{isYearly ? \"year\" : \"month\"}\n \n
\n
    \n {plan.features.map((feature) => (\n \n \n {feature}\n \n ))}\n
\n
\n \n {plan.buttonText || \"Get Started\"}\n \n \n \n ))}\n
\n
\n
\n );\n}\n" } ], "dependencies": [ - "@/components/ui/button" + "framer-motion", + "canvas-confetti" ] } \ No newline at end of file diff --git a/src/components/prismui/pricing.tsx b/src/components/prismui/pricing.tsx new file mode 100644 index 0000000..1b0c756 --- /dev/null +++ b/src/components/prismui/pricing.tsx @@ -0,0 +1,142 @@ +"use client"; + +import { useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Switch } from "@/components/ui/switch"; +import { Check } from "lucide-react"; +import { cn } from "@/lib/utils"; +import NumberFlow from "@/components/prismui/number-flow"; +import confetti from "canvas-confetti"; + +interface PricingPlan { + name: string; + description: string; + price: { + monthly: number; + yearly: number; + }; + features: string[]; + isPopular?: boolean; + buttonText?: string; + buttonVariant?: "default" | "outline"; +} + +interface PricingProps { + title?: string; + description?: string; + plans: PricingPlan[]; + className?: string; +} + +export default function Pricing({ + title = "Simple, transparent pricing", + description = "Choose the plan that's right for you", + plans, + className, +}: PricingProps) { + const [isYearly, setIsYearly] = useState(false); + + const handleToggle = () => { + setIsYearly(!isYearly); + if (!isYearly) { + confetti({ + particleCount: 100, + spread: 70, + origin: { y: 0.6 }, + }); + } + }; + + return ( +
+
+
+

+ {title} +

+

+ {description} +

+
+ Monthly + + Yearly + {isYearly && ( + + Save 20% + + )} +
+
+
+ {plans.map((plan, index) => ( + + + {plan.isPopular && ( + + Most Popular + + )} +
+

{plan.name}

+

+ {plan.description} +

+
+ $ + + + + + /{isYearly ? "year" : "month"} + +
+
    + {plan.features.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+
+ +
+
+ ))} +
+
+
+ ); +} diff --git a/src/content/docs/sections/pricing.mdx b/src/content/docs/sections/pricing.mdx index f6f6ae4..138f0c9 100644 --- a/src/content/docs/sections/pricing.mdx +++ b/src/content/docs/sections/pricing.mdx @@ -6,7 +6,7 @@ author: codehagen slug: sections/pricing --- - + A modern pricing section with interactive features, smooth animations, and responsive design. Perfect for showcasing different pricing tiers with a dynamic monthly/yearly toggle. @@ -18,7 +18,43 @@ slug: sections/pricing - Interactive monthly/yearly pricing toggle with confetti effect - Responsive design with mobile-first approach - Dynamic price updates with NumberFlow animations -- Industry-specific content customization - Popular plan highlighting - Dark mode compatible +## Dependencies + +```json +{ + "dependencies": [ + "@/components/section", + "@/components/ui/button", + "@/components/ui/label", + "@/components/ui/switch", + "framer-motion", + "canvas-confetti", + "@number-flow/react" + ] +} +``` + +## Usage + +The Pricing section is designed to be used as a full-width section in your landing page or pricing page. It includes: + +- Animated price cards with popular plan highlighting +- Interactive monthly/yearly toggle with confetti effects +- Smooth price transitions using NumberFlow +- Responsive layout that adapts to all screen sizes +- Built-in dark mode support +- Accessible toggle controls and pricing information + +## Customization + +The section can be customized through your global Tailwind theme: + +- Colors through `primary`, `secondary`, and `muted` color tokens +- Typography through font family and size utilities +- Spacing and layout through padding and margin utilities +- Animations through Framer Motion configuration +- Card styles through border and shadow utilities + diff --git a/src/registry/example/pricing-basic.tsx b/src/registry/example/pricing-basic.tsx new file mode 100644 index 0000000..bcb206f --- /dev/null +++ b/src/registry/example/pricing-basic.tsx @@ -0,0 +1,507 @@ +"use client"; + +import Section from "@/components/section"; +import { buttonVariants } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import useWindowSize from "@/lib/hooks/use-window-size"; +import { cn } from "@/lib/utils"; +import { motion } from "framer-motion"; +import { Check } from "lucide-react"; +import Link from "next/link"; +import { useState, useRef } from "react"; +import { FaStar } from "react-icons/fa"; +import confetti from "canvas-confetti"; +import NumberFlow from "@number-flow/react"; + +export default function PricingBasic() { + const [isMonthly, setIsMonthly] = useState(true); + const { isDesktop } = useWindowSize(); + const switchRef = useRef(null); + + const handleToggle = (checked: boolean) => { + setIsMonthly(!checked); + if (checked && switchRef.current) { + const rect = switchRef.current.getBoundingClientRect(); + const x = rect.left + rect.width / 2; + const y = rect.top + rect.height / 2; + + confetti({ + particleCount: 50, + spread: 60, + origin: { + x: x / window.innerWidth, + y: y / window.innerHeight, + }, + colors: [ + "hsl(var(--primary))", + "hsl(var(--accent))", + "hsl(var(--secondary))", + "hsl(var(--muted))", + ], + ticks: 200, + gravity: 1.2, + decay: 0.94, + startVelocity: 30, + shapes: ["circle"], + }); + } + }; + + return ( +
+
+

+ Simple, Transparent Pricing +

+

+ Choose the plan that works for you +
+ All plans include access to our platform, lead generation tools, and + dedicated support. +

+
+ +
+ + + Annual billing (Save 20%) + +
+ +
+ {[ + { + name: "STARTER", + price: "50", + yearlyPrice: "40", + period: "per month", + features: [ + "Up to 10 projects", + "Basic analytics", + "48-hour support response time", + "Limited API access", + "Community support", + ], + description: "Perfect for individuals and small projects", + buttonText: "Start Free Trial", + href: "/sign-up", + isPopular: false, + }, + { + name: "PROFESSIONAL", + price: "99", + yearlyPrice: "79", + period: "per month", + features: [ + "Unlimited projects", + "Advanced analytics", + "24-hour support response time", + "Full API access", + "Priority support", + "Team collaboration", + "Custom integrations", + ], + description: "Ideal for growing teams and businesses", + buttonText: "Get Started", + href: "/sign-up", + isPopular: true, + }, + { + name: "ENTERPRISE", + price: "299", + yearlyPrice: "239", + period: "per month", + features: [ + "Everything in Professional", + "Custom solutions", + "Dedicated account manager", + "1-hour support response time", + "SSO Authentication", + "Advanced security", + "Custom contracts", + "SLA agreement", + ], + description: "For large organizations with specific needs", + buttonText: "Contact Sales", + href: "/contact", + isPopular: false, + }, + ].map((plan, index) => ( + + {plan.isPopular && ( +
+ + + Popular + +
+ )} +
+

+ {plan.name} +

+
+ + `$${value}`} + transformTiming={{ + duration: 500, + easing: "ease-out", + }} + willChange + className="font-variant-numeric: tabular-nums" + /> + + {plan.period !== "Next 3 months" && ( + + / {plan.period} + + )} +
+ +

+ {isMonthly ? "billed monthly" : "billed annually"} +

+ +
    + {plan.features.map((feature, idx) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ + + {plan.buttonText} + +

+ {plan.description} +

+
+
+ ))} +
+
+ ); +} + +export const demoSource = `"use client"; + +import Section from "@/components/section"; +import { buttonVariants } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import useWindowSize from "@/lib/hooks/use-window-size"; +import { cn } from "@/lib/utils"; +import { motion } from "framer-motion"; +import { Check } from "lucide-react"; +import Link from "next/link"; +import { useState, useRef } from "react"; +import { FaStar } from "react-icons/fa"; +import confetti from "canvas-confetti"; +import NumberFlow from "@number-flow/react"; + +export default function PricingBasic() { + const [isMonthly, setIsMonthly] = useState(true); + const { isDesktop } = useWindowSize(); + const switchRef = useRef(null); + + const handleToggle = (checked: boolean) => { + setIsMonthly(!checked); + if (checked && switchRef.current) { + const rect = switchRef.current.getBoundingClientRect(); + const x = rect.left + rect.width / 2; + const y = rect.top + rect.height / 2; + + confetti({ + particleCount: 50, + spread: 60, + origin: { + x: x / window.innerWidth, + y: y / window.innerHeight, + }, + colors: [ + "hsl(var(--primary))", + "hsl(var(--accent))", + "hsl(var(--secondary))", + "hsl(var(--muted))", + ], + ticks: 200, + gravity: 1.2, + decay: 0.94, + startVelocity: 30, + shapes: ["circle"], + }); + } + }; + + return ( +
+
+

+ Simple, Transparent Pricing +

+

+ Choose the plan that works for you +
+ All plans include access to our platform, lead generation tools, and + dedicated support. +

+
+ +
+ + + Annual billing (Save 20%) + +
+ +
+ {[ + { + name: "STARTER", + price: "50", + yearlyPrice: "40", + period: "per month", + features: [ + "Up to 10 projects", + "Basic analytics", + "48-hour support response time", + "Limited API access", + "Community support", + ], + description: "Perfect for individuals and small projects", + buttonText: "Start Free Trial", + href: "/sign-up", + isPopular: false, + }, + { + name: "PROFESSIONAL", + price: "99", + yearlyPrice: "79", + period: "per month", + features: [ + "Unlimited projects", + "Advanced analytics", + "24-hour support response time", + "Full API access", + "Priority support", + "Team collaboration", + "Custom integrations", + ], + description: "Ideal for growing teams and businesses", + buttonText: "Get Started", + href: "/sign-up", + isPopular: true, + }, + { + name: "ENTERPRISE", + price: "299", + yearlyPrice: "239", + period: "per month", + features: [ + "Everything in Professional", + "Custom solutions", + "Dedicated account manager", + "1-hour support response time", + "SSO Authentication", + "Advanced security", + "Custom contracts", + "SLA agreement", + ], + description: "For large organizations with specific needs", + buttonText: "Contact Sales", + href: "/contact", + isPopular: false, + }, + ].map((plan, index) => ( + + {plan.isPopular && ( +
+ + + Popular + +
+ )} +
+

+ {plan.name} +

+
+ + \`$\${value}\`} + transformTiming={{ + duration: 500, + easing: "ease-out", + }} + willChange + className="font-variant-numeric: tabular-nums" + /> + + {plan.period !== "Next 3 months" && ( + + / {plan.period} + + )} +
+ +

+ {isMonthly ? "billed monthly" : "billed annually"} +

+ +
    + {plan.features.map((feature, idx) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ + + {plan.buttonText} + +

+ {plan.description} +

+
+
+ ))} +
+
+ ); +}`; diff --git a/src/registry/registry-components.ts b/src/registry/registry-components.ts index 49fd010..b0e95c9 100644 --- a/src/registry/registry-components.ts +++ b/src/registry/registry-components.ts @@ -2286,4 +2286,27 @@ export function PopoverButton({ }, dependencies: ["framer-motion"], }, + { + name: "pricing", + type: "registry:ui", + category: "sections", + subcategory: "marketing", + code: `"use client"; + +// ... (paste the entire component code here) +`, + files: [ + { + path: "components/prismui/pricing.tsx", + type: "registry:ui", + }, + ], + cli: { + npm: 'npx shadcn@latest add "https://www.prismui.tech/r/styles/default/pricing.json"', + pnpm: 'pnpm dlx shadcn@latest add "https://www.prismui.tech/r/styles/default/pricing.json"', + yarn: 'yarn dlx shadcn@latest add "https://www.prismui.tech/r/styles/default/pricing.json"', + bun: 'bunx shadcn@latest add "https://www.prismui.tech/r/styles/default/pricing.json"', + }, + dependencies: ["framer-motion", "canvas-confetti"], + }, ]; diff --git a/src/registry/registry-examples.ts b/src/registry/registry-examples.ts index 0d6730d..dd7d8bc 100644 --- a/src/registry/registry-examples.ts +++ b/src/registry/registry-examples.ts @@ -104,6 +104,9 @@ import PopoverForm, { import PopoverProject, { demoSource as PopoverProjectSource, } from "./example/popover-project"; +import PricingBasic, { + demoSource as PricingBasicSource, +} from "./example/pricing-basic"; export const examples: RegistryItem[] = [ { @@ -382,4 +385,19 @@ export const examples: RegistryItem[] = [ "lucide-react", ], }, + { + name: "pricing-basic", + type: "examples", + component: PricingBasic, + code: PricingBasicSource, + dependencies: [ + "@/components/section", + "@/components/ui/button", + "@/components/ui/label", + "@/components/ui/switch", + "framer-motion", + "canvas-confetti", + "@number-flow/react", + ], + }, ];