@lukoweb/apitogo-module-landing 0.2.23 → 0.2.25

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.
@@ -1,46 +1,46 @@
1
- import { cn } from "@lukoweb/apitogo";
2
- import { Link } from "@lukoweb/apitogo/components";
3
- import { Button } from "@lukoweb/apitogo/ui/Button.js";
4
- import type { LucideIcon } from "lucide-react";
5
- import type { ReactNode } from "react";
6
-
7
- type LandingButtonProps = {
8
- href: string;
9
- label: string;
10
- icon?: LucideIcon;
11
- iconPosition?: "start" | "end";
12
- variant?: "primary" | "secondary";
13
- size?: "default" | "sm";
14
- className?: string;
15
- children?: ReactNode;
16
- };
17
-
18
- export const LandingButton = ({
19
- href,
20
- label,
21
- icon: Icon,
22
- iconPosition = "end",
23
- variant = "primary",
24
- size = "default",
25
- className,
26
- children,
27
- }: LandingButtonProps) => {
28
- const content = children ?? (
29
- <>
30
- {Icon && iconPosition === "start" ? <Icon size={17} aria-hidden /> : null}
31
- {label}
32
- {Icon && iconPosition === "end" ? <Icon size={17} aria-hidden /> : null}
33
- </>
34
- );
35
-
36
- return (
37
- <Button
38
- asChild
39
- variant={variant === "primary" ? "default" : "outline"}
40
- size={size === "sm" ? "sm" : "lg"}
41
- className={cn(className)}
42
- >
43
- <Link to={href}>{content}</Link>
44
- </Button>
45
- );
46
- };
1
+ import { cn } from "@lukoweb/apitogo";
2
+ import { Link } from "@lukoweb/apitogo/components";
3
+ import { Button } from "@lukoweb/apitogo/ui/Button.js";
4
+ import type { LucideIcon } from "lucide-react";
5
+ import type { ReactNode } from "react";
6
+
7
+ type LandingButtonProps = {
8
+ href: string;
9
+ label: string;
10
+ icon?: LucideIcon;
11
+ iconPosition?: "start" | "end";
12
+ variant?: "primary" | "secondary";
13
+ size?: "default" | "sm";
14
+ className?: string;
15
+ children?: ReactNode;
16
+ };
17
+
18
+ export const LandingButton = ({
19
+ href,
20
+ label,
21
+ icon: Icon,
22
+ iconPosition = "end",
23
+ variant = "primary",
24
+ size = "default",
25
+ className,
26
+ children,
27
+ }: LandingButtonProps) => {
28
+ const content = children ?? (
29
+ <>
30
+ {Icon && iconPosition === "start" ? <Icon size={17} aria-hidden /> : null}
31
+ {label}
32
+ {Icon && iconPosition === "end" ? <Icon size={17} aria-hidden /> : null}
33
+ </>
34
+ );
35
+
36
+ return (
37
+ <Button
38
+ asChild
39
+ variant={variant === "primary" ? "default" : "outline"}
40
+ size={size === "sm" ? "sm" : "lg"}
41
+ className={cn(className)}
42
+ >
43
+ <Link to={href}>{content}</Link>
44
+ </Button>
45
+ );
46
+ };
@@ -1,54 +1,54 @@
1
- import type { LucideIcon } from "lucide-react";
2
- import {
3
- ActivityIcon,
4
- ArrowRightLeftIcon,
5
- BadgeDollarSignIcon,
6
- BlocksIcon,
7
- BookOpenIcon,
8
- BoxesIcon,
9
- CheckIcon,
10
- CodeIcon,
11
- CoinsIcon,
12
- GaugeIcon,
13
- LayersIcon,
14
- RadioIcon,
15
- ShieldCheckIcon,
16
- WalletIcon,
17
- WebhookIcon,
18
- ZapIcon,
19
- } from "lucide-react";
20
-
21
- const ICONS: Record<string, LucideIcon> = {
22
- activity: ActivityIcon,
23
- "arrow-left-right": ArrowRightLeftIcon,
24
- "arrow-right-left": ArrowRightLeftIcon,
25
- "badge-dollar-sign": BadgeDollarSignIcon,
26
- blocks: BlocksIcon,
27
- "book-open": BookOpenIcon,
28
- boxes: BoxesIcon,
29
- check: CheckIcon,
30
- code: CodeIcon,
31
- coins: CoinsIcon,
32
- gauge: GaugeIcon,
33
- layers: LayersIcon,
34
- radio: RadioIcon,
35
- "shield-check": ShieldCheckIcon,
36
- wallet: WalletIcon,
37
- webhook: WebhookIcon,
38
- zap: ZapIcon,
39
- };
40
-
41
- export type LandingIconProps = {
42
- name?: string;
43
- size?: number;
44
- className?: string;
45
- };
46
-
47
- export const LandingIcon = ({
48
- name,
49
- size = 20,
50
- className,
51
- }: LandingIconProps) => {
52
- const Icon = (name && ICONS[name]) || CodeIcon;
53
- return <Icon size={size} className={className} aria-hidden />;
54
- };
1
+ import type { LucideIcon } from "lucide-react";
2
+ import {
3
+ ActivityIcon,
4
+ ArrowRightLeftIcon,
5
+ BadgeDollarSignIcon,
6
+ BlocksIcon,
7
+ BookOpenIcon,
8
+ BoxesIcon,
9
+ CheckIcon,
10
+ CodeIcon,
11
+ CoinsIcon,
12
+ GaugeIcon,
13
+ LayersIcon,
14
+ RadioIcon,
15
+ ShieldCheckIcon,
16
+ WalletIcon,
17
+ WebhookIcon,
18
+ ZapIcon,
19
+ } from "lucide-react";
20
+
21
+ const ICONS: Record<string, LucideIcon> = {
22
+ activity: ActivityIcon,
23
+ "arrow-left-right": ArrowRightLeftIcon,
24
+ "arrow-right-left": ArrowRightLeftIcon,
25
+ "badge-dollar-sign": BadgeDollarSignIcon,
26
+ blocks: BlocksIcon,
27
+ "book-open": BookOpenIcon,
28
+ boxes: BoxesIcon,
29
+ check: CheckIcon,
30
+ code: CodeIcon,
31
+ coins: CoinsIcon,
32
+ gauge: GaugeIcon,
33
+ layers: LayersIcon,
34
+ radio: RadioIcon,
35
+ "shield-check": ShieldCheckIcon,
36
+ wallet: WalletIcon,
37
+ webhook: WebhookIcon,
38
+ zap: ZapIcon,
39
+ };
40
+
41
+ export type LandingIconProps = {
42
+ name?: string;
43
+ size?: number;
44
+ className?: string;
45
+ };
46
+
47
+ export const LandingIcon = ({
48
+ name,
49
+ size = 20,
50
+ className,
51
+ }: LandingIconProps) => {
52
+ const Icon = (name && ICONS[name]) || CodeIcon;
53
+ return <Icon size={size} className={className} aria-hidden />;
54
+ };
@@ -1,76 +1,76 @@
1
- import { cn } from "@lukoweb/apitogo";
2
- import { Link } from "@lukoweb/apitogo/components";
3
- import { ArrowRightIcon } from "lucide-react";
4
-
5
- export type PlansPreviewPlan = {
6
- name: string;
7
- price: string;
8
- unit: string;
9
- blurb: string;
10
- href?: string;
11
- featured?: boolean;
12
- };
13
-
14
- export type PlansPreviewSectionProps = {
15
- title?: string;
16
- seeAllHref?: string;
17
- seeAllLabel?: string;
18
- plans: PlansPreviewPlan[];
19
- };
20
-
21
- export const PlansPreviewSection = ({
22
- title = "Simple, usage-based pricing",
23
- seeAllHref = "/pricing",
24
- seeAllLabel = "See full pricing →",
25
- plans,
26
- }: PlansPreviewSectionProps) => {
27
- if (plans.length === 0) {
28
- return null;
29
- }
30
-
31
- return (
32
- <section className="border-t border-border bg-muted/30">
33
- <div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
34
- <div className="mb-10 flex flex-wrap items-end justify-between gap-4">
35
- <h2 className="text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
36
- {title}
37
- </h2>
38
- <Link
39
- to={seeAllHref}
40
- className="inline-flex items-center gap-1 text-sm font-semibold text-primary no-underline hover:underline"
41
- >
42
- {seeAllLabel}
43
- {!seeAllLabel.includes("→") ? (
44
- <ArrowRightIcon size={14} aria-hidden />
45
- ) : null}
46
- </Link>
47
- </div>
48
- <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
49
- {plans.map((plan) => (
50
- <article
51
- key={plan.name}
52
- className={cn(
53
- "rounded-[14px] border border-border bg-card p-6",
54
- plan.featured &&
55
- "border-primary/50 ring-1 ring-primary/25 shadow-[0_10px_28px_rgba(13,148,136,0.18)]",
56
- )}
57
- >
58
- <div className="text-sm font-bold uppercase tracking-[0.04em]">
59
- {plan.name}
60
- </div>
61
- <div className="mt-2.5 text-[1.8rem] font-extrabold tracking-[-0.02em]">
62
- {plan.price}
63
- </div>
64
- <div className="mb-3.5 text-[0.78125rem] text-muted-foreground">
65
- {plan.unit}
66
- </div>
67
- <p className="text-[0.8125rem] leading-relaxed text-muted-foreground">
68
- {plan.blurb}
69
- </p>
70
- </article>
71
- ))}
72
- </div>
73
- </div>
74
- </section>
75
- );
76
- };
1
+ import { cn } from "@lukoweb/apitogo";
2
+ import { Link } from "@lukoweb/apitogo/components";
3
+ import { ArrowRightIcon } from "lucide-react";
4
+
5
+ export type PlansPreviewPlan = {
6
+ name: string;
7
+ price: string;
8
+ unit: string;
9
+ blurb: string;
10
+ href?: string;
11
+ featured?: boolean;
12
+ };
13
+
14
+ export type PlansPreviewSectionProps = {
15
+ title?: string;
16
+ seeAllHref?: string;
17
+ seeAllLabel?: string;
18
+ plans: PlansPreviewPlan[];
19
+ };
20
+
21
+ export const PlansPreviewSection = ({
22
+ title = "Simple, usage-based pricing",
23
+ seeAllHref = "/pricing",
24
+ seeAllLabel = "See full pricing →",
25
+ plans,
26
+ }: PlansPreviewSectionProps) => {
27
+ if (plans.length === 0) {
28
+ return null;
29
+ }
30
+
31
+ return (
32
+ <section className="border-t border-border bg-muted/30">
33
+ <div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
34
+ <div className="mb-10 flex flex-wrap items-end justify-between gap-4">
35
+ <h2 className="text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
36
+ {title}
37
+ </h2>
38
+ <Link
39
+ to={seeAllHref}
40
+ className="inline-flex items-center gap-1 text-sm font-semibold text-primary no-underline hover:underline"
41
+ >
42
+ {seeAllLabel}
43
+ {!seeAllLabel.includes("→") ? (
44
+ <ArrowRightIcon size={14} aria-hidden />
45
+ ) : null}
46
+ </Link>
47
+ </div>
48
+ <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
49
+ {plans.map((plan) => (
50
+ <article
51
+ key={plan.name}
52
+ className={cn(
53
+ "rounded-[14px] border border-border bg-card p-6",
54
+ plan.featured &&
55
+ "border-primary/50 ring-1 ring-primary/25 shadow-[0_10px_28px_rgba(13,148,136,0.18)]",
56
+ )}
57
+ >
58
+ <div className="text-sm font-bold uppercase tracking-[0.04em]">
59
+ {plan.name}
60
+ </div>
61
+ <div className="mt-2.5 text-[1.8rem] font-extrabold tracking-[-0.02em]">
62
+ {plan.price}
63
+ </div>
64
+ <div className="mb-3.5 text-[0.78125rem] text-muted-foreground">
65
+ {plan.unit}
66
+ </div>
67
+ <p className="text-[0.8125rem] leading-relaxed text-muted-foreground">
68
+ {plan.blurb}
69
+ </p>
70
+ </article>
71
+ ))}
72
+ </div>
73
+ </div>
74
+ </section>
75
+ );
76
+ };
@@ -1,82 +1,82 @@
1
- import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
- import { ArrowRightIcon } from "lucide-react";
3
- import { LandingButton } from "./LandingButtons.js";
4
-
5
- export type PricingCtaSectionProps = {
6
- pricingCta: NonNullable<ResolvedLandingContent["pricingCta"]>;
7
- variant?: "default" | "banner";
8
- };
9
-
10
- export const PricingCtaSection = ({
11
- pricingCta,
12
- variant = "default",
13
- }: PricingCtaSectionProps) => {
14
- const title = pricingCta.title ?? "Start free. Scale when you do.";
15
- const description =
16
- pricingCta.description ??
17
- "Generous free tier. Upgrade only when your app takes off.";
18
-
19
- if (variant === "banner") {
20
- return (
21
- <section className="relative overflow-hidden bg-foreground text-background">
22
- <div
23
- aria-hidden
24
- className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_120%,color-mix(in_oklch,var(--primary)_30%,transparent),transparent_55%)]"
25
- />
26
- <div className="relative mx-auto max-w-[640px] px-6 py-[4.5rem] text-center">
27
- <h2 className="mb-3.5 text-[2.125rem] leading-tight font-bold tracking-[-0.025em] text-balance">
28
- {title}
29
- </h2>
30
- <p className="mx-auto mb-7 max-w-[520px] text-lg leading-relaxed text-background/70">
31
- {description}
32
- </p>
33
- <div className="flex flex-wrap items-center justify-center gap-3">
34
- {pricingCta.primaryAction ? (
35
- <LandingButton
36
- href={pricingCta.primaryAction.href}
37
- label={pricingCta.primaryAction.label}
38
- icon={ArrowRightIcon}
39
- variant="primary"
40
- />
41
- ) : null}
42
- {pricingCta.secondaryAction ? (
43
- <LandingButton
44
- href={pricingCta.secondaryAction.href}
45
- label={pricingCta.secondaryAction.label}
46
- variant="secondary"
47
- />
48
- ) : null}
49
- </div>
50
- </div>
51
- </section>
52
- );
53
- }
54
-
55
- return (
56
- <section className="mx-auto max-w-[1200px] px-6 py-[4.5rem] text-center">
57
- <h2 className="mb-4 text-[2.5rem] leading-tight font-bold tracking-[-0.03em] text-balance">
58
- {title}
59
- </h2>
60
- <p className="mx-auto mb-[1.875rem] max-w-[520px] text-[1.09375rem] leading-relaxed text-muted-foreground">
61
- {description}
62
- </p>
63
- <div className="flex flex-wrap items-center justify-center gap-3">
64
- {pricingCta.primaryAction ? (
65
- <LandingButton
66
- href={pricingCta.primaryAction.href}
67
- label={pricingCta.primaryAction.label}
68
- icon={ArrowRightIcon}
69
- variant="primary"
70
- />
71
- ) : null}
72
- {pricingCta.secondaryAction ? (
73
- <LandingButton
74
- href={pricingCta.secondaryAction.href}
75
- label={pricingCta.secondaryAction.label}
76
- variant="secondary"
77
- />
78
- ) : null}
79
- </div>
80
- </section>
81
- );
82
- };
1
+ import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
+ import { ArrowRightIcon } from "lucide-react";
3
+ import { LandingButton } from "./LandingButtons.js";
4
+
5
+ export type PricingCtaSectionProps = {
6
+ pricingCta: NonNullable<ResolvedLandingContent["pricingCta"]>;
7
+ variant?: "default" | "banner";
8
+ };
9
+
10
+ export const PricingCtaSection = ({
11
+ pricingCta,
12
+ variant = "default",
13
+ }: PricingCtaSectionProps) => {
14
+ const title = pricingCta.title ?? "Start free. Scale when you do.";
15
+ const description =
16
+ pricingCta.description ??
17
+ "Generous free tier. Upgrade only when your app takes off.";
18
+
19
+ if (variant === "banner") {
20
+ return (
21
+ <section className="relative overflow-hidden bg-foreground text-background">
22
+ <div
23
+ aria-hidden
24
+ className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_120%,color-mix(in_oklch,var(--primary)_30%,transparent),transparent_55%)]"
25
+ />
26
+ <div className="relative mx-auto max-w-[640px] px-6 py-[4.5rem] text-center">
27
+ <h2 className="mb-3.5 text-[2.125rem] leading-tight font-bold tracking-[-0.025em] text-balance">
28
+ {title}
29
+ </h2>
30
+ <p className="mx-auto mb-7 max-w-[520px] text-lg leading-relaxed text-background/70">
31
+ {description}
32
+ </p>
33
+ <div className="flex flex-wrap items-center justify-center gap-3">
34
+ {pricingCta.primaryAction ? (
35
+ <LandingButton
36
+ href={pricingCta.primaryAction.href}
37
+ label={pricingCta.primaryAction.label}
38
+ icon={ArrowRightIcon}
39
+ variant="primary"
40
+ />
41
+ ) : null}
42
+ {pricingCta.secondaryAction ? (
43
+ <LandingButton
44
+ href={pricingCta.secondaryAction.href}
45
+ label={pricingCta.secondaryAction.label}
46
+ variant="secondary"
47
+ />
48
+ ) : null}
49
+ </div>
50
+ </div>
51
+ </section>
52
+ );
53
+ }
54
+
55
+ return (
56
+ <section className="mx-auto max-w-[1200px] px-6 py-[4.5rem] text-center">
57
+ <h2 className="mb-4 text-[2.5rem] leading-tight font-bold tracking-[-0.03em] text-balance">
58
+ {title}
59
+ </h2>
60
+ <p className="mx-auto mb-[1.875rem] max-w-[520px] text-[1.09375rem] leading-relaxed text-muted-foreground">
61
+ {description}
62
+ </p>
63
+ <div className="flex flex-wrap items-center justify-center gap-3">
64
+ {pricingCta.primaryAction ? (
65
+ <LandingButton
66
+ href={pricingCta.primaryAction.href}
67
+ label={pricingCta.primaryAction.label}
68
+ icon={ArrowRightIcon}
69
+ variant="primary"
70
+ />
71
+ ) : null}
72
+ {pricingCta.secondaryAction ? (
73
+ <LandingButton
74
+ href={pricingCta.secondaryAction.href}
75
+ label={pricingCta.secondaryAction.label}
76
+ variant="secondary"
77
+ />
78
+ ) : null}
79
+ </div>
80
+ </section>
81
+ );
82
+ };
@@ -1,44 +1,44 @@
1
- export type StepsSectionStep = {
2
- step: string;
3
- title: string;
4
- description: string;
5
- };
6
-
7
- export type StepsSectionProps = {
8
- title?: string;
9
- steps: StepsSectionStep[];
10
- };
11
-
12
- export const StepsSection = ({
13
- title = "Three steps to your first request",
14
- steps,
15
- }: StepsSectionProps) => {
16
- if (steps.length === 0) {
17
- return null;
18
- }
19
-
20
- return (
21
- <section className="border-y border-border bg-muted/30">
22
- <div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
23
- <h2 className="mb-10 text-center text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
24
- {title}
25
- </h2>
26
- <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
27
- {steps.map((item) => (
28
- <div key={item.step}>
29
- <div className="font-mono text-[2rem] font-bold text-primary">
30
- {item.step}
31
- </div>
32
- <h3 className="mt-2 mb-1.5 text-lg font-semibold">
33
- {item.title}
34
- </h3>
35
- <p className="text-sm leading-relaxed text-muted-foreground">
36
- {item.description}
37
- </p>
38
- </div>
39
- ))}
40
- </div>
41
- </div>
42
- </section>
43
- );
44
- };
1
+ export type StepsSectionStep = {
2
+ step: string;
3
+ title: string;
4
+ description: string;
5
+ };
6
+
7
+ export type StepsSectionProps = {
8
+ title?: string;
9
+ steps: StepsSectionStep[];
10
+ };
11
+
12
+ export const StepsSection = ({
13
+ title = "Three steps to your first request",
14
+ steps,
15
+ }: StepsSectionProps) => {
16
+ if (steps.length === 0) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <section className="border-y border-border bg-muted/30">
22
+ <div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
23
+ <h2 className="mb-10 text-center text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
24
+ {title}
25
+ </h2>
26
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
27
+ {steps.map((item) => (
28
+ <div key={item.step}>
29
+ <div className="font-mono text-[2rem] font-bold text-primary">
30
+ {item.step}
31
+ </div>
32
+ <h3 className="mt-2 mb-1.5 text-lg font-semibold">
33
+ {item.title}
34
+ </h3>
35
+ <p className="text-sm leading-relaxed text-muted-foreground">
36
+ {item.description}
37
+ </p>
38
+ </div>
39
+ ))}
40
+ </div>
41
+ </div>
42
+ </section>
43
+ );
44
+ };