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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1366 -0
  5. package/docs-template/README.md +255 -0
  6. package/docs-template/[slug]/[subslug]/page.tsx +1242 -0
  7. package/docs-template/[slug]/page.tsx +422 -0
  8. package/docs-template/api/page.tsx +47 -0
  9. package/docs-template/components/docs/docs-category-page.tsx +162 -0
  10. package/docs-template/components/docs/docs-code-card.tsx +135 -0
  11. package/docs-template/components/docs/docs-header.tsx +69 -0
  12. package/docs-template/components/docs/docs-nav.ts +95 -0
  13. package/docs-template/components/docs/docs-sidebar.tsx +112 -0
  14. package/docs-template/components/docs/docs-toc.tsx +38 -0
  15. package/docs-template/components/ui/badge.tsx +47 -0
  16. package/docs-template/components/ui/button.tsx +60 -0
  17. package/docs-template/components/ui/card.tsx +93 -0
  18. package/docs-template/components/ui/sheet.tsx +140 -0
  19. package/docs-template/documentation/page.tsx +80 -0
  20. package/docs-template/layout.tsx +27 -0
  21. package/docs-template/lib/utils.ts +7 -0
  22. package/docs-template/page.tsx +360 -0
  23. package/package.json +66 -0
  24. package/template/.env.example +45 -0
  25. package/template/README.md +239 -0
  26. package/template/app/api/auth/[...all]/route.ts +4 -0
  27. package/template/app/api/chat/route.ts +16 -0
  28. package/template/app/api/subscription/route.ts +25 -0
  29. package/template/app/api/upload-image/route.ts +64 -0
  30. package/template/app/blog/[slug]/page.tsx +314 -0
  31. package/template/app/blog/page.tsx +107 -0
  32. package/template/app/dashboard/_components/chart-interactive.tsx +289 -0
  33. package/template/app/dashboard/_components/chatbot.tsx +39 -0
  34. package/template/app/dashboard/_components/mode-toggle.tsx +46 -0
  35. package/template/app/dashboard/_components/navbar.tsx +84 -0
  36. package/template/app/dashboard/_components/section-cards.tsx +102 -0
  37. package/template/app/dashboard/_components/sidebar.tsx +90 -0
  38. package/template/app/dashboard/_components/subscribe-button.tsx +49 -0
  39. package/template/app/dashboard/billing/page.tsx +277 -0
  40. package/template/app/dashboard/chat/page.tsx +73 -0
  41. package/template/app/dashboard/cli/page.tsx +260 -0
  42. package/template/app/dashboard/layout.tsx +24 -0
  43. package/template/app/dashboard/page.tsx +216 -0
  44. package/template/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
  45. package/template/app/dashboard/payment/page.tsx +126 -0
  46. package/template/app/dashboard/settings/page.tsx +613 -0
  47. package/template/app/dashboard/upload/page.tsx +324 -0
  48. package/template/app/error.tsx +78 -0
  49. package/template/app/favicon.ico +0 -0
  50. package/template/app/globals.css +126 -0
  51. package/template/app/layout.tsx +135 -0
  52. package/template/app/not-found.tsx +45 -0
  53. package/template/app/page.tsx +28 -0
  54. package/template/app/pricing/_component/pricing-table.tsx +276 -0
  55. package/template/app/pricing/page.tsx +23 -0
  56. package/template/app/privacy-policy/page.tsx +280 -0
  57. package/template/app/robots.txt +12 -0
  58. package/template/app/sign-in/page.tsx +228 -0
  59. package/template/app/sign-up/page.tsx +243 -0
  60. package/template/app/sitemap.ts +62 -0
  61. package/template/app/success/page.tsx +123 -0
  62. package/template/app/terms-of-service/page.tsx +212 -0
  63. package/template/auth-schema.ts +47 -0
  64. package/template/components/homepage/cli-workflow-section.tsx +138 -0
  65. package/template/components/homepage/features-section.tsx +150 -0
  66. package/template/components/homepage/footer.tsx +53 -0
  67. package/template/components/homepage/hero-section.tsx +112 -0
  68. package/template/components/homepage/integrations.tsx +124 -0
  69. package/template/components/homepage/navigation.tsx +116 -0
  70. package/template/components/homepage/news-section.tsx +82 -0
  71. package/template/components/homepage/testimonials-section.tsx +34 -0
  72. package/template/components/logos/BetterAuth.tsx +21 -0
  73. package/template/components/logos/NeonPostgres.tsx +41 -0
  74. package/template/components/logos/Nextjs.tsx +72 -0
  75. package/template/components/logos/Polar.tsx +7 -0
  76. package/template/components/logos/TailwindCSS.tsx +27 -0
  77. package/template/components/logos/index.ts +6 -0
  78. package/template/components/logos/shadcnui.tsx +8 -0
  79. package/template/components/provider.tsx +8 -0
  80. package/template/components/ui/avatar.tsx +53 -0
  81. package/template/components/ui/badge.tsx +46 -0
  82. package/template/components/ui/button.tsx +59 -0
  83. package/template/components/ui/card.tsx +92 -0
  84. package/template/components/ui/chart.tsx +353 -0
  85. package/template/components/ui/checkbox.tsx +32 -0
  86. package/template/components/ui/dialog.tsx +135 -0
  87. package/template/components/ui/dropdown-menu.tsx +257 -0
  88. package/template/components/ui/form.tsx +167 -0
  89. package/template/components/ui/input.tsx +21 -0
  90. package/template/components/ui/label.tsx +24 -0
  91. package/template/components/ui/progress.tsx +31 -0
  92. package/template/components/ui/resizable.tsx +56 -0
  93. package/template/components/ui/select.tsx +185 -0
  94. package/template/components/ui/separator.tsx +28 -0
  95. package/template/components/ui/sheet.tsx +139 -0
  96. package/template/components/ui/skeleton.tsx +13 -0
  97. package/template/components/ui/sonner.tsx +25 -0
  98. package/template/components/ui/switch.tsx +31 -0
  99. package/template/components/ui/tabs.tsx +66 -0
  100. package/template/components/ui/textarea.tsx +18 -0
  101. package/template/components/ui/toggle-group.tsx +73 -0
  102. package/template/components/ui/toggle.tsx +47 -0
  103. package/template/components/ui/tooltip.tsx +61 -0
  104. package/template/components/user-profile.tsx +139 -0
  105. package/template/components.json +21 -0
  106. package/template/db/drizzle.ts +14 -0
  107. package/template/db/migrations/0000_worried_rawhide_kid.sql +77 -0
  108. package/template/db/migrations/meta/0000_snapshot.json +494 -0
  109. package/template/db/migrations/meta/_journal.json +13 -0
  110. package/template/db/schema.ts +85 -0
  111. package/template/drizzle.config.ts +13 -0
  112. package/template/emails/components/layout.tsx +181 -0
  113. package/template/emails/password-reset.tsx +67 -0
  114. package/template/emails/payment-failed.tsx +167 -0
  115. package/template/emails/subscription-confirmation.tsx +129 -0
  116. package/template/emails/welcome.tsx +100 -0
  117. package/template/eslint.config.mjs +16 -0
  118. package/template/hooks/use-mobile.ts +21 -0
  119. package/template/lib/auth-client.ts +8 -0
  120. package/template/lib/auth.ts +276 -0
  121. package/template/lib/email.ts +118 -0
  122. package/template/lib/polar-products.ts +49 -0
  123. package/template/lib/subscription.ts +148 -0
  124. package/template/lib/upload-image.ts +28 -0
  125. package/template/lib/utils.ts +6 -0
  126. package/template/middleware.ts +30 -0
  127. package/template/next-env.d.ts +5 -0
  128. package/template/next.config.ts +27 -0
  129. package/template/package.json +99 -0
  130. package/template/postcss.config.mjs +5 -0
  131. package/template/public/add.png +0 -0
  132. package/template/public/favicon.svg +4 -0
  133. package/template/public/file.svg +1 -0
  134. package/template/public/globe.svg +1 -0
  135. package/template/public/iphone.png +0 -0
  136. package/template/public/logo.png +0 -0
  137. package/template/public/next.svg +1 -0
  138. package/template/public/polar-sh.svg +1 -0
  139. package/template/public/shadcn-ui.svg +1 -0
  140. package/template/public/site.webmanifest +21 -0
  141. package/template/public/vercel.svg +1 -0
  142. package/template/public/window.svg +1 -0
  143. package/template/tailwind.config.ts +89 -0
  144. package/template/template.config.json +138 -0
  145. 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
+