@mehdad67/apitogo-module-landing 0.1.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/package.json +33 -0
- package/src/DefaultLandingPage.tsx +108 -0
- package/src/index.tsx +4 -0
- package/src/landingModule.tsx +46 -0
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mehdad67/apitogo-module-landing",
|
|
3
|
+
"version": "0.1.24",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.tsx"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"lucide-react": "0.577.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/react": "catalog:",
|
|
16
|
+
"@types/react-dom": "catalog:",
|
|
17
|
+
"@mehdad67/apitogo": "0.1.24",
|
|
18
|
+
"react": "catalog:",
|
|
19
|
+
"react-dom": "catalog:",
|
|
20
|
+
"typescript": "5.9.3"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@mehdad67/apitogo": "*",
|
|
24
|
+
"react": ">=19.2.0",
|
|
25
|
+
"react-dom": ">=19.2.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cn } from "@lukoweb/apitogo";
|
|
2
|
+
import type { ResolvedLandingContent } from "@lukoweb/apitogo";
|
|
3
|
+
import { Head, Link, useZudoku } from "@lukoweb/apitogo/components";
|
|
4
|
+
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
5
|
+
import { ArrowRightIcon, BookOpenIcon, CodeIcon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
export type DefaultLandingPageProps = {
|
|
8
|
+
content: ResolvedLandingContent;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const DefaultLandingPage = ({ content }: DefaultLandingPageProps) => {
|
|
12
|
+
const { options } = useZudoku();
|
|
13
|
+
const siteTitle =
|
|
14
|
+
options.metadata?.applicationName ?? options.site?.title ?? "APIToGo";
|
|
15
|
+
|
|
16
|
+
const hero = content.hero;
|
|
17
|
+
const title = hero?.title ?? siteTitle;
|
|
18
|
+
const subtitle = hero?.subtitle;
|
|
19
|
+
const description =
|
|
20
|
+
hero?.description ??
|
|
21
|
+
options.metadata?.description ??
|
|
22
|
+
"Beautiful API documentation and developer portals powered by APIToGo.";
|
|
23
|
+
|
|
24
|
+
const features = content.features ?? [];
|
|
25
|
+
const primaryAction = content.primaryAction;
|
|
26
|
+
const secondaryAction = content.secondaryAction;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="relative overflow-hidden">
|
|
30
|
+
<Head>
|
|
31
|
+
<title>{title}</title>
|
|
32
|
+
{description ? <meta name="description" content={description} /> : null}
|
|
33
|
+
</Head>
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
aria-hidden
|
|
37
|
+
className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[520px] bg-[radial-gradient(ellipse_at_top,var(--primary)_0%,transparent_55%)] opacity-[0.08]"
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<section className="mx-auto flex max-w-screen-xl flex-col gap-10 px-4 py-16 lg:px-8 lg:py-24">
|
|
41
|
+
<div className="mx-auto flex max-w-3xl flex-col items-center gap-6 text-center">
|
|
42
|
+
{subtitle ? (
|
|
43
|
+
<p className="rounded-full border bg-muted/50 px-3 py-1 text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
44
|
+
{subtitle}
|
|
45
|
+
</p>
|
|
46
|
+
) : null}
|
|
47
|
+
<h1 className="text-4xl font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl">
|
|
48
|
+
{title}
|
|
49
|
+
</h1>
|
|
50
|
+
<p className="max-w-2xl text-lg text-muted-foreground text-pretty">
|
|
51
|
+
{description}
|
|
52
|
+
</p>
|
|
53
|
+
{(primaryAction || secondaryAction) && (
|
|
54
|
+
<div className="flex flex-wrap items-center justify-center gap-3 pt-2">
|
|
55
|
+
{primaryAction ? (
|
|
56
|
+
<Button asChild size="lg">
|
|
57
|
+
<Link to={primaryAction.href}>
|
|
58
|
+
{primaryAction.label}
|
|
59
|
+
<ArrowRightIcon className="ms-1" />
|
|
60
|
+
</Link>
|
|
61
|
+
</Button>
|
|
62
|
+
) : null}
|
|
63
|
+
{secondaryAction ? (
|
|
64
|
+
<Button asChild variant="outline" size="lg">
|
|
65
|
+
<Link to={secondaryAction.href}>{secondaryAction.label}</Link>
|
|
66
|
+
</Button>
|
|
67
|
+
) : null}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{features.length > 0 ? (
|
|
73
|
+
<div className="grid gap-4 pt-8 sm:grid-cols-2 lg:grid-cols-3">
|
|
74
|
+
{features.map((feature) => (
|
|
75
|
+
<article
|
|
76
|
+
key={feature.title}
|
|
77
|
+
className="rounded-xl border bg-card/60 p-6 shadow-xs backdrop-blur-sm"
|
|
78
|
+
>
|
|
79
|
+
<div className="mb-4 flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
80
|
+
{feature.title.toLowerCase().includes("api") ? (
|
|
81
|
+
<CodeIcon size={20} />
|
|
82
|
+
) : (
|
|
83
|
+
<BookOpenIcon size={20} />
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
<h2 className="text-lg font-medium">{feature.title}</h2>
|
|
87
|
+
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
88
|
+
{feature.description}
|
|
89
|
+
</p>
|
|
90
|
+
</article>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
) : null}
|
|
94
|
+
</section>
|
|
95
|
+
|
|
96
|
+
{content.showPoweredBy !== false ? (
|
|
97
|
+
<div
|
|
98
|
+
className={cn(
|
|
99
|
+
"border-t py-6 text-center text-xs text-muted-foreground",
|
|
100
|
+
!options.site?.footer && "mb-8",
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
Built with APIToGo
|
|
104
|
+
</div>
|
|
105
|
+
) : null}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +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
|
+
};
|