Skip to content

Commit 7677dab

Browse files
thegdsksglinr
andauthored
feat: add /docs guides page (#1109)
* feat: add /docs guides page with framework, best-practice, and FAQ content Co-Authored-By: Glinr <bot@glincker.com> * feat: expand /docs with recipes, migration guides, and more FAQ Co-Authored-By: Glinr <bot@glincker.com> * fix: address docs page review feedback Co-Authored-By: Glinr <bot@glincker.com> * feat: replace navbar Extensions button with Docs Co-Authored-By: Glinr <bot@glincker.com> * feat: add page footer, ask-AI button, heading anchors, and docs search Co-Authored-By: Glinr <bot@glincker.com> * feat: show brand icons for each Ask AI target Co-Authored-By: Glinr <bot@glincker.com> * refactor: extract shared docs page layout to fix duplicated code Co-Authored-By: Glinr <bot@glincker.com> --------- Co-authored-by: Glinr <bot@glincker.com>
1 parent 25543d3 commit 7677dab

31 files changed

Lines changed: 1747 additions & 9 deletions

‎public/llms-full.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
theSVG (thesvg.org) is a free, open-source library of 7,417+ SVG icons across 7 collections: Brand Icons, AWS Architecture, Azure Services, Google Cloud, Kubernetes, Community, and Auth Badges. Every icon includes core metadata such as a name, hex color, categories, and license. Where present, icons also include a website URL, guideline links, and up to 7 SVG variants.
88

9+
Full human-readable framework guides, best practices, tricks, and FAQ: https://thesvg.org/docs
10+
911
## Quick Start
1012

1113
### Install via npm
@@ -201,6 +203,7 @@ MIT for the codebase and tooling. Brand icons are property of their respective t
201203
## Links
202204

203205
- Website: https://thesvg.org
206+
- Docs & Guides: https://thesvg.org/docs
204207
- GitHub: https://github.com/glincker/thesvg
205208
- npm: https://www.npmjs.com/package/thesvg
206209
- Compare Libraries: https://thesvg.org/compare

‎public/llms.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ theSVG provides 7,417+ SVG icons across 7 collections: Brand Icons, AWS Architec
99
## Links
1010

1111
- Website: https://thesvg.org
12+
- Docs & Guides: https://thesvg.org/docs
1213
- Compare Libraries: https://thesvg.org/compare
1314
- GitHub: https://github.com/glincker/thesvg
1415
- npm: https://www.npmjs.com/package/thesvg

‎src/app/docs/[slug]/page.tsx‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { Metadata } from "next";
2+
import { notFound } from "next/navigation";
3+
import Link from "next/link";
4+
import { ExternalLink, Package as PackageIcon } from "lucide-react";
5+
import { FRAMEWORK_GUIDES, FAQ_ITEMS } from "@/lib/docs-content";
6+
import { slugifyHeading } from "@/lib/docs-nav";
7+
import { GUIDE_RELATED_FAQ } from "@/lib/docs-related-faq";
8+
import { DocsCodeBlock } from "@/components/docs/docs-code-block";
9+
import { DocsHeading } from "@/components/docs/docs-heading";
10+
import { DocsBrandIcon } from "@/components/docs/docs-nav-icon";
11+
import { DocsListPage } from "@/components/docs/docs-list-page";
12+
13+
interface PageProps {
14+
params: Promise<{ slug: string }>;
15+
}
16+
17+
export function generateStaticParams() {
18+
return FRAMEWORK_GUIDES.map((guide) => ({ slug: guide.id }));
19+
}
20+
21+
export async function generateMetadata({ params }: Readonly<PageProps>): Promise<Metadata> {
22+
const { slug } = await params;
23+
const guide = FRAMEWORK_GUIDES.find((g) => g.id === slug);
24+
if (!guide) return {};
25+
return {
26+
title: guide.label,
27+
description: guide.summary,
28+
keywords: [`${guide.label} brand icons`, `${guide.label} SVG icons`, "theSVG"],
29+
openGraph: { title: guide.label, description: guide.summary, siteName: "theSVG" },
30+
alternates: { canonical: `https://thesvg.org/docs/${guide.id}` },
31+
};
32+
}
33+
34+
function PackageBadge({ href, icon, label }: Readonly<{ href: string; icon: React.ReactNode; label: string }>) {
35+
return (
36+
<Link
37+
href={href}
38+
target="_blank"
39+
rel="noopener noreferrer"
40+
className="inline-flex items-center gap-1 rounded-full bg-muted/60 px-2 py-0.5 font-mono text-[11px] text-muted-foreground hover:text-foreground dark:bg-white/[0.04]"
41+
>
42+
{icon}
43+
{label}
44+
<ExternalLink className="h-2.5 w-2.5" />
45+
</Link>
46+
);
47+
}
48+
49+
export default async function DocsGuidePage({ params }: Readonly<PageProps>) {
50+
const { slug } = await params;
51+
const guide = FRAMEWORK_GUIDES.find((g) => g.id === slug);
52+
if (!guide) notFound();
53+
54+
const tocItems = guide.snippets.map((s) => ({ id: slugifyHeading(s.label), label: s.label }));
55+
const relatedQuestions = GUIDE_RELATED_FAQ[guide.id] ?? [];
56+
const relatedFaq = FAQ_ITEMS.filter((item) => relatedQuestions.includes(item.question));
57+
58+
return (
59+
<DocsListPage
60+
title={guide.label}
61+
tocItems={tocItems}
62+
sourceFile="src/lib/docs-content.ts"
63+
titleBadges={
64+
<>
65+
{guide.packageName && (
66+
<PackageBadge
67+
href={guide.npmUrl ?? "#"}
68+
icon={<PackageIcon className="h-2.5 w-2.5" />}
69+
label={guide.packageName}
70+
/>
71+
)}
72+
{guide.githubUrl && (
73+
<PackageBadge
74+
href={guide.githubUrl}
75+
icon={<DocsBrandIcon slug="github" className="h-2.5 w-2.5" />}
76+
label="Source"
77+
/>
78+
)}
79+
</>
80+
}
81+
intro={<p className="mb-8 max-w-2xl text-sm text-muted-foreground">{guide.summary}</p>}
82+
>
83+
<div className="space-y-8">
84+
{guide.snippets.map((snippet) => (
85+
<section key={snippet.label} className="scroll-mt-24">
86+
<DocsHeading id={slugifyHeading(snippet.label)}>{snippet.label}</DocsHeading>
87+
<DocsCodeBlock snippet={snippet} />
88+
</section>
89+
))}
90+
</div>
91+
92+
{relatedFaq.length > 0 && (
93+
<div className="mt-10 border-t border-border/40 pt-6 dark:border-white/[0.06]">
94+
<h2 className="mb-3 text-sm font-semibold text-muted-foreground">Related questions</h2>
95+
<div className="space-y-3">
96+
{relatedFaq.map((item) => (
97+
<details key={item.question} className="group rounded-lg border border-border/40 px-3 py-2 dark:border-white/[0.06]">
98+
<summary className="cursor-pointer list-none text-sm font-medium marker:content-none">
99+
{item.question}
100+
</summary>
101+
<p className="mt-1.5 text-xs leading-relaxed text-muted-foreground">{item.answer}</p>
102+
</details>
103+
))}
104+
</div>
105+
<Link
106+
href="/docs/faq"
107+
className="mt-3 inline-block text-xs text-orange-600 underline underline-offset-2 dark:text-orange-400"
108+
>
109+
See all FAQ &rarr;
110+
</Link>
111+
</div>
112+
)}
113+
</DocsListPage>
114+
);
115+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Metadata } from "next";
2+
import { BEST_PRACTICES } from "@/lib/docs-content";
3+
import { DocsPageFooter } from "@/components/docs/docs-page-footer";
4+
import { DocsAskAi } from "@/components/docs/docs-ask-ai";
5+
6+
export const metadata: Metadata = {
7+
title: "Best Practices",
8+
description: "Sizing, variant choice, CDN pinning, and dark mode best practices for theSVG brand icons.",
9+
openGraph: { title: "Best Practices - theSVG Docs", description: "How to use brand icons well.", siteName: "theSVG" },
10+
alternates: { canonical: "https://thesvg.org/docs/best-practices" },
11+
};
12+
13+
export default function BestPracticesPage() {
14+
return (
15+
<article className="max-w-3xl">
16+
<div className="mb-2 flex flex-wrap items-center justify-between gap-3">
17+
<h1 className="text-2xl font-bold">Best Practices</h1>
18+
<DocsAskAi pageTitle="Best Practices" />
19+
</div>
20+
<p className="mb-8 max-w-2xl text-sm text-muted-foreground">
21+
Sizing, variant choice, CDN pinning, and dark mode, done right.
22+
</p>
23+
<div className="grid gap-3 sm:grid-cols-2">
24+
{BEST_PRACTICES.map((item) => (
25+
<div
26+
key={item.title}
27+
className="rounded-xl border border-border/40 bg-card/30 p-4 dark:border-white/[0.06] dark:bg-white/[0.02]"
28+
>
29+
<h2 className="mb-1.5 text-sm font-semibold">{item.title}</h2>
30+
<p className="text-xs leading-relaxed text-muted-foreground">{item.body}</p>
31+
</div>
32+
))}
33+
</div>
34+
<DocsPageFooter sourceFile="src/lib/docs-content.ts" pageTitle="Best Practices" />
35+
</article>
36+
);
37+
}

‎src/app/docs/faq/page.tsx‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Metadata } from "next";
2+
import { FAQ_ITEMS } from "@/lib/docs-content";
3+
import { JsonLd } from "@/components/json-ld";
4+
import { FaqList } from "@/components/docs/faq-list";
5+
import { DocsPageFooter } from "@/components/docs/docs-page-footer";
6+
import { DocsAskAi } from "@/components/docs/docs-ask-ai";
7+
8+
export const metadata: Metadata = {
9+
title: "FAQ",
10+
description: "Licensing, missing variants, rate limits, self-hosting, and other common questions about theSVG.",
11+
openGraph: { title: "FAQ - theSVG Docs", description: "Common questions about theSVG.", siteName: "theSVG" },
12+
alternates: { canonical: "https://thesvg.org/docs/faq" },
13+
};
14+
15+
export default function FaqPage() {
16+
const faqJsonLd = {
17+
"@context": "https://schema.org",
18+
"@type": "FAQPage",
19+
mainEntity: FAQ_ITEMS.map((item) => ({
20+
"@type": "Question",
21+
name: item.question,
22+
acceptedAnswer: { "@type": "Answer", text: item.answer },
23+
})),
24+
};
25+
26+
return (
27+
<article className="max-w-3xl">
28+
<JsonLd data={faqJsonLd} />
29+
<div className="mb-2 flex flex-wrap items-center justify-between gap-3">
30+
<h1 className="text-2xl font-bold">FAQ</h1>
31+
<DocsAskAi pageTitle="FAQ" />
32+
</div>
33+
<p className="mb-8 max-w-2xl text-sm text-muted-foreground">
34+
Common questions about theSVG.
35+
</p>
36+
<FaqList items={FAQ_ITEMS} />
37+
<DocsPageFooter sourceFile="src/lib/docs-content.ts" pageTitle="FAQ" />
38+
</article>
39+
);
40+
}

‎src/app/docs/layout.tsx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getCategoryCounts } from "@/lib/icons";
2+
import { SidebarShell } from "@/components/layout/sidebar-shell";
3+
import { DocsSidebar } from "@/components/docs/docs-sidebar";
4+
5+
export default function DocsLayout({ children }: Readonly<{ children: React.ReactNode }>) {
6+
const categoryCounts = getCategoryCounts();
7+
8+
return (
9+
<SidebarShell categoryCounts={categoryCounts}>
10+
<div className="mx-auto flex max-w-6xl gap-8 px-4 py-8 sm:px-6 sm:py-10 lg:px-8">
11+
<DocsSidebar />
12+
<div className="min-w-0 flex-1">{children}</div>
13+
</div>
14+
</SidebarShell>
15+
);
16+
}

‎src/app/docs/migration/page.tsx‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Metadata } from "next";
2+
import Link from "next/link";
3+
import { MIGRATION_GUIDES } from "@/lib/docs-migration";
4+
import { DocsCodeBlock } from "@/components/docs/docs-code-block";
5+
import { DocsHeading } from "@/components/docs/docs-heading";
6+
import { DocsListPage } from "@/components/docs/docs-list-page";
7+
8+
export const metadata: Metadata = {
9+
title: "Migrating from other libraries",
10+
description: "Migrating to theSVG from Simple Icons or svgl, and how to choose between theSVG, Iconify, and Lucide.",
11+
openGraph: { title: "Migrating - theSVG Docs", description: "Moving from another icon library.", siteName: "theSVG" },
12+
alternates: { canonical: "https://thesvg.org/docs/migration" },
13+
};
14+
15+
export default function MigrationPage() {
16+
const tocItems = MIGRATION_GUIDES.map((g) => ({ id: g.id, label: g.title }));
17+
18+
return (
19+
<DocsListPage
20+
title="Migrating from other libraries"
21+
tocItems={tocItems}
22+
sourceFile="src/lib/docs-migration.ts"
23+
intro={
24+
<p className="mb-8 max-w-2xl text-sm text-muted-foreground">
25+
Full feature-by-feature comparison lives on{" "}
26+
<Link href="/compare" className="text-orange-600 underline underline-offset-2 dark:text-orange-400">
27+
/compare
28+
</Link>
29+
. These are the short, practical version.
30+
</p>
31+
}
32+
>
33+
<div className="space-y-10">
34+
{MIGRATION_GUIDES.map((guide) => (
35+
<section key={guide.id} className="scroll-mt-24">
36+
<DocsHeading id={guide.id}>{guide.title}</DocsHeading>
37+
<p className="mb-3 max-w-2xl text-xs leading-relaxed text-muted-foreground">
38+
{guide.summary}
39+
</p>
40+
<ul className="mb-3 list-disc space-y-1 pl-4 text-xs leading-relaxed text-muted-foreground">
41+
{guide.points.map((point) => (
42+
<li key={point}>{point}</li>
43+
))}
44+
</ul>
45+
{guide.snippet && <DocsCodeBlock snippet={guide.snippet} />}
46+
</section>
47+
))}
48+
</div>
49+
</DocsListPage>
50+
);
51+
}

0 commit comments

Comments
 (0)