@lukoweb/apitogo-module-landing 0.2.22 → 0.2.24

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.
package/src/landing.css CHANGED
@@ -1,14 +1,14 @@
1
- @keyframes landing-fu {
2
- from {
3
- opacity: 0;
4
- transform: translateY(10px);
5
- }
6
- to {
7
- opacity: 1;
8
- transform: none;
9
- }
10
- }
11
-
12
- .landing-fu {
13
- animation: landing-fu 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) both;
14
- }
1
+ @keyframes landing-fu {
2
+ from {
3
+ opacity: 0;
4
+ transform: translateY(10px);
5
+ }
6
+ to {
7
+ opacity: 1;
8
+ transform: none;
9
+ }
10
+ }
11
+
12
+ .landing-fu {
13
+ animation: landing-fu 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) both;
14
+ }
@@ -1,46 +1,46 @@
1
- import type { ResolvedLandingModule } from "@lukoweb/apitogo";
2
- import type { NavigationPlugin } from "@lukoweb/apitogo/plugins";
3
- import type { ComponentType, ReactNode } from "react";
4
- import { isValidElement } from "react";
5
- import type { RouteObject } from "react-router";
6
- import { DefaultLandingPage } from "./DefaultLandingPage.js";
7
-
8
- export type LandingModuleOptions = ResolvedLandingModule & {
9
- component?: ComponentType | ReactNode;
10
- };
11
-
12
- const renderLandingElement = (options: LandingModuleOptions) => {
13
- if (options.component) {
14
- if (isValidElement(options.component)) {
15
- return options.component;
16
- }
17
-
18
- const Component = options.component as ComponentType;
19
- return <Component />;
20
- }
21
-
22
- return <DefaultLandingPage content={options.content} />;
23
- };
24
-
25
- export const landingModule = (
26
- options: LandingModuleOptions,
27
- ): NavigationPlugin | undefined => {
28
- if (!options.enabled) {
29
- return undefined;
30
- }
31
-
32
- const routes: RouteObject[] = [
33
- {
34
- path: options.path,
35
- element: renderLandingElement(options),
36
- handle: {
37
- layout: options.layout,
38
- },
39
- },
40
- ];
41
-
42
- return {
43
- getRoutes: () => routes,
44
- getNavigation: async () => [],
45
- };
46
- };
1
+ import type { ResolvedLandingModule } from "@lukoweb/apitogo";
2
+ import type { NavigationPlugin } from "@lukoweb/apitogo/plugins";
3
+ import type { ComponentType, ReactNode } from "react";
4
+ import { isValidElement } from "react";
5
+ import type { RouteObject } from "react-router";
6
+ import { DefaultLandingPage } from "./DefaultLandingPage.js";
7
+
8
+ export type LandingModuleOptions = ResolvedLandingModule & {
9
+ component?: ComponentType | ReactNode;
10
+ };
11
+
12
+ const renderLandingElement = (options: LandingModuleOptions) => {
13
+ if (options.component) {
14
+ if (isValidElement(options.component)) {
15
+ return options.component;
16
+ }
17
+
18
+ const Component = options.component as ComponentType;
19
+ return <Component />;
20
+ }
21
+
22
+ return <DefaultLandingPage content={options.content} />;
23
+ };
24
+
25
+ export const landingModule = (
26
+ options: LandingModuleOptions,
27
+ ): NavigationPlugin | undefined => {
28
+ if (!options.enabled) {
29
+ return undefined;
30
+ }
31
+
32
+ const routes: RouteObject[] = [
33
+ {
34
+ path: options.path,
35
+ element: renderLandingElement(options),
36
+ handle: {
37
+ layout: options.layout,
38
+ },
39
+ },
40
+ ];
41
+
42
+ return {
43
+ getRoutes: () => routes,
44
+ getNavigation: async () => [],
45
+ };
46
+ };
@@ -1,131 +1,131 @@
1
- import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
- import { cn } from "@lukoweb/apitogo";
3
- import { Link } from "@lukoweb/apitogo/components";
4
- import { Button } from "@lukoweb/apitogo/ui/Button.js";
5
- import { ArrowRightIcon } from "lucide-react";
6
- import { LandingIcon } from "./LandingIcon.js";
7
-
8
- export type ApiShowcaseSectionProps = {
9
- apiShowcase: NonNullable<ResolvedLandingContent["apiShowcase"]> & {
10
- layout?: "grid" | "list";
11
- };
12
- };
13
-
14
- const methodTone = (method?: string) => {
15
- const m = (method ?? "").toUpperCase();
16
- if (m === "POST" || m === "PUT" || m === "PATCH") {
17
- return "bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300";
18
- }
19
- if (m === "DELETE") {
20
- return "bg-red-50 text-red-600 dark:bg-red-500/15 dark:text-red-300";
21
- }
22
- return "bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300";
23
- };
24
-
25
- export const ApiShowcaseSection = ({
26
- apiShowcase,
27
- }: ApiShowcaseSectionProps) => {
28
- const items = apiShowcase.items ?? [];
29
- if (items.length === 0) {
30
- return null;
31
- }
32
-
33
- const browseHref = apiShowcase.browseAllHref ?? "/api";
34
- const browseLabel = apiShowcase.browseAllLabel ?? "Browse all APIs";
35
- const layout = apiShowcase.layout ?? "grid";
36
-
37
- return (
38
- <section className="border-y border-border bg-background">
39
- <div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
40
- <div className="mb-8 flex flex-wrap items-end justify-between gap-5">
41
- <div>
42
- {layout === "grid" ? (
43
- <p className="mb-3 font-mono text-[0.8125rem] font-semibold text-primary">
44
- {"// the catalog"}
45
- </p>
46
- ) : null}
47
- <h2 className="text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
48
- {apiShowcase.title ?? "Production APIs. One key."}
49
- </h2>
50
- {apiShowcase.subtitle ? (
51
- <p className="mt-2 text-muted-foreground">
52
- {apiShowcase.subtitle}
53
- </p>
54
- ) : null}
55
- </div>
56
- <Button asChild variant="outline" size="sm">
57
- <Link to={browseHref}>
58
- {browseLabel}
59
- <ArrowRightIcon size={15} aria-hidden />
60
- </Link>
61
- </Button>
62
- </div>
63
-
64
- {layout === "list" ? (
65
- <div className="overflow-hidden rounded-[14px] border border-border bg-card">
66
- {items.map((item, index) => {
67
- const method =
68
- "method" in item && typeof item.method === "string"
69
- ? item.method
70
- : undefined;
71
- const description = item.summary ?? item.name;
72
- return (
73
- <div
74
- key={`${item.name}-${item.path}`}
75
- className={cn(
76
- "flex flex-wrap items-center gap-3.5 px-5 py-3.5",
77
- index < items.length - 1 && "border-b border-border",
78
- )}
79
- >
80
- {method ? (
81
- <span
82
- className={cn(
83
- "min-w-14 rounded-[5px] px-2 py-0.5 text-center font-mono text-[11px] font-bold",
84
- methodTone(method),
85
- )}
86
- >
87
- {method.toUpperCase()}
88
- </span>
89
- ) : null}
90
- <code className="flex-1 font-mono text-[0.84375rem] text-foreground">
91
- {item.path}
92
- </code>
93
- <span className="text-[0.8125rem] text-muted-foreground">
94
- {description}
95
- </span>
96
- </div>
97
- );
98
- })}
99
- </div>
100
- ) : (
101
- <div className="grid gap-3.5 sm:grid-cols-2 lg:grid-cols-4">
102
- {items.map((item) => (
103
- <Link
104
- key={`${item.name}-${item.path}`}
105
- to={browseHref}
106
- className="block rounded-[13px] border border-border bg-card p-[1.125rem] text-foreground no-underline transition-[border-color,transform] hover:-translate-y-0.5 hover:border-muted-foreground/30"
107
- >
108
- <div className="mb-3.5 flex items-center justify-between">
109
- <div className="grid size-9 place-items-center rounded-[9px] border border-border bg-muted/40 text-primary">
110
- <LandingIcon name={item.icon ?? "boxes"} size={18} />
111
- </div>
112
- {item.latency ? (
113
- <span className="rounded-md bg-primary/10 px-[0.4375rem] py-[0.1875rem] font-mono text-[0.6875rem] font-semibold text-primary">
114
- {item.latency}
115
- </span>
116
- ) : null}
117
- </div>
118
- <h3 className="mb-1 text-[0.9375rem] font-semibold">
119
- {item.name}
120
- </h3>
121
- <p className="font-mono text-[0.71875rem] text-muted-foreground/70">
122
- {item.path}
123
- </p>
124
- </Link>
125
- ))}
126
- </div>
127
- )}
128
- </div>
129
- </section>
130
- );
131
- };
1
+ import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
+ import { cn } from "@lukoweb/apitogo";
3
+ import { Link } from "@lukoweb/apitogo/components";
4
+ import { Button } from "@lukoweb/apitogo/ui/Button.js";
5
+ import { ArrowRightIcon } from "lucide-react";
6
+ import { LandingIcon } from "./LandingIcon.js";
7
+
8
+ export type ApiShowcaseSectionProps = {
9
+ apiShowcase: NonNullable<ResolvedLandingContent["apiShowcase"]> & {
10
+ layout?: "grid" | "list";
11
+ };
12
+ };
13
+
14
+ const methodTone = (method?: string) => {
15
+ const m = (method ?? "").toUpperCase();
16
+ if (m === "POST" || m === "PUT" || m === "PATCH") {
17
+ return "bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300";
18
+ }
19
+ if (m === "DELETE") {
20
+ return "bg-red-50 text-red-600 dark:bg-red-500/15 dark:text-red-300";
21
+ }
22
+ return "bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300";
23
+ };
24
+
25
+ export const ApiShowcaseSection = ({
26
+ apiShowcase,
27
+ }: ApiShowcaseSectionProps) => {
28
+ const items = apiShowcase.items ?? [];
29
+ if (items.length === 0) {
30
+ return null;
31
+ }
32
+
33
+ const browseHref = apiShowcase.browseAllHref ?? "/api";
34
+ const browseLabel = apiShowcase.browseAllLabel ?? "Browse all APIs";
35
+ const layout = apiShowcase.layout ?? "grid";
36
+
37
+ return (
38
+ <section className="border-y border-border bg-background">
39
+ <div className="mx-auto max-w-[1200px] px-6 py-[3.75rem]">
40
+ <div className="mb-8 flex flex-wrap items-end justify-between gap-5">
41
+ <div>
42
+ {layout === "grid" ? (
43
+ <p className="mb-3 font-mono text-[0.8125rem] font-semibold text-primary">
44
+ {"// the catalog"}
45
+ </p>
46
+ ) : null}
47
+ <h2 className="text-[2.125rem] leading-tight font-bold tracking-[-0.025em]">
48
+ {apiShowcase.title ?? "Production APIs. One key."}
49
+ </h2>
50
+ {apiShowcase.subtitle ? (
51
+ <p className="mt-2 text-muted-foreground">
52
+ {apiShowcase.subtitle}
53
+ </p>
54
+ ) : null}
55
+ </div>
56
+ <Button asChild variant="outline" size="sm">
57
+ <Link to={browseHref}>
58
+ {browseLabel}
59
+ <ArrowRightIcon size={15} aria-hidden />
60
+ </Link>
61
+ </Button>
62
+ </div>
63
+
64
+ {layout === "list" ? (
65
+ <div className="overflow-hidden rounded-[14px] border border-border bg-card">
66
+ {items.map((item, index) => {
67
+ const method =
68
+ "method" in item && typeof item.method === "string"
69
+ ? item.method
70
+ : undefined;
71
+ const description = item.summary ?? item.name;
72
+ return (
73
+ <div
74
+ key={`${item.name}-${item.path}`}
75
+ className={cn(
76
+ "flex flex-wrap items-center gap-3.5 px-5 py-3.5",
77
+ index < items.length - 1 && "border-b border-border",
78
+ )}
79
+ >
80
+ {method ? (
81
+ <span
82
+ className={cn(
83
+ "min-w-14 rounded-[5px] px-2 py-0.5 text-center font-mono text-[11px] font-bold",
84
+ methodTone(method),
85
+ )}
86
+ >
87
+ {method.toUpperCase()}
88
+ </span>
89
+ ) : null}
90
+ <code className="flex-1 font-mono text-[0.84375rem] text-foreground">
91
+ {item.path}
92
+ </code>
93
+ <span className="text-[0.8125rem] text-muted-foreground">
94
+ {description}
95
+ </span>
96
+ </div>
97
+ );
98
+ })}
99
+ </div>
100
+ ) : (
101
+ <div className="grid gap-3.5 sm:grid-cols-2 lg:grid-cols-4">
102
+ {items.map((item) => (
103
+ <Link
104
+ key={`${item.name}-${item.path}`}
105
+ to={browseHref}
106
+ className="block rounded-[13px] border border-border bg-card p-[1.125rem] text-foreground no-underline transition-[border-color,transform] hover:-translate-y-0.5 hover:border-muted-foreground/30"
107
+ >
108
+ <div className="mb-3.5 flex items-center justify-between">
109
+ <div className="grid size-9 place-items-center rounded-[9px] border border-border bg-muted/40 text-primary">
110
+ <LandingIcon name={item.icon ?? "boxes"} size={18} />
111
+ </div>
112
+ {item.latency ? (
113
+ <span className="rounded-md bg-primary/10 px-[0.4375rem] py-[0.1875rem] font-mono text-[0.6875rem] font-semibold text-primary">
114
+ {item.latency}
115
+ </span>
116
+ ) : null}
117
+ </div>
118
+ <h3 className="mb-1 text-[0.9375rem] font-semibold">
119
+ {item.name}
120
+ </h3>
121
+ <p className="font-mono text-[0.71875rem] text-muted-foreground/70">
122
+ {item.path}
123
+ </p>
124
+ </Link>
125
+ ))}
126
+ </div>
127
+ )}
128
+ </div>
129
+ </section>
130
+ );
131
+ };
@@ -1,37 +1,37 @@
1
- import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
- import { TerminalIcon } from "lucide-react";
3
-
4
- export type CodeExampleCardProps = {
5
- example: NonNullable<
6
- NonNullable<ResolvedLandingContent["hero"]>["codeExample"]
7
- >;
8
- };
9
-
10
- export const CodeExampleCard = ({ example }: CodeExampleCardProps) => {
11
- const filename = example.filename ?? "example.sh";
12
- const language = example.language ?? "bash";
13
-
14
- return (
15
- <div className="overflow-hidden rounded-2xl border border-border bg-card shadow-sm">
16
- <div className="flex items-center gap-2 border-b border-border bg-muted/40 px-4 py-3">
17
- <span
18
- className="size-[11px] rounded-full bg-destructive/80"
19
- aria-hidden
20
- />
21
- <span className="size-[11px] rounded-full bg-warning" aria-hidden />
22
- <span className="size-[11px] rounded-full bg-primary" aria-hidden />
23
- <span className="ms-2 font-mono text-xs text-muted-foreground">
24
- {filename}
25
- </span>
26
- <TerminalIcon
27
- size={14}
28
- className="ms-auto text-muted-foreground/70"
29
- aria-hidden
30
- />
31
- </div>
32
- <pre className="overflow-x-auto p-[1.125rem] font-mono text-[0.78125rem] leading-[1.85] text-foreground">
33
- <code data-language={language}>{example.code}</code>
34
- </pre>
35
- </div>
36
- );
37
- };
1
+ import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
+ import { TerminalIcon } from "lucide-react";
3
+
4
+ export type CodeExampleCardProps = {
5
+ example: NonNullable<
6
+ NonNullable<ResolvedLandingContent["hero"]>["codeExample"]
7
+ >;
8
+ };
9
+
10
+ export const CodeExampleCard = ({ example }: CodeExampleCardProps) => {
11
+ const filename = example.filename ?? "example.sh";
12
+ const language = example.language ?? "bash";
13
+
14
+ return (
15
+ <div className="overflow-hidden rounded-2xl border border-border bg-card shadow-sm">
16
+ <div className="flex items-center gap-2 border-b border-border bg-muted/40 px-4 py-3">
17
+ <span
18
+ className="size-[11px] rounded-full bg-destructive/80"
19
+ aria-hidden
20
+ />
21
+ <span className="size-[11px] rounded-full bg-warning" aria-hidden />
22
+ <span className="size-[11px] rounded-full bg-primary" aria-hidden />
23
+ <span className="ms-2 font-mono text-xs text-muted-foreground">
24
+ {filename}
25
+ </span>
26
+ <TerminalIcon
27
+ size={14}
28
+ className="ms-auto text-muted-foreground/70"
29
+ aria-hidden
30
+ />
31
+ </div>
32
+ <pre className="overflow-x-auto p-[1.125rem] font-mono text-[0.78125rem] leading-[1.85] text-foreground">
33
+ <code data-language={language}>{example.code}</code>
34
+ </pre>
35
+ </div>
36
+ );
37
+ };
@@ -1,60 +1,60 @@
1
- import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
- import { LandingIcon } from "./LandingIcon.js";
3
-
4
- export type FeaturesSectionProps = {
5
- features: NonNullable<ResolvedLandingContent["features"]>;
6
- section?: ResolvedLandingContent["featuresSection"];
7
- };
8
-
9
- export const FeaturesSection = ({
10
- features,
11
- section,
12
- }: FeaturesSectionProps) => {
13
- if (features.length === 0) {
14
- return null;
15
- }
16
-
17
- const label = section?.label ?? "// why us";
18
- const title = section?.title ?? "Everything you need to build on your API";
19
- const description =
20
- section?.description ??
21
- "Skip the infra. Focus on your product while your developer portal handles docs, auth, and billing.";
22
-
23
- return (
24
- <section
25
- id="features"
26
- className="mx-auto max-w-[1200px] scroll-mt-(--top-header-height) px-6 py-10 pb-16"
27
- >
28
- <div className="mb-11 max-w-[640px]">
29
- <p className="mb-3 font-mono text-[0.8125rem] font-semibold text-primary">
30
- {label}
31
- </p>
32
- <h2 className="mb-3.5 text-[2.375rem] leading-tight font-bold tracking-[-0.025em] text-balance">
33
- {title}
34
- </h2>
35
- <p className="text-[1.0625rem] leading-relaxed text-muted-foreground">
36
- {description}
37
- </p>
38
- </div>
39
-
40
- <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
41
- {features.map((feature) => (
42
- <article
43
- key={feature.title}
44
- className="rounded-[14px] border border-border bg-card p-6 transition-colors hover:border-muted-foreground/30"
45
- >
46
- <div className="mb-4 grid size-[2.625rem] place-items-center rounded-[11px] bg-primary/10 text-primary">
47
- <LandingIcon name={feature.icon} size={21} />
48
- </div>
49
- <h3 className="mb-2 text-[1.09375rem] font-semibold">
50
- {feature.title}
51
- </h3>
52
- <p className="text-[0.90625rem] leading-relaxed text-muted-foreground">
53
- {feature.description}
54
- </p>
55
- </article>
56
- ))}
57
- </div>
58
- </section>
59
- );
60
- };
1
+ import type { ResolvedLandingContent } from "@lukoweb/apitogo";
2
+ import { LandingIcon } from "./LandingIcon.js";
3
+
4
+ export type FeaturesSectionProps = {
5
+ features: NonNullable<ResolvedLandingContent["features"]>;
6
+ section?: ResolvedLandingContent["featuresSection"];
7
+ };
8
+
9
+ export const FeaturesSection = ({
10
+ features,
11
+ section,
12
+ }: FeaturesSectionProps) => {
13
+ if (features.length === 0) {
14
+ return null;
15
+ }
16
+
17
+ const label = section?.label ?? "// why us";
18
+ const title = section?.title ?? "Everything you need to build on your API";
19
+ const description =
20
+ section?.description ??
21
+ "Skip the infra. Focus on your product while your developer portal handles docs, auth, and billing.";
22
+
23
+ return (
24
+ <section
25
+ id="features"
26
+ className="mx-auto max-w-[1200px] scroll-mt-(--top-header-height) px-6 py-10 pb-16"
27
+ >
28
+ <div className="mb-11 max-w-[640px]">
29
+ <p className="mb-3 font-mono text-[0.8125rem] font-semibold text-primary">
30
+ {label}
31
+ </p>
32
+ <h2 className="mb-3.5 text-[2.375rem] leading-tight font-bold tracking-[-0.025em] text-balance">
33
+ {title}
34
+ </h2>
35
+ <p className="text-[1.0625rem] leading-relaxed text-muted-foreground">
36
+ {description}
37
+ </p>
38
+ </div>
39
+
40
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
41
+ {features.map((feature) => (
42
+ <article
43
+ key={feature.title}
44
+ className="rounded-[14px] border border-border bg-card p-6 transition-colors hover:border-muted-foreground/30"
45
+ >
46
+ <div className="mb-4 grid size-[2.625rem] place-items-center rounded-[11px] bg-primary/10 text-primary">
47
+ <LandingIcon name={feature.icon} size={21} />
48
+ </div>
49
+ <h3 className="mb-2 text-[1.09375rem] font-semibold">
50
+ {feature.title}
51
+ </h3>
52
+ <p className="text-[0.90625rem] leading-relaxed text-muted-foreground">
53
+ {feature.description}
54
+ </p>
55
+ </article>
56
+ ))}
57
+ </div>
58
+ </section>
59
+ );
60
+ };