Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/app/[lang]/docs/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { isLocale, locales, type Locale } from "@/i18n/config";
import { DOC_NAV, DOC_SLUGS, type DocSlug } from "@/lib/docs";
import { DOC_COMPONENTS } from "@/components/docs/registry";

export function generateStaticParams() {
return locales.flatMap((lang) => DOC_SLUGS.map((slug) => ({ lang, slug })));
}

export async function generateMetadata({
params,
}: {
params: Promise<{ lang: string; slug: string }>;
}): Promise<Metadata> {
const { lang, slug } = await params;
const item = DOC_NAV.find((d) => d.slug === slug);
const locale: Locale = isLocale(lang) ? lang : "en";
const title = item ? item.title[locale] : "Docs";
return { title: `${title} — Weftmap` };
}

export default async function DocPage({
params,
}: {
params: Promise<{ lang: string; slug: string }>;
}) {
const { lang, slug } = await params;
if (!isLocale(lang)) notFound();

const index = DOC_NAV.findIndex((d) => d.slug === slug);
if (index === -1) notFound();

const Content = DOC_COMPONENTS[slug as DocSlug];
const prev = DOC_NAV[index - 1];
const next = DOC_NAV[index + 1];

return (
<article>
<Content lang={lang} />

<nav className="mt-16 flex items-center justify-between gap-4 border-t border-white/[0.08] pt-6 text-sm">
{prev ? (
<Link
href={`/${lang}/docs/${prev.slug}`}
className="text-muted transition-colors hover:text-fg"
>
← {prev.title[lang]}
</Link>
) : (
<span />
)}
{next ? (
<Link
href={`/${lang}/docs/${next.slug}`}
className="text-right font-medium text-[#e6e9ef] transition-colors hover:text-white"
>
{next.title[lang]} →
</Link>
) : (
<span />
)}
</nav>
</article>
);
}
34 changes: 34 additions & 0 deletions src/app/[lang]/docs/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ReactNode } from "react";
import { notFound } from "next/navigation";
import { isLocale, locales, type Locale } from "@/i18n/config";
import Header from "@/components/layout/Header";
import DocsSidebar from "@/components/docs/DocsSidebar";

export function generateStaticParams() {
return locales.map((lang) => ({ lang }));
}

export default async function DocsLayout({
children,
params,
}: {
children: ReactNode;
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;
if (!isLocale(lang)) notFound();

return (
<>
<Header lang={lang as Locale} />
<div className="mx-auto grid max-w-[1200px] gap-10 px-6 py-10 lg:grid-cols-[220px_1fr] lg:gap-14 lg:py-16">
<aside className="hidden lg:block">
<div className="sticky top-[92px]">
<DocsSidebar lang={lang as Locale} />
</div>
</aside>
<main className="min-w-0 max-w-[760px]">{children}</main>
</div>
</>
);
}
10 changes: 10 additions & 0 deletions src/app/[lang]/docs/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { redirect } from "next/navigation";

export default async function DocsIndex({
params,
}: {
params: Promise<{ lang: string }>;
}) {
const { lang } = await params;
redirect(`/${lang}/docs/introduction`);
}
36 changes: 36 additions & 0 deletions src/components/docs/DocsSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import type { Locale } from "@/i18n/config";
import { DOC_NAV } from "@/lib/docs";

export default function DocsSidebar({ lang }: { lang: Locale }) {
const pathname = usePathname();

return (
<nav aria-label="Docs" className="flex flex-col gap-0.5">
<p className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-muted/60">
{lang === "es" ? "Documentación" : "Documentation"}
</p>
{DOC_NAV.map((d) => {
const href = `/${lang}/docs/${d.slug}`;
const active = pathname === href;
return (
<Link
key={d.slug}
href={href}
aria-current={active ? "page" : undefined}
className={`rounded-lg px-3 py-2 text-sm transition-colors ${
active
? "bg-white/[0.07] font-medium text-[#e6e9ef]"
: "text-muted hover:bg-white/[0.04] hover:text-fg"
}`}
>
{d.title[lang]}
</Link>
);
})}
</nav>
);
}
112 changes: 112 additions & 0 deletions src/components/docs/Prose.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { ReactNode } from "react";

// Shared typography primitives for documentation pages — keeps every page
// visually consistent without pulling in a markdown/MDX toolchain.

export function H1({ children }: { children: ReactNode }) {
return (
<h1 className="text-[2rem] font-bold leading-tight tracking-[-0.02em] text-[#e6e9ef]">
{children}
</h1>
);
}

export function Lead({ children }: { children: ReactNode }) {
return <p className="mt-4 text-lg leading-8 text-[#c7cdd6]">{children}</p>;
}

export function H2({ children }: { children: ReactNode }) {
return (
<h2 className="mt-14 mb-4 border-b border-white/[0.08] pb-2 text-xl font-semibold tracking-[-0.01em] text-[#e6e9ef]">
{children}
</h2>
);
}

export function H3({ children }: { children: ReactNode }) {
return (
<h3 className="mt-8 mb-2 text-[15px] font-semibold text-[#e6e9ef]">
{children}
</h3>
);
}

export function P({ children }: { children: ReactNode }) {
return <p className="mt-4 text-[15px] leading-7 text-muted">{children}</p>;
}

export function UL({ children }: { children: ReactNode }) {
return (
<ul className="mt-4 flex flex-col gap-2.5 text-[15px] leading-7 text-muted">
{children}
</ul>
);
}

export function LI({ children }: { children: ReactNode }) {
return (
<li className="flex gap-2.5">
<span className="mt-[11px] h-1 w-1 shrink-0 rounded-full bg-white/40" />
<span>{children}</span>
</li>
);
}

export function Code({ children }: { children: ReactNode }) {
return (
<code className="rounded-md bg-white/[0.07] px-1.5 py-0.5 font-mono text-[0.85em] text-[#e6e9ef]">
{children}
</code>
);
}

export function CodeBlock({ children, label }: { children: ReactNode; label?: string }) {
return (
<div className="mt-5 overflow-hidden rounded-xl border border-white/[0.08] bg-[#0c0d12]">
{label && (
<div className="border-b border-white/[0.06] px-4 py-2 font-mono text-[11px] text-muted/70">
{label}
</div>
)}
<pre className="overflow-auto px-4 py-3.5 font-mono text-[13px] leading-[1.7] text-[#cbd5e1]">
{children}
</pre>
</div>
);
}

const DOT_COLORS = {
calls: "rgba(255,255,255,0.5)",
imports: "#5eead4",
extends: "#c4b5fd",
} as const;

export function EdgeDot({ kind }: { kind: "calls" | "imports" | "extends" }) {
return (
<span
className="mr-1.5 inline-block h-2 w-2 rounded-full align-middle"
style={{ background: DOT_COLORS[kind] }}
/>
);
}

const CALLOUT_STYLES = {
note: "border-teal-300/25 bg-teal-300/[0.05]",
warn: "border-amber-300/25 bg-amber-300/[0.05]",
} as const;

export function Callout({
children,
kind = "note",
}: {
children: ReactNode;
kind?: "note" | "warn";
}) {
return (
<div
className={`mt-5 rounded-xl border px-4 py-3.5 text-[14px] leading-7 text-[#c7cdd6] ${CALLOUT_STYLES[kind]}`}
>
{children}
</div>
);
}
98 changes: 98 additions & 0 deletions src/components/docs/pages/GettingStarted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { Locale } from "@/i18n/config";
import { H1, Lead, H2, H3, P, UL, LI, Code, Callout } from "../Prose";

export default function GettingStarted({ lang }: { lang: Locale }) {
if (lang === "es") {
return (
<>
<H1>Empezar</H1>
<Lead>
Hay dos formas de generar un grafo: pegar un fragmento suelto, o subir
una carpeta para mapear todo un proyecto.
</Lead>

<H2>Modo Snippet</H2>
<P>Ideal para un archivo o un ejemplo rápido.</P>
<UL>
<LI>Abre la app y deja la pestaña <Code>Snippet</Code> activa.</LI>
<LI>Elige el lenguaje en el selector inferior.</LI>
<LI>Pega tu código en el editor.</LI>
<LI>Pulsa <Code>Analizar</Code> (o <Code>⌘/Ctrl + Enter</Code>).</LI>
</UL>
<P>El grafo aparece en el panel derecho. Cambiar de lenguaje carga un ejemplo nuevo solo si no has tocado el editor.</P>

<H2>Modo Proyecto</H2>
<P>Para ver la arquitectura de varios archivos a la vez.</P>
<UL>
<LI>Cambia a la pestaña <Code>Proyecto</Code>.</LI>
<LI>Pulsa <Code>Subir una carpeta</Code> y elige el directorio.</LI>
<LI>Weftmap filtra por extensión del lenguaje e ignora <Code>node_modules</Code>, <Code>.git</Code> y similares.</LI>
<LI>Revisa la lista de archivos y pulsa <Code>Analizar</Code>.</LI>
</UL>
<Callout kind="note">
Todo ocurre en tu navegador y servidor; los archivos no se guardan. Hay
un límite de tamaño y de número de archivos para mantenerlo ágil.
</Callout>

<H2>Explorar el diagrama</H2>
<UL>
<LI><strong>Mover y zoom:</strong> arrastra el lienzo, rueda para acercar.</LI>
<LI><strong>Encuadrar:</strong> usa los controles para reajustar la vista.</LI>
<LI><strong>Filtrar:</strong> en la leyenda, oculta o muestra <Code>calls</Code>, <Code>imports</Code> y <Code>extends</Code>.</LI>
</UL>

<H3>¿No aparece nada?</H3>
<UL>
<LI>Verifica que el lenguaje seleccionado coincide con el código.</LI>
<LI>Solo se dibujan funciones definidas en el código; las llamadas a librerías se omiten.</LI>
</UL>
</>
);
}

return (
<>
<H1>Getting started</H1>
<Lead>
There are two ways to build a graph: paste a single snippet, or upload a
folder to map a whole project.
</Lead>

<H2>Snippet mode</H2>
<P>Great for one file or a quick example.</P>
<UL>
<LI>Open the app and keep the <Code>Snippet</Code> tab active.</LI>
<LI>Pick the language in the bottom selector.</LI>
<LI>Paste your code into the editor.</LI>
<LI>Hit <Code>Analyze</Code> (or <Code>⌘/Ctrl + Enter</Code>).</LI>
</UL>
<P>The graph appears in the right panel. Switching language loads a fresh sample only if you haven&apos;t edited the editor.</P>

<H2>Project mode</H2>
<P>To see the architecture across many files at once.</P>
<UL>
<LI>Switch to the <Code>Project</Code> tab.</LI>
<LI>Click <Code>Upload a folder</Code> and pick the directory.</LI>
<LI>Weftmap filters by the language&apos;s extensions and ignores <Code>node_modules</Code>, <Code>.git</Code> and the like.</LI>
<LI>Review the file list and hit <Code>Analyze</Code>.</LI>
</UL>
<Callout kind="note">
Everything runs in your browser and server; files are not stored. There is
a size and file-count limit to keep things fast.
</Callout>

<H2>Explore the diagram</H2>
<UL>
<LI><strong>Pan &amp; zoom:</strong> drag the canvas, scroll to zoom.</LI>
<LI><strong>Fit:</strong> use the controls to reset the view.</LI>
<LI><strong>Filter:</strong> in the legend, toggle <Code>calls</Code>, <Code>imports</Code> and <Code>extends</Code>.</LI>
</UL>

<H3>Nothing shows up?</H3>
<UL>
<LI>Make sure the selected language matches the code.</LI>
<LI>Only functions defined in the code are drawn; calls to libraries are skipped.</LI>
</UL>
</>
);
}
Loading
Loading