@ugurdemirel/landcraft 0.1.5 → 0.1.9

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.
Files changed (138) hide show
  1. package/package.json +4 -4
  2. package/src/components/Badge.test.tsx +0 -55
  3. package/src/components/Badge.tsx +0 -68
  4. package/src/components/Blog/Blog.test.tsx +0 -64
  5. package/src/components/Blog/BlogCard.tsx +0 -12
  6. package/src/components/Blog/BlogSection.tsx +0 -95
  7. package/src/components/Blog/index.ts +0 -8
  8. package/src/components/Blog/parts.tsx +0 -38
  9. package/src/components/Blog/types.ts +0 -42
  10. package/src/components/Blog/variants/Card.tsx +0 -62
  11. package/src/components/Blog/variants/Row.tsx +0 -41
  12. package/src/components/Button.test.tsx +0 -91
  13. package/src/components/Button.tsx +0 -113
  14. package/src/components/CTA/CTA.test.tsx +0 -53
  15. package/src/components/CTA/CTA.tsx +0 -12
  16. package/src/components/CTA/index.ts +0 -2
  17. package/src/components/CTA/parts.tsx +0 -26
  18. package/src/components/CTA/types.ts +0 -12
  19. package/src/components/CTA/variants/Inverse.tsx +0 -50
  20. package/src/components/CTA/variants/Panel.tsx +0 -60
  21. package/src/components/CTA/variants/Surface.tsx +0 -34
  22. package/src/components/Card.test.tsx +0 -49
  23. package/src/components/Card.tsx +0 -80
  24. package/src/components/Container.test.tsx +0 -52
  25. package/src/components/Container.tsx +0 -86
  26. package/src/components/FAQ/FAQ.test.tsx +0 -54
  27. package/src/components/FAQ/FAQ.tsx +0 -48
  28. package/src/components/FAQ/index.ts +0 -2
  29. package/src/components/FAQ/parts.tsx +0 -74
  30. package/src/components/FAQ/types.ts +0 -18
  31. package/src/components/FAQ/variants/Accordion.tsx +0 -46
  32. package/src/components/FAQ/variants/Cards.tsx +0 -40
  33. package/src/components/FAQ/variants/Split.tsx +0 -44
  34. package/src/components/Features/FeatureCard.tsx +0 -44
  35. package/src/components/Features/FeatureGrid.tsx +0 -14
  36. package/src/components/Features/Features.test.tsx +0 -49
  37. package/src/components/Features/index.ts +0 -3
  38. package/src/components/Features/parts.ts +0 -15
  39. package/src/components/Features/types.ts +0 -25
  40. package/src/components/Features/variants/Bento.tsx +0 -32
  41. package/src/components/Features/variants/Columns.tsx +0 -21
  42. package/src/components/Features/variants/EditorialRows.tsx +0 -38
  43. package/src/components/Footer/Footer.test.tsx +0 -49
  44. package/src/components/Footer/Footer.tsx +0 -11
  45. package/src/components/Footer/index.ts +0 -2
  46. package/src/components/Footer/parts.tsx +0 -101
  47. package/src/components/Footer/types.ts +0 -32
  48. package/src/components/Footer/variants/Classic.tsx +0 -59
  49. package/src/components/Footer/variants/Editorial.tsx +0 -47
  50. package/src/components/Footer/variants/Minimal.tsx +0 -33
  51. package/src/components/Hero/Hero.test.tsx +0 -71
  52. package/src/components/Hero/Hero.tsx +0 -12
  53. package/src/components/Hero/index.ts +0 -2
  54. package/src/components/Hero/parts.tsx +0 -29
  55. package/src/components/Hero/types.ts +0 -20
  56. package/src/components/Hero/variants/Centered.tsx +0 -54
  57. package/src/components/Hero/variants/Split.tsx +0 -53
  58. package/src/components/Hero/variants/Statement.tsx +0 -60
  59. package/src/components/LanguageSwitcher/LanguageSwitcher.test.tsx +0 -64
  60. package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +0 -37
  61. package/src/components/LanguageSwitcher/index.ts +0 -6
  62. package/src/components/LanguageSwitcher/parts.tsx +0 -144
  63. package/src/components/LanguageSwitcher/types.ts +0 -40
  64. package/src/components/LanguageSwitcher/useLanguageSwitcher.ts +0 -23
  65. package/src/components/LanguageSwitcher/variants/Dropdown.tsx +0 -88
  66. package/src/components/LanguageSwitcher/variants/Modal.tsx +0 -56
  67. package/src/components/LogoCloud/LogoCloud.test.tsx +0 -35
  68. package/src/components/LogoCloud/LogoCloud.tsx +0 -14
  69. package/src/components/LogoCloud/index.ts +0 -2
  70. package/src/components/LogoCloud/parts.tsx +0 -32
  71. package/src/components/LogoCloud/types.ts +0 -16
  72. package/src/components/LogoCloud/variants/Marquee.tsx +0 -30
  73. package/src/components/LogoCloud/variants/Quiet.tsx +0 -26
  74. package/src/components/LogoCloud/variants/Strip.tsx +0 -25
  75. package/src/components/MegaMenu/MegaMenu.test.tsx +0 -89
  76. package/src/components/MegaMenu/MegaMenu.tsx +0 -454
  77. package/src/components/MegaMenu/index.ts +0 -9
  78. package/src/components/MegaMenu/types.ts +0 -56
  79. package/src/components/Modal.test.tsx +0 -80
  80. package/src/components/Modal.tsx +0 -125
  81. package/src/components/Navbar/Navbar.test.tsx +0 -91
  82. package/src/components/Navbar/Navbar.tsx +0 -185
  83. package/src/components/Navbar/index.ts +0 -2
  84. package/src/components/Navbar/types.ts +0 -30
  85. package/src/components/Newsletter/Newsletter.test.tsx +0 -45
  86. package/src/components/Newsletter/Newsletter.tsx +0 -12
  87. package/src/components/Newsletter/index.ts +0 -2
  88. package/src/components/Newsletter/parts.tsx +0 -23
  89. package/src/components/Newsletter/types.ts +0 -12
  90. package/src/components/Newsletter/useNewsletterForm.ts +0 -27
  91. package/src/components/Newsletter/variants/Card.tsx +0 -62
  92. package/src/components/Newsletter/variants/Inline.tsx +0 -54
  93. package/src/components/Newsletter/variants/Underline.tsx +0 -52
  94. package/src/components/Pricing/Pricing.test.tsx +0 -67
  95. package/src/components/Pricing/Pricing.tsx +0 -37
  96. package/src/components/Pricing/index.ts +0 -2
  97. package/src/components/Pricing/parts.tsx +0 -132
  98. package/src/components/Pricing/types.ts +0 -28
  99. package/src/components/Pricing/variants/Bento.tsx +0 -72
  100. package/src/components/Pricing/variants/Cards.tsx +0 -79
  101. package/src/components/Pricing/variants/Compact.tsx +0 -69
  102. package/src/components/Prose.test.tsx +0 -49
  103. package/src/components/Prose.tsx +0 -55
  104. package/src/components/Section.test.tsx +0 -51
  105. package/src/components/Section.tsx +0 -106
  106. package/src/components/Slot.test.tsx +0 -45
  107. package/src/components/Slot.tsx +0 -78
  108. package/src/components/Stats/Stats.test.tsx +0 -47
  109. package/src/components/Stats/Stats.tsx +0 -16
  110. package/src/components/Stats/index.ts +0 -2
  111. package/src/components/Stats/parts.tsx +0 -29
  112. package/src/components/Stats/types.ts +0 -23
  113. package/src/components/Stats/variants/Cells.tsx +0 -34
  114. package/src/components/Stats/variants/Editorial.tsx +0 -41
  115. package/src/components/Stats/variants/Hairline.tsx +0 -31
  116. package/src/components/Stats/variants/Ticker.tsx +0 -36
  117. package/src/components/Testimonials/TestimonialCard.tsx +0 -61
  118. package/src/components/Testimonials/Testimonials.test.tsx +0 -48
  119. package/src/components/Testimonials/Testimonials.tsx +0 -14
  120. package/src/components/Testimonials/index.ts +0 -3
  121. package/src/components/Testimonials/parts.ts +0 -7
  122. package/src/components/Testimonials/types.ts +0 -24
  123. package/src/components/Testimonials/variants/Carousel.tsx +0 -94
  124. package/src/components/Testimonials/variants/Grid.tsx +0 -19
  125. package/src/components/Testimonials/variants/Marquee.tsx +0 -28
  126. package/src/icons.tsx +0 -290
  127. package/src/index.ts +0 -120
  128. package/src/styles/index.css +0 -18
  129. package/src/test/setup.ts +0 -7
  130. package/src/utils/cn.test.ts +0 -21
  131. package/src/utils/cn.ts +0 -6
  132. package/src/utils/contrast.test.ts +0 -71
  133. package/src/utils/contrast.ts +0 -107
  134. package/src/utils/iso2Flag.test.ts +0 -26
  135. package/src/utils/iso2Flag.ts +0 -26
  136. package/src/utils/useTokenForeground.test.ts +0 -50
  137. package/src/utils/useTokenForeground.ts +0 -39
  138. /package/{src/styles → dist}/theme.css +0 -0
@@ -1,54 +0,0 @@
1
- "use client";
2
- import { cn } from "../../../utils/cn";
3
- import { ArrowRight } from "../../../icons";
4
- import { useNewsletterForm } from "../useNewsletterForm";
5
- import { inputBase, NewsletterSuccess, NewsletterNote } from "../parts";
6
- import type { NewsletterProps } from "../types";
7
-
8
- interface InlineProps extends Omit<NewsletterProps, "option"> {}
9
-
10
- export function NewsletterInline({
11
- className,
12
- placeholder = "you@company.com",
13
- buttonLabel = "Join",
14
- successMessage = "You’re on the list!",
15
- note,
16
- onSubmit,
17
- ...props
18
- }: InlineProps) {
19
- const { email, done, error, handleSubmit, updateEmail } = useNewsletterForm(onSubmit);
20
-
21
- return (
22
- <form onSubmit={handleSubmit} className={cn("w-full", className)} {...props}>
23
- {done ? (
24
- <NewsletterSuccess message={successMessage} />
25
- ) : (
26
- <>
27
- <label htmlFor="newsletter-email" className="sr-only">
28
- Your email address
29
- </label>
30
- <div className="flex flex-col gap-3 sm:flex-row">
31
- <input
32
- id="newsletter-email"
33
- type="email"
34
- autoComplete="email"
35
- value={email}
36
- onChange={(e) => updateEmail(e.target.value)}
37
- placeholder={placeholder}
38
- className={cn(inputBase, "h-11")}
39
- />
40
- <button
41
- type="submit"
42
- className="inline-flex h-11 shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg bg-primary px-5 text-sm font-semibold text-on-primary transition-colors duration-200 hover:bg-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
43
- >
44
- {buttonLabel}
45
- <ArrowRight className="h-4 w-4 opacity-60" />
46
- </button>
47
- </div>
48
- <NewsletterNote error={error} note={note} />
49
- </>
50
- )}
51
- </form>
52
- );
53
- }
54
- NewsletterInline.displayName = "NewsletterInline";
@@ -1,52 +0,0 @@
1
- "use client";
2
- import { cn } from "../../../utils/cn";
3
- import { ArrowRight } from "../../../icons";
4
- import { useNewsletterForm } from "../useNewsletterForm";
5
- import { NewsletterSuccess, NewsletterNote } from "../parts";
6
- import type { NewsletterProps } from "../types";
7
-
8
- interface UnderlineProps extends Omit<NewsletterProps, "option"> {}
9
-
10
- export function NewsletterUnderline({
11
- className,
12
- placeholder = "you@company.com",
13
- buttonLabel = "Join",
14
- successMessage = "You’re on the list!",
15
- note,
16
- onSubmit,
17
- ...props
18
- }: UnderlineProps) {
19
- const { email, done, error, handleSubmit, updateEmail } = useNewsletterForm(onSubmit);
20
-
21
- return (
22
- <form onSubmit={handleSubmit} className={cn("w-full", className)} {...props}>
23
- {done ? (
24
- <NewsletterSuccess message={successMessage} />
25
- ) : (
26
- <div className="flex items-end gap-3 border-b border-foreground/20 pb-3 transition-colors duration-200 focus-within:border-foreground">
27
- <label htmlFor="newsletter-email" className="sr-only">
28
- Your email address
29
- </label>
30
- <input
31
- id="newsletter-email"
32
- type="email"
33
- autoComplete="email"
34
- value={email}
35
- onChange={(e) => updateEmail(e.target.value)}
36
- placeholder={placeholder}
37
- className="w-full bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
38
- />
39
- <button
40
- type="submit"
41
- className="inline-flex shrink-0 cursor-pointer items-center gap-1.5 text-sm font-semibold text-foreground transition-colors duration-150 hover:text-primary"
42
- >
43
- {buttonLabel}
44
- <ArrowRight className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5" />
45
- </button>
46
- </div>
47
- )}
48
- <NewsletterNote error={error} note={note} />
49
- </form>
50
- );
51
- }
52
- NewsletterUnderline.displayName = "NewsletterUnderline";
@@ -1,67 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { render, screen, fireEvent } from "@testing-library/react";
3
- import { Pricing } from "./Pricing";
4
-
5
- const plans = [
6
- {
7
- name: "Starter",
8
- monthly: 9,
9
- yearly: 90,
10
- features: ["1 project"],
11
- cta: "Get started",
12
- },
13
- {
14
- name: "Pro",
15
- monthly: 29,
16
- yearly: 290,
17
- highlighted: true,
18
- features: ["Unlimited projects"],
19
- cta: "Go Pro",
20
- },
21
- ];
22
-
23
- describe("Pricing", () => {
24
- it("renders plan names, prices and the cards option by default", () => {
25
- render(<Pricing plans={plans} />);
26
- expect(screen.getByText("Starter")).toBeInTheDocument();
27
- expect(screen.getByText("Pro")).toBeInTheDocument();
28
- // Monthly price for the starter plan is $9 (default billing = monthly).
29
- expect(screen.getByText("$9")).toBeInTheDocument();
30
- });
31
-
32
- it("switches between monthly and yearly billing via the toggle", () => {
33
- render(<Pricing plans={[plans[0]]} />);
34
- expect(screen.getByText("$9")).toBeInTheDocument();
35
-
36
- fireEvent.click(screen.getByRole("switch"));
37
- expect(screen.getByText("$90")).toBeInTheDocument();
38
- });
39
-
40
- it("renders the compact option", () => {
41
- render(<Pricing plans={plans} option="compact" />);
42
- expect(screen.getByText("Starter")).toBeInTheDocument();
43
- });
44
-
45
- it("renders the bento option", () => {
46
- render(<Pricing plans={plans} option="bento" />);
47
- expect(screen.getByText("Starter")).toBeInTheDocument();
48
- });
49
-
50
- it("calls onSelect with the plan and current billing when a plan button is pressed", () => {
51
- const onSelect = vi.fn();
52
- render(<Pricing plans={[plans[0]]} onSelect={onSelect} />);
53
- fireEvent.click(screen.getByRole("button", { name: "Get started" }));
54
- expect(onSelect).toHaveBeenCalledWith(plans[0], "monthly");
55
- });
56
-
57
- it("renders a price label for custom (null) pricing", () => {
58
- render(
59
- <Pricing
60
- plans={[
61
- { name: "Enterprise", monthly: null, yearly: null, features: [], cta: "Contact" },
62
- ]}
63
- />,
64
- );
65
- expect(screen.getByText("Custom")).toBeInTheDocument();
66
- });
67
- });
@@ -1,37 +0,0 @@
1
- "use client";
2
- import { useState } from "react";
3
- import type { PricingProps } from "./types";
4
- import { PricingCards } from "./variants/Cards";
5
- import { PricingBento } from "./variants/Bento";
6
- import { PricingCompact } from "./variants/Compact";
7
-
8
- export const Pricing = ({
9
- className,
10
- plans,
11
- option = "cards",
12
- defaultBilling = "monthly",
13
- onSelect,
14
- ...props
15
- }: PricingProps) => {
16
- const [billing, setBilling] = useState<"monthly" | "yearly">(defaultBilling);
17
-
18
- if (option === "compact") {
19
- return <PricingCompact className={className} plans={plans} billing={billing} onSelect={onSelect} {...props} />;
20
- }
21
-
22
- if (option === "bento") {
23
- return <PricingBento className={className} plans={plans} billing={billing} onSelect={onSelect} {...props} />;
24
- }
25
-
26
- return (
27
- <PricingCards
28
- className={className}
29
- plans={plans}
30
- billing={billing}
31
- onSelect={onSelect}
32
- onBillingChange={setBilling}
33
- {...props}
34
- />
35
- );
36
- };
37
- Pricing.displayName = "Pricing";
@@ -1,2 +0,0 @@
1
- export { Pricing } from "./Pricing";
2
- export type { PricingProps, PricingOption, Plan } from "./types";
@@ -1,132 +0,0 @@
1
- "use client";
2
- import { cn } from "../../utils/cn";
3
- import { getContrastText } from "../../utils/contrast";
4
- import { useTokenForeground } from "../../utils/useTokenForeground";
5
- import { ArrowRight } from "../../icons";
6
- import type { Plan } from "./types";
7
-
8
- export function PriceValue({
9
- value,
10
- billing,
11
- dim,
12
- }: {
13
- value: number | null;
14
- billing: "monthly" | "yearly";
15
- /** False when rendered on a colored background where opacity would kill contrast. */
16
- dim?: boolean;
17
- }) {
18
- if (value === null) {
19
- return <span>Custom</span>;
20
- }
21
- return (
22
- <>
23
- <span className="text-[2.6rem] font-semibold tracking-tight sm:text-[3rem]">${value}</span>
24
- <span className={cn("text-sm font-normal", dim && "opacity-60")}>
25
- /{billing === "monthly" ? "mo" : "yr"}
26
- </span>
27
- </>
28
- );
29
- }
30
-
31
- export function BillingToggle({
32
- billing,
33
- onChange,
34
- dark,
35
- }: {
36
- billing: "monthly" | "yearly";
37
- onChange: (b: "monthly" | "yearly") => void;
38
- dark?: boolean;
39
- }) {
40
- // The accent token is a background color, but its shipped "on-accent"
41
- // pairing isn't guaranteed to be contrast-safe (e.g. white on amber).
42
- // Derive the badge text color from the actual accent value instead.
43
- const accentText = useTokenForeground("--color-accent");
44
- return (
45
- <div className="mb-10 flex items-center justify-center gap-4">
46
- <span
47
- className={cn(
48
- "text-sm font-medium transition-colors",
49
- billing === "monthly" ? (dark ? "text-white" : "text-foreground") : "text-muted-foreground",
50
- )}
51
- >
52
- Monthly
53
- </span>
54
- <button
55
- type="button"
56
- role="switch"
57
- aria-checked={billing === "yearly"}
58
- aria-label="Billing period"
59
- onClick={() => onChange(billing === "monthly" ? "yearly" : "monthly")}
60
- className={cn(
61
- "relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full transition-colors duration-200",
62
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
63
- billing === "yearly" ? "bg-primary" : "bg-border",
64
- )}
65
- >
66
- <span
67
- className={cn(
68
- "inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform duration-200",
69
- billing === "yearly" ? "translate-x-6" : "translate-x-1",
70
- )}
71
- />
72
- </button>
73
- <span
74
- className={cn(
75
- "flex items-center gap-2 text-sm font-medium transition-colors",
76
- billing === "yearly" ? (dark ? "text-white" : "text-foreground") : "text-muted-foreground",
77
- )}
78
- >
79
- Yearly
80
- <span
81
- className="rounded-full bg-accent px-2 py-0.5 text-xs font-semibold"
82
- style={{ color: accentText }}
83
- >
84
- −2 ay
85
- </span>
86
- </span>
87
- </div>
88
- );
89
- }
90
-
91
- export function PlanButton({
92
- plan,
93
- billing,
94
- onSelect,
95
- dark,
96
- }: {
97
- plan: Plan;
98
- billing: "monthly" | "yearly";
99
- onSelect?: (plan: Plan, billing: "monthly" | "yearly") => void;
100
- dark?: boolean;
101
- }) {
102
- const isHighlighted = plan.highlighted && !plan.customColor;
103
- const fg = plan.customColor ? getContrastText(plan.customColor, "#111111", "#ffffff") : undefined;
104
- return (
105
- <button
106
- type="button"
107
- onClick={() => onSelect?.(plan, billing)}
108
- className={cn(
109
- "inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-lg px-5 text-sm font-semibold transition-colors duration-200",
110
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
111
- isHighlighted || plan.customColor
112
- ? "hover:brightness-95"
113
- : dark
114
- ? "bg-white text-black hover:bg-white/90"
115
- : "border border-border text-foreground hover:bg-surface",
116
- )}
117
- style={
118
- plan.customColor
119
- ? { backgroundColor: plan.customColor, color: fg }
120
- : isHighlighted
121
- ? {
122
- backgroundColor: "rgb(var(--color-primary))",
123
- color: "rgb(var(--color-on-primary))",
124
- }
125
- : undefined
126
- }
127
- >
128
- {plan.cta || "Start"}
129
- <ArrowRight className="h-4 w-4 opacity-60" />
130
- </button>
131
- );
132
- }
@@ -1,28 +0,0 @@
1
- import type { HTMLAttributes } from "react";
2
-
3
- export type PricingOption = "cards" | "bento" | "compact";
4
-
5
- export interface Plan {
6
- name: string;
7
- description?: string;
8
- monthly: number | null;
9
- yearly: number | null;
10
- features: string[];
11
- cta?: string;
12
- href?: string;
13
- highlighted?: boolean;
14
- customColor?: string;
15
- }
16
-
17
- export interface PricingProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {
18
- plans: Plan[];
19
- option?: PricingOption;
20
- defaultBilling?: "monthly" | "yearly";
21
- onSelect?: (plan: Plan, billing: "monthly" | "yearly") => void;
22
- }
23
-
24
- export interface PricingVariantProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {
25
- plans: Plan[];
26
- billing: "monthly" | "yearly";
27
- onSelect?: (plan: Plan, billing: "monthly" | "yearly") => void;
28
- }
@@ -1,72 +0,0 @@
1
- import { cn } from "../../../utils/cn";
2
- import { getContrastText } from "../../../utils/contrast";
3
- import { PriceValue, PlanButton } from "../parts";
4
- import { Check } from "../../../icons";
5
- import type { PricingVariantProps } from "../types";
6
-
7
- interface BentoProps extends PricingVariantProps {
8
- className?: string;
9
- }
10
-
11
- export const PricingBento = ({ className, plans, billing, onSelect, ...props }: BentoProps) => {
12
- return (
13
- <div className={cn("w-full", className)} {...props}>
14
- <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
15
- {plans.map((plan) => {
16
- const isHighlighted = plan.highlighted && !plan.customColor;
17
- const bg = plan.customColor || (isHighlighted ? "rgb(var(--color-primary))" : undefined);
18
- const fg = plan.customColor
19
- ? getContrastText(plan.customColor, "#111111", "#ffffff")
20
- : isHighlighted
21
- ? "rgb(var(--color-on-primary))"
22
- : undefined;
23
- return (
24
- <div
25
- key={plan.name}
26
- className={cn(
27
- "flex h-full flex-col rounded-xl p-8",
28
- bg
29
- ? "lg:col-span-2"
30
- : "border border-border bg-surface",
31
- )}
32
- style={bg ? { backgroundColor: bg, color: fg } : undefined}
33
- >
34
- <div className="flex flex-wrap items-start justify-between gap-4">
35
- <div>
36
- <h3 className={cn("font-display text-xl font-semibold tracking-tight", bg ? "text-current" : "text-foreground")}>
37
- {plan.name}
38
- </h3>
39
- {plan.description ? (
40
- <p className={cn("mt-1.5 text-sm", bg ? undefined : "text-muted-foreground")}>
41
- {plan.description}
42
- </p>
43
- ) : null}
44
- </div>
45
- <div className={cn("font-display", bg ? "text-current" : "text-foreground")}>
46
- <PriceValue value={billing === "monthly" ? plan.monthly : plan.yearly} billing={billing} dim={!bg} />
47
- </div>
48
- </div>
49
- <ul
50
- className={cn(
51
- "mt-7 grid grid-cols-1 gap-x-8 gap-y-2.5 text-sm sm:grid-cols-2",
52
- bg ? "lg:max-w-2xl" : undefined,
53
- )}
54
- >
55
- {plan.features.map((feature) => (
56
- <li key={feature} className={cn("flex items-start gap-2.5", bg ? "text-current" : "text-muted-foreground")}>
57
- <Check className={cn("mt-0.5 h-4 w-4 shrink-0", bg ? "text-current" : "text-primary")} />
58
- {feature}
59
- </li>
60
- ))}
61
- </ul>
62
- <div className="mt-8">
63
- <PlanButton plan={plan} billing={billing} onSelect={onSelect} dark={!bg} />
64
- </div>
65
- </div>
66
- );
67
- })}
68
- </div>
69
- </div>
70
- );
71
- };
72
- PricingBento.displayName = "PricingBento";
@@ -1,79 +0,0 @@
1
- import { cn } from "../../../utils/cn";
2
- import { getContrastText } from "../../../utils/contrast";
3
- import { PriceValue, BillingToggle, PlanButton } from "../parts";
4
- import { Check } from "../../../icons";
5
- import type { PricingVariantProps } from "../types";
6
-
7
- interface CardsProps extends PricingVariantProps {
8
- className?: string;
9
- onBillingChange: (billing: "monthly" | "yearly") => void;
10
- }
11
-
12
- export const PricingCards = ({ className, plans, billing, onSelect, onBillingChange, ...props }: CardsProps) => {
13
- return (
14
- <div className={cn("w-full", className)} {...props}>
15
- <BillingToggle billing={billing} onChange={onBillingChange} />
16
- <div className="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3">
17
- {plans.map((plan) => {
18
- const isHighlighted = plan.highlighted && !plan.customColor;
19
- const bg = plan.customColor || (isHighlighted ? "rgb(var(--color-primary))" : undefined);
20
- const fg = plan.customColor
21
- ? getContrastText(plan.customColor, "#111111", "#ffffff")
22
- : isHighlighted
23
- ? "rgb(var(--color-on-primary))"
24
- : undefined;
25
- return (
26
- <div
27
- key={plan.name}
28
- className={cn(
29
- "relative flex flex-col rounded-xl p-7",
30
- bg ? "shadow-raised" : "border border-border bg-surface",
31
- !bg && isHighlighted && "border-primary",
32
- )}
33
- style={bg ? { backgroundColor: bg, color: fg } : undefined}
34
- >
35
- {isHighlighted ? (
36
- <span
37
- className={cn(
38
- "absolute -top-3 left-7 rounded-full px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wider",
39
- !bg && "bg-primary text-on-primary",
40
- )}
41
- style={
42
- bg
43
- ? { backgroundColor: "rgb(var(--color-on-primary))", color: "rgb(var(--color-primary))" }
44
- : undefined
45
- }
46
- >
47
- Most popular
48
- </span>
49
- ) : null}
50
- <h3 className={cn("font-display text-lg font-semibold tracking-tight", bg ? "text-current" : "text-foreground")}>
51
- {plan.name}
52
- </h3>
53
- {plan.description ? (
54
- <p className={cn("mt-1.5 text-sm", bg ? undefined : "text-muted-foreground")}>
55
- {plan.description}
56
- </p>
57
- ) : null}
58
- <div className={cn("mt-6 flex items-baseline font-display", bg ? "text-current" : "text-foreground")}>
59
- <PriceValue value={billing === "monthly" ? plan.monthly : plan.yearly} billing={billing} dim={!bg} />
60
- </div>
61
- <ul className={cn("mt-7 flex-1 space-y-2.5 text-sm", bg ? "text-current" : "text-muted-foreground")}>
62
- {plan.features.map((feature) => (
63
- <li key={feature} className="flex items-start gap-2.5">
64
- <Check className={cn("mt-0.5 h-4 w-4 shrink-0", bg ? "text-current" : "text-primary")} />
65
- {feature}
66
- </li>
67
- ))}
68
- </ul>
69
- <div className="mt-8">
70
- <PlanButton plan={plan} billing={billing} onSelect={onSelect} dark={!bg} />
71
- </div>
72
- </div>
73
- );
74
- })}
75
- </div>
76
- </div>
77
- );
78
- };
79
- PricingCards.displayName = "PricingCards";
@@ -1,69 +0,0 @@
1
- import { cn } from "../../../utils/cn";
2
- import { PlanButton } from "../parts";
3
- import { Check } from "../../../icons";
4
- import type { PricingVariantProps } from "../types";
5
-
6
- interface CompactProps extends PricingVariantProps {
7
- className?: string;
8
- }
9
-
10
- export const PricingCompact = ({ className, plans, billing, onSelect, ...props }: CompactProps) => {
11
- const price = (v: number | null) => (v === null ? "Custom" : `$${v}`);
12
-
13
- return (
14
- <div className={cn("w-full", className)} {...props}>
15
- <div className="divide-y divide-border overflow-hidden rounded-xl border border-border bg-surface">
16
- {plans.map((plan) => {
17
- const value = billing === "monthly" ? plan.monthly : plan.yearly;
18
- const isHighlighted = plan.highlighted && !plan.customColor;
19
- const bg = plan.customColor ? "rgb(var(--color-surface))" : isHighlighted ? "rgb(var(--color-primary-soft))" : undefined;
20
- const fg = plan.customColor ? plan.customColor : isHighlighted ? "rgb(var(--color-primary))" : undefined;
21
- return (
22
- <div
23
- key={plan.name}
24
- className={cn(
25
- "group flex flex-col gap-5 p-5 transition-colors duration-200 hover:bg-surface-strong sm:flex-row sm:items-center sm:justify-between",
26
- isHighlighted && "bg-primary-soft/60",
27
- )}
28
- style={bg ? { backgroundColor: bg } : undefined}
29
- >
30
- <div className="min-w-0">
31
- <div className="flex items-center gap-2.5">
32
- <h3 className="font-display text-lg font-semibold tracking-tight text-foreground">
33
- {plan.name}
34
- </h3>
35
- {isHighlighted ? (
36
- <span className="rounded-full bg-primary px-2.5 py-0.5 text-[11px] font-semibold text-on-primary uppercase tracking-wider">
37
- Popular
38
- </span>
39
- ) : null}
40
- </div>
41
- {plan.description ? (
42
- <p className="mt-1 truncate text-sm text-muted-foreground">{plan.description}</p>
43
- ) : null}
44
- </div>
45
- <div className="flex items-center gap-6">
46
- <ul className="hidden items-center gap-4 lg:flex">
47
- {plan.features.slice(0, 3).map((f) => (
48
- <li key={f} className="flex items-center gap-1.5 text-sm text-muted-foreground" style={{ color: fg }}>
49
- <Check className="h-3.5 w-3.5 text-current" />
50
- {f}
51
- </li>
52
- ))}
53
- </ul>
54
- <div className="font-display text-2xl font-semibold tracking-tight text-foreground">
55
- {price(value)}
56
- {value !== null ? (
57
- <span className="text-sm font-normal text-muted-foreground">/{billing === "monthly" ? "mo" : "yr"}</span>
58
- ) : null}
59
- </div>
60
- <PlanButton plan={plan} billing={billing} onSelect={onSelect} />
61
- </div>
62
- </div>
63
- );
64
- })}
65
- </div>
66
- </div>
67
- );
68
- };
69
- PricingCompact.displayName = "PricingCompact";
@@ -1,49 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { render, screen } from "@testing-library/react";
3
- import { Prose, ProseLead } from "./Prose";
4
-
5
- describe("Prose", () => {
6
- it("renders an article element with prose classes", () => {
7
- render(<Prose>Text</Prose>);
8
- const article = screen.getByText("Text");
9
- expect(article.tagName).toBe("ARTICLE");
10
- expect(article).toHaveClass("prose");
11
- });
12
-
13
- it("applies the requested size", () => {
14
- render(<Prose size="xl">Text</Prose>);
15
- expect(screen.getByText("Text")).toHaveClass("prose-xl");
16
- });
17
-
18
- it("caps width by default and removes it when wide", () => {
19
- render(<Prose>Capped</Prose>);
20
- expect(screen.getByText("Capped")).toHaveClass("max-w-3xl");
21
-
22
- render(<Prose wide>Wide</Prose>);
23
- expect(screen.getByText("Wide")).not.toHaveClass("max-w-3xl");
24
- });
25
-
26
- it("renders children", () => {
27
- render(
28
- <Prose>
29
- <h1>Heading</h1>
30
- </Prose>,
31
- );
32
- expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
33
- });
34
-
35
- it("renders raw HTML via the html prop instead of children", () => {
36
- render(<Prose html="<h2>CMS content</h2><p>A paragraph.</p>" />);
37
- expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent("CMS content");
38
- expect(screen.getByText("A paragraph.")).toBeInTheDocument();
39
- });
40
- });
41
-
42
- describe("ProseLead", () => {
43
- it("renders a lead-styled paragraph", () => {
44
- render(<ProseLead>Intro</ProseLead>);
45
- const p = screen.getByText("Intro");
46
- expect(p.tagName).toBe("P");
47
- expect(p).toHaveClass("lead");
48
- });
49
- });