shipd 0.1.0
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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1366 -0
- package/docs-template/README.md +255 -0
- package/docs-template/[slug]/[subslug]/page.tsx +1242 -0
- package/docs-template/[slug]/page.tsx +422 -0
- package/docs-template/api/page.tsx +47 -0
- package/docs-template/components/docs/docs-category-page.tsx +162 -0
- package/docs-template/components/docs/docs-code-card.tsx +135 -0
- package/docs-template/components/docs/docs-header.tsx +69 -0
- package/docs-template/components/docs/docs-nav.ts +95 -0
- package/docs-template/components/docs/docs-sidebar.tsx +112 -0
- package/docs-template/components/docs/docs-toc.tsx +38 -0
- package/docs-template/components/ui/badge.tsx +47 -0
- package/docs-template/components/ui/button.tsx +60 -0
- package/docs-template/components/ui/card.tsx +93 -0
- package/docs-template/components/ui/sheet.tsx +140 -0
- package/docs-template/documentation/page.tsx +80 -0
- package/docs-template/layout.tsx +27 -0
- package/docs-template/lib/utils.ts +7 -0
- package/docs-template/page.tsx +360 -0
- package/package.json +66 -0
- package/template/.env.example +45 -0
- package/template/README.md +239 -0
- package/template/app/api/auth/[...all]/route.ts +4 -0
- package/template/app/api/chat/route.ts +16 -0
- package/template/app/api/subscription/route.ts +25 -0
- package/template/app/api/upload-image/route.ts +64 -0
- package/template/app/blog/[slug]/page.tsx +314 -0
- package/template/app/blog/page.tsx +107 -0
- package/template/app/dashboard/_components/chart-interactive.tsx +289 -0
- package/template/app/dashboard/_components/chatbot.tsx +39 -0
- package/template/app/dashboard/_components/mode-toggle.tsx +46 -0
- package/template/app/dashboard/_components/navbar.tsx +84 -0
- package/template/app/dashboard/_components/section-cards.tsx +102 -0
- package/template/app/dashboard/_components/sidebar.tsx +90 -0
- package/template/app/dashboard/_components/subscribe-button.tsx +49 -0
- package/template/app/dashboard/billing/page.tsx +277 -0
- package/template/app/dashboard/chat/page.tsx +73 -0
- package/template/app/dashboard/cli/page.tsx +260 -0
- package/template/app/dashboard/layout.tsx +24 -0
- package/template/app/dashboard/page.tsx +216 -0
- package/template/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
- package/template/app/dashboard/payment/page.tsx +126 -0
- package/template/app/dashboard/settings/page.tsx +613 -0
- package/template/app/dashboard/upload/page.tsx +324 -0
- package/template/app/error.tsx +78 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/globals.css +126 -0
- package/template/app/layout.tsx +135 -0
- package/template/app/not-found.tsx +45 -0
- package/template/app/page.tsx +28 -0
- package/template/app/pricing/_component/pricing-table.tsx +276 -0
- package/template/app/pricing/page.tsx +23 -0
- package/template/app/privacy-policy/page.tsx +280 -0
- package/template/app/robots.txt +12 -0
- package/template/app/sign-in/page.tsx +228 -0
- package/template/app/sign-up/page.tsx +243 -0
- package/template/app/sitemap.ts +62 -0
- package/template/app/success/page.tsx +123 -0
- package/template/app/terms-of-service/page.tsx +212 -0
- package/template/auth-schema.ts +47 -0
- package/template/components/homepage/cli-workflow-section.tsx +138 -0
- package/template/components/homepage/features-section.tsx +150 -0
- package/template/components/homepage/footer.tsx +53 -0
- package/template/components/homepage/hero-section.tsx +112 -0
- package/template/components/homepage/integrations.tsx +124 -0
- package/template/components/homepage/navigation.tsx +116 -0
- package/template/components/homepage/news-section.tsx +82 -0
- package/template/components/homepage/testimonials-section.tsx +34 -0
- package/template/components/logos/BetterAuth.tsx +21 -0
- package/template/components/logos/NeonPostgres.tsx +41 -0
- package/template/components/logos/Nextjs.tsx +72 -0
- package/template/components/logos/Polar.tsx +7 -0
- package/template/components/logos/TailwindCSS.tsx +27 -0
- package/template/components/logos/index.ts +6 -0
- package/template/components/logos/shadcnui.tsx +8 -0
- package/template/components/provider.tsx +8 -0
- package/template/components/ui/avatar.tsx +53 -0
- package/template/components/ui/badge.tsx +46 -0
- package/template/components/ui/button.tsx +59 -0
- package/template/components/ui/card.tsx +92 -0
- package/template/components/ui/chart.tsx +353 -0
- package/template/components/ui/checkbox.tsx +32 -0
- package/template/components/ui/dialog.tsx +135 -0
- package/template/components/ui/dropdown-menu.tsx +257 -0
- package/template/components/ui/form.tsx +167 -0
- package/template/components/ui/input.tsx +21 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/progress.tsx +31 -0
- package/template/components/ui/resizable.tsx +56 -0
- package/template/components/ui/select.tsx +185 -0
- package/template/components/ui/separator.tsx +28 -0
- package/template/components/ui/sheet.tsx +139 -0
- package/template/components/ui/skeleton.tsx +13 -0
- package/template/components/ui/sonner.tsx +25 -0
- package/template/components/ui/switch.tsx +31 -0
- package/template/components/ui/tabs.tsx +66 -0
- package/template/components/ui/textarea.tsx +18 -0
- package/template/components/ui/toggle-group.tsx +73 -0
- package/template/components/ui/toggle.tsx +47 -0
- package/template/components/ui/tooltip.tsx +61 -0
- package/template/components/user-profile.tsx +139 -0
- package/template/components.json +21 -0
- package/template/db/drizzle.ts +14 -0
- package/template/db/migrations/0000_worried_rawhide_kid.sql +77 -0
- package/template/db/migrations/meta/0000_snapshot.json +494 -0
- package/template/db/migrations/meta/_journal.json +13 -0
- package/template/db/schema.ts +85 -0
- package/template/drizzle.config.ts +13 -0
- package/template/emails/components/layout.tsx +181 -0
- package/template/emails/password-reset.tsx +67 -0
- package/template/emails/payment-failed.tsx +167 -0
- package/template/emails/subscription-confirmation.tsx +129 -0
- package/template/emails/welcome.tsx +100 -0
- package/template/eslint.config.mjs +16 -0
- package/template/hooks/use-mobile.ts +21 -0
- package/template/lib/auth-client.ts +8 -0
- package/template/lib/auth.ts +276 -0
- package/template/lib/email.ts +118 -0
- package/template/lib/polar-products.ts +49 -0
- package/template/lib/subscription.ts +148 -0
- package/template/lib/upload-image.ts +28 -0
- package/template/lib/utils.ts +6 -0
- package/template/middleware.ts +30 -0
- package/template/next-env.d.ts +5 -0
- package/template/next.config.ts +27 -0
- package/template/package.json +99 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/add.png +0 -0
- package/template/public/favicon.svg +4 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/iphone.png +0 -0
- package/template/public/logo.png +0 -0
- package/template/public/next.svg +1 -0
- package/template/public/polar-sh.svg +1 -0
- package/template/public/shadcn-ui.svg +1 -0
- package/template/public/site.webmanifest +21 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/tailwind.config.ts +89 -0
- package/template/template.config.json +138 -0
- package/template/tsconfig.json +27 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Copy, Check } from "lucide-react";
|
|
5
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
6
|
+
|
|
7
|
+
export type DocsCodeVariant = "terminal" | "env" | "code";
|
|
8
|
+
|
|
9
|
+
export type DocsCodeCardProps = {
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
variant: DocsCodeVariant;
|
|
13
|
+
code: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function TokenizedTerminalLine({ line }: { line: string }) {
|
|
17
|
+
if (!line.startsWith("$")) {
|
|
18
|
+
return <span className="text-white/80">{line}</span>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rest = line.replace(/^\$\s?/, "");
|
|
22
|
+
const [cmd, ...args] = rest.split(" ");
|
|
23
|
+
const cmdColor =
|
|
24
|
+
cmd === "npx" || cmd === "npm" || cmd === "pnpm" || cmd === "yarn"
|
|
25
|
+
? "text-[#ff7043]"
|
|
26
|
+
: "text-white/80";
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
<span className="text-emerald-400">$</span>
|
|
31
|
+
<span className="text-white/80"> </span>
|
|
32
|
+
<span className={cmdColor}>{cmd}</span>
|
|
33
|
+
{args.length ? <span className="text-white/80"> {args.join(" ")}</span> : null}
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function TokenizedEnvLine({ line }: { line: string }) {
|
|
39
|
+
const trimmed = line.trim();
|
|
40
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
41
|
+
return <span className="text-white/45">{line}</span>;
|
|
42
|
+
}
|
|
43
|
+
const idx = line.indexOf("=");
|
|
44
|
+
if (idx === -1) return <span className="text-white/80">{line}</span>;
|
|
45
|
+
const key = line.slice(0, idx);
|
|
46
|
+
const value = line.slice(idx + 1);
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<span className="text-white/80">{key}</span>
|
|
50
|
+
<span className="text-white/50">=</span>
|
|
51
|
+
<span className="text-emerald-200">{value}</span>
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function CodeBody({ variant, code }: { variant: DocsCodeVariant; code: string }) {
|
|
57
|
+
const lines = code.replace(/\n$/, "").split("\n");
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<pre className="overflow-x-auto rounded-lg border border-white/10 bg-black/40 p-3 text-[13px] leading-6">
|
|
61
|
+
<code className="font-mono">
|
|
62
|
+
{lines.map((line, i) => (
|
|
63
|
+
<div key={i}>
|
|
64
|
+
{variant === "terminal" ? (
|
|
65
|
+
<TokenizedTerminalLine line={line} />
|
|
66
|
+
) : variant === "env" ? (
|
|
67
|
+
<TokenizedEnvLine line={line} />
|
|
68
|
+
) : (
|
|
69
|
+
<span className="text-white/80">{line}</span>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
))}
|
|
73
|
+
</code>
|
|
74
|
+
</pre>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default function DocsCodeCard({ title, description, variant, code }: DocsCodeCardProps) {
|
|
79
|
+
const [copied, setCopied] = React.useState(false);
|
|
80
|
+
|
|
81
|
+
async function copyToClipboard() {
|
|
82
|
+
try {
|
|
83
|
+
await navigator.clipboard.writeText(code);
|
|
84
|
+
setCopied(true);
|
|
85
|
+
window.setTimeout(() => setCopied(false), 1200);
|
|
86
|
+
return;
|
|
87
|
+
} catch {
|
|
88
|
+
// Fallback for older browsers / permission issues
|
|
89
|
+
try {
|
|
90
|
+
const ta = document.createElement("textarea");
|
|
91
|
+
ta.value = code;
|
|
92
|
+
ta.style.position = "fixed";
|
|
93
|
+
ta.style.left = "-9999px";
|
|
94
|
+
document.body.appendChild(ta);
|
|
95
|
+
ta.select();
|
|
96
|
+
document.execCommand("copy");
|
|
97
|
+
document.body.removeChild(ta);
|
|
98
|
+
setCopied(true);
|
|
99
|
+
window.setTimeout(() => setCopied(false), 1200);
|
|
100
|
+
} catch {
|
|
101
|
+
// If copy fails silently, we just don't show "Copied"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Card className="bg-white/5 border-white/10 shadow-none">
|
|
108
|
+
<CardContent className="px-6">
|
|
109
|
+
<div className="pt-4">
|
|
110
|
+
<div className="flex items-start justify-between gap-3">
|
|
111
|
+
<div className="min-w-0">
|
|
112
|
+
<div className="text-sm font-semibold text-white">{title}</div>
|
|
113
|
+
{description ? <p className="mt-1.5 text-sm text-white/60 leading-relaxed">{description}</p> : null}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
onClick={copyToClipboard}
|
|
119
|
+
className="shrink-0 inline-flex items-center gap-2 rounded-md border border-white/10 bg-black/30 px-3 py-1.5 text-xs font-medium text-white/70 hover:text-white hover:bg-white/10 transition-colors"
|
|
120
|
+
aria-label={copied ? "Copied" : "Copy snippet"}
|
|
121
|
+
>
|
|
122
|
+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
|
123
|
+
{copied ? "Copied" : "Copy"}
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="mt-4">
|
|
127
|
+
<CodeBody variant={variant} code={code} />
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="pb-4" />
|
|
131
|
+
</CardContent>
|
|
132
|
+
</Card>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { Search } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
|
7
|
+
import DocsSidebar from "./docs-sidebar";
|
|
8
|
+
|
|
9
|
+
export default function DocsHeader() {
|
|
10
|
+
return (
|
|
11
|
+
<header className="sticky top-0 z-50 border-b border-white/10 bg-black/80 backdrop-blur">
|
|
12
|
+
<div className="mx-auto max-w-[1400px] px-4 sm:px-6">
|
|
13
|
+
<div className="flex h-14 items-center gap-3">
|
|
14
|
+
<div className="flex items-center gap-2">
|
|
15
|
+
<Sheet>
|
|
16
|
+
<SheetTrigger asChild>
|
|
17
|
+
<Button variant="ghost" size="sm" className="md:hidden text-white/80 hover:text-white">
|
|
18
|
+
Menu
|
|
19
|
+
</Button>
|
|
20
|
+
</SheetTrigger>
|
|
21
|
+
<SheetContent side="left" className="p-0">
|
|
22
|
+
<DocsSidebar />
|
|
23
|
+
</SheetContent>
|
|
24
|
+
</Sheet>
|
|
25
|
+
|
|
26
|
+
<Link href="/docs" className="flex items-center gap-2">
|
|
27
|
+
<div className="h-7 w-7 rounded-md bg-white/10 ring-1 ring-white/15 flex items-center justify-center">
|
|
28
|
+
<span className="text-sm font-semibold text-white">S</span>
|
|
29
|
+
</div>
|
|
30
|
+
<span className="font-medium text-white">shipd</span>
|
|
31
|
+
</Link>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div className="hidden lg:flex items-center gap-5 ml-6 text-sm text-white/70">
|
|
35
|
+
<button type="button" className="hover:text-white transition-colors">
|
|
36
|
+
Products
|
|
37
|
+
</button>
|
|
38
|
+
<button type="button" className="hover:text-white transition-colors">
|
|
39
|
+
Resources
|
|
40
|
+
</button>
|
|
41
|
+
<button type="button" className="hover:text-white transition-colors">
|
|
42
|
+
Solutions
|
|
43
|
+
</button>
|
|
44
|
+
<button type="button" className="hover:text-white transition-colors">
|
|
45
|
+
Enterprise
|
|
46
|
+
</button>
|
|
47
|
+
<button type="button" className="hover:text-white transition-colors">
|
|
48
|
+
Pricing
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="ml-auto flex items-center gap-2">
|
|
53
|
+
<div className="hidden sm:flex items-center gap-2 rounded-md border border-white/10 bg-white/5 px-3 py-1.5 text-sm text-white/70">
|
|
54
|
+
<Search className="h-4 w-4" />
|
|
55
|
+
<span className="whitespace-nowrap">Search Documentation</span>
|
|
56
|
+
<span className="ml-2 rounded border border-white/10 bg-black/40 px-1.5 py-0.5 text-[11px] text-white/60">
|
|
57
|
+
⌘ K
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
<Button variant="ghost" size="sm" className="text-white/80 hover:text-white">
|
|
61
|
+
Ask AI
|
|
62
|
+
</Button>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</header>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type DocsNavItem = {
|
|
2
|
+
title: string;
|
|
3
|
+
href?: string;
|
|
4
|
+
items?: DocsNavItem[];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const docsNav: DocsNavItem[] = [
|
|
8
|
+
{
|
|
9
|
+
title: "Docs",
|
|
10
|
+
items: [
|
|
11
|
+
{ title: "Introduction", href: "/docs" },
|
|
12
|
+
{ title: "Documentation", href: "/docs/documentation" },
|
|
13
|
+
{ title: "Production Checklist", href: "/docs/production-checklist" },
|
|
14
|
+
{ title: "Knowledge Base", href: "/docs/knowledge-base" },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: "Packages",
|
|
19
|
+
items: [
|
|
20
|
+
{
|
|
21
|
+
title: "Authentication",
|
|
22
|
+
href: "/docs/authentication",
|
|
23
|
+
items: [
|
|
24
|
+
{ title: "Better Auth", href: "/docs/authentication/better-auth" },
|
|
25
|
+
{ title: "Supabase Auth", href: "/docs/authentication/supabase-auth" },
|
|
26
|
+
{ title: "NextAuth", href: "/docs/authentication/nextauth" },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: "Payments",
|
|
31
|
+
href: "/docs/payments",
|
|
32
|
+
items: [
|
|
33
|
+
{ title: "Stripe", href: "/docs/payments/stripe" },
|
|
34
|
+
{ title: "Lemon Squeezy", href: "/docs/payments/lemon-squeezy" },
|
|
35
|
+
{ title: "Polar", href: "/docs/payments/polar" },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: "Emails",
|
|
40
|
+
href: "/docs/emails",
|
|
41
|
+
items: [
|
|
42
|
+
{ title: "Resend", href: "/docs/emails/resend" },
|
|
43
|
+
{ title: "Mailgun", href: "/docs/emails/mailgun" },
|
|
44
|
+
{ title: "Inbound webhooks", href: "/docs/emails/inbound-webhooks" },
|
|
45
|
+
{ title: "DNS (DKIM/DMARC/SPF)", href: "/docs/emails/dns" },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: "Database",
|
|
50
|
+
href: "/docs/database",
|
|
51
|
+
items: [
|
|
52
|
+
{ title: "Supabase Postgres", href: "/docs/database/supabase-postgres" },
|
|
53
|
+
{ title: "Neon Postgres", href: "/docs/database/neon-postgres" },
|
|
54
|
+
{ title: "MongoDB", href: "/docs/database/mongodb" },
|
|
55
|
+
{ title: "Schema & migrations", href: "/docs/database/schema-migrations" },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: "SEO",
|
|
60
|
+
href: "/docs/seo",
|
|
61
|
+
items: [
|
|
62
|
+
{ title: "Blog starter", href: "/docs/seo/blog" },
|
|
63
|
+
{ title: "Meta tags", href: "/docs/seo/meta-tags" },
|
|
64
|
+
{ title: "OpenGraph", href: "/docs/seo/opengraph" },
|
|
65
|
+
{ title: "Sitemap", href: "/docs/seo/sitemap" },
|
|
66
|
+
{ title: "Structured data", href: "/docs/seo/structured-data" },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: "Style",
|
|
71
|
+
href: "/docs/style",
|
|
72
|
+
items: [
|
|
73
|
+
{ title: "Tailwind CSS", href: "/docs/style/tailwind" },
|
|
74
|
+
{ title: "daisyUI themes", href: "/docs/style/daisyui" },
|
|
75
|
+
{ title: "Automatic dark mode", href: "/docs/style/dark-mode" },
|
|
76
|
+
{ title: "UI components", href: "/docs/style/ui-components" },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: "Build & Deploy",
|
|
83
|
+
items: [
|
|
84
|
+
{ title: "Build your applications", href: "/docs/build-your-applications" },
|
|
85
|
+
{ title: "Deploy and scale", href: "/docs/deploy-and-scale" },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: "Security",
|
|
90
|
+
items: [
|
|
91
|
+
{ title: "Secure your applications", href: "/docs/secure" },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
7
|
+
import { docsNav } from "./docs-nav";
|
|
8
|
+
|
|
9
|
+
function SidebarLink({ href, title, indent = 0 }: { href: string; title: string; indent?: number }) {
|
|
10
|
+
const pathname = usePathname();
|
|
11
|
+
const active = pathname === href;
|
|
12
|
+
return (
|
|
13
|
+
<Link
|
|
14
|
+
href={href}
|
|
15
|
+
className={[
|
|
16
|
+
"block rounded-md px-2 py-1.5 text-sm transition-colors",
|
|
17
|
+
active ? "bg-white/10 text-white" : "text-white/60 hover:text-white hover:bg-white/5",
|
|
18
|
+
].join(" ")}
|
|
19
|
+
style={indent ? { paddingLeft: `${8 + indent * 12}px` } : undefined}
|
|
20
|
+
>
|
|
21
|
+
{title}
|
|
22
|
+
</Link>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ExpandableItem({
|
|
27
|
+
title,
|
|
28
|
+
href,
|
|
29
|
+
items,
|
|
30
|
+
}: {
|
|
31
|
+
title: string;
|
|
32
|
+
href: string;
|
|
33
|
+
items: { title: string; href: string }[];
|
|
34
|
+
}) {
|
|
35
|
+
const pathname = usePathname();
|
|
36
|
+
const isInSection = pathname === href || pathname.startsWith(href + "/");
|
|
37
|
+
const [open, setOpen] = React.useState(isInSection);
|
|
38
|
+
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
if (isInSection) setOpen(true);
|
|
41
|
+
}, [isInSection]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="space-y-1">
|
|
45
|
+
<div className="flex items-center">
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
aria-label={open ? `Collapse ${title}` : `Expand ${title}`}
|
|
49
|
+
onClick={() => setOpen((v) => !v)}
|
|
50
|
+
className="mr-1 rounded-md p-1 text-white/50 hover:text-white/80 hover:bg-white/5 transition-colors"
|
|
51
|
+
>
|
|
52
|
+
{open ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
|
53
|
+
</button>
|
|
54
|
+
<Link
|
|
55
|
+
href={href}
|
|
56
|
+
className={[
|
|
57
|
+
"flex-1 rounded-md px-2 py-1.5 text-sm transition-colors",
|
|
58
|
+
pathname === href ? "bg-white/10 text-white" : "text-white/60 hover:text-white hover:bg-white/5",
|
|
59
|
+
].join(" ")}
|
|
60
|
+
>
|
|
61
|
+
{title}
|
|
62
|
+
</Link>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{open ? (
|
|
66
|
+
<div className="space-y-1">
|
|
67
|
+
{items.map((child) => (
|
|
68
|
+
<SidebarLink key={child.href} href={child.href} title={child.title} indent={1} />
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
) : null}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default function DocsSidebar() {
|
|
77
|
+
return (
|
|
78
|
+
<div className="h-full bg-black">
|
|
79
|
+
<div className="h-14 border-b border-white/10 flex items-center px-4">
|
|
80
|
+
<span className="text-sm font-medium text-white/80">Docs</span>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className="px-3 py-4 space-y-6 overflow-auto h-[calc(100vh-3.5rem)]">
|
|
84
|
+
{docsNav.map((section) => (
|
|
85
|
+
<div key={section.title} className="space-y-2">
|
|
86
|
+
<div className="px-2 text-xs font-semibold tracking-wide text-white/50">
|
|
87
|
+
{section.title}
|
|
88
|
+
</div>
|
|
89
|
+
<div className="space-y-1">
|
|
90
|
+
{(section.items || []).map((item) => {
|
|
91
|
+
if (!item.href) return null;
|
|
92
|
+
if (item.items && item.items.length > 0) {
|
|
93
|
+
const children = item.items.filter((c) => !!c.href) as { title: string; href: string }[];
|
|
94
|
+
return (
|
|
95
|
+
<ExpandableItem
|
|
96
|
+
key={item.href}
|
|
97
|
+
title={item.title}
|
|
98
|
+
href={item.href}
|
|
99
|
+
items={children}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return <SidebarLink key={item.href} href={item.href} title={item.title} />;
|
|
104
|
+
})}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type TocItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export default function DocsToc({ items }: { items: TocItem[] }) {
|
|
7
|
+
if (!items.length) return null;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="pt-10">
|
|
11
|
+
<div className="text-xs font-semibold tracking-wide text-white/60 mb-3">On this page</div>
|
|
12
|
+
<nav className="space-y-2">
|
|
13
|
+
{items.map((item) => (
|
|
14
|
+
<a
|
|
15
|
+
key={item.id}
|
|
16
|
+
href={`#${item.id}`}
|
|
17
|
+
className="block text-sm text-white/60 hover:text-white transition-colors"
|
|
18
|
+
>
|
|
19
|
+
{item.title}
|
|
20
|
+
</a>
|
|
21
|
+
))}
|
|
22
|
+
</nav>
|
|
23
|
+
|
|
24
|
+
<div className="mt-8 space-y-2 text-sm text-white/60">
|
|
25
|
+
<button type="button" className="flex items-center gap-2 hover:text-white transition-colors">
|
|
26
|
+
Copy page
|
|
27
|
+
</button>
|
|
28
|
+
<button type="button" className="flex items-center gap-2 hover:text-white transition-colors">
|
|
29
|
+
Give feedback
|
|
30
|
+
</button>
|
|
31
|
+
<button type="button" className="flex items-center gap-2 hover:text-white transition-colors">
|
|
32
|
+
Ask AI about this page
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
14
|
+
secondary:
|
|
15
|
+
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
16
|
+
destructive:
|
|
17
|
+
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
18
|
+
outline:
|
|
19
|
+
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
function Badge({
|
|
29
|
+
className,
|
|
30
|
+
variant,
|
|
31
|
+
asChild = false,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<"span"> &
|
|
34
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
35
|
+
const Comp = asChild ? Slot : "span"
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Comp
|
|
39
|
+
data-slot="badge"
|
|
40
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Badge, badgeVariants }
|
|
47
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
16
|
+
outline:
|
|
17
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
18
|
+
secondary:
|
|
19
|
+
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
20
|
+
ghost:
|
|
21
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
26
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
27
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
28
|
+
icon: "size-9",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
variant: "default",
|
|
33
|
+
size: "default",
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
function Button({
|
|
39
|
+
className,
|
|
40
|
+
variant,
|
|
41
|
+
size,
|
|
42
|
+
asChild = false,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<"button"> &
|
|
45
|
+
VariantProps<typeof buttonVariants> & {
|
|
46
|
+
asChild?: boolean
|
|
47
|
+
}) {
|
|
48
|
+
const Comp = asChild ? Slot : "button"
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Comp
|
|
52
|
+
data-slot="button"
|
|
53
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Button, buttonVariants }
|
|
60
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn(
|
|
23
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="card-title"
|
|
35
|
+
className={cn("leading-none font-semibold", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="card-description"
|
|
45
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="card-action"
|
|
55
|
+
className={cn(
|
|
56
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="card-content"
|
|
68
|
+
className={cn("px-6", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="card-footer"
|
|
78
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
Card,
|
|
86
|
+
CardHeader,
|
|
87
|
+
CardFooter,
|
|
88
|
+
CardTitle,
|
|
89
|
+
CardAction,
|
|
90
|
+
CardDescription,
|
|
91
|
+
CardContent,
|
|
92
|
+
}
|
|
93
|
+
|