diff --git a/apps/cli/package.json b/apps/cli/package.json index e5d1a96..fe8832c 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -19,9 +19,6 @@ "url": "git+https://github.com/jakejarvis/stanza.git", "directory": "apps/cli" }, - "bin": { - "stanza": "./dist/bin.mjs" - }, "files": [ "dist" ], @@ -32,6 +29,9 @@ ".": "./src/index.ts" }, "publishConfig": { + "bin": { + "stanza": "./dist/bin.mjs" + }, "main": "./dist/index.mjs", "types": "./dist/index.d.mts", "exports": { diff --git a/apps/web/src/components/builder/file-preview.tsx b/apps/web/src/components/builder/file-preview.tsx index d7689e3..ff00f78 100644 --- a/apps/web/src/components/builder/file-preview.tsx +++ b/apps/web/src/components/builder/file-preview.tsx @@ -114,7 +114,7 @@ export function FilePreview({ diff --git a/apps/web/src/components/docs/docs-sidebar.tsx b/apps/web/src/components/docs/docs-sidebar.tsx new file mode 100644 index 0000000..417c861 --- /dev/null +++ b/apps/web/src/components/docs/docs-sidebar.tsx @@ -0,0 +1,93 @@ +import { Link, useRouterState } from "@tanstack/react-router"; +import type { Node, Root } from "fumadocs-core/page-tree"; +import type { ReactNode } from "react"; + +import { cn } from "@/lib/utils"; + +function toSplat(url: string): string { + return url.replace(/^\/docs\/?/, ""); +} + +function normalize(path: string): string { + return path.length > 1 ? path.replace(/\/$/, "") : path; +} + +function NavLink({ url, name, pathname }: { url: string; name: ReactNode; pathname: string }) { + const active = normalize(pathname) === normalize(url); + return ( +
  • + + {name} + +
  • + ); +} + +function NavNode({ node, pathname }: { node: Node; pathname: string }) { + if (node.type === "separator") { + return ( +
  • + {node.name} +
  • + ); + } + + if (node.type === "folder") { + return ( +
  • +

    + {node.name} +

    + +
  • + ); + } + + return ; +} + +function NavList({ nodes, pathname }: { nodes: Node[]; pathname: string }) { + return ( + + ); +} + +export function DocsSidebar({ tree }: { tree: Root }) { + const pathname = useRouterState({ select: (s) => s.location.pathname }); + const list = ; + + return ( + <> + {/* Mobile: a disclosure above the content so docs stay navigable < md. */} +
    + Documentation +
    {list}
    +
    + + {/* Desktop: sticky left rail aligned to the page frame. */} + + + ); +} diff --git a/apps/web/src/components/docs/docs-toc.tsx b/apps/web/src/components/docs/docs-toc.tsx new file mode 100644 index 0000000..42f3785 --- /dev/null +++ b/apps/web/src/components/docs/docs-toc.tsx @@ -0,0 +1,48 @@ +import { AnchorProvider, useActiveAnchor } from "fumadocs-core/toc"; +import type { TOCItemType } from "fumadocs-core/toc"; + +import { cn } from "@/lib/utils"; + +function TocList({ toc }: { toc: TOCItemType[] }) { + const active = useActiveAnchor(); + + return ( +
      + {toc.map((item) => { + const id = item.url.split("#")[1]; + const isActive = id === active; + return ( +
    • + + {item.title} + +
    • + ); + })} +
    + ); +} + +export function DocsToc({ toc }: { toc: TOCItemType[] }) { + if (toc.length === 0) return null; + + return ( + + ); +} diff --git a/apps/web/src/lib/layout.shared.tsx b/apps/web/src/lib/layout.shared.tsx deleted file mode 100644 index e3cc75c..0000000 --- a/apps/web/src/lib/layout.shared.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; - -// The stanza site header/footer wrap docs from __root, so Fumadocs renders no -// top nav of its own — only its sidebar + table of contents. Dark mode and -// search are owned by the site shell (ThemeProvider + registry palette), so -// Fumadocs' own theme switch and search trigger stay off. -export function baseOptions(): BaseLayoutProps { - return { - nav: { enabled: false }, - themeSwitch: { enabled: false }, - searchToggle: { enabled: false }, - }; -} diff --git a/apps/web/src/routes/docs.$.tsx b/apps/web/src/routes/docs.$.tsx index e31e9c6..aff00bb 100644 --- a/apps/web/src/routes/docs.$.tsx +++ b/apps/web/src/routes/docs.$.tsx @@ -2,13 +2,13 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; import { createServerFn } from "@tanstack/react-start"; import browserCollections from "collections/browser"; import { useFumadocsLoader } from "fumadocs-core/source/client"; -import { DocsLayout } from "fumadocs-ui/layouts/docs"; -import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page"; +import { DocsBody } from "fumadocs-ui/layouts/docs/page"; import { RootProvider } from "fumadocs-ui/provider/tanstack"; import { Suspense } from "react"; +import { DocsSidebar } from "@/components/docs/docs-sidebar"; +import { DocsToc } from "@/components/docs/docs-toc"; import { useMDXComponents } from "@/components/mdx"; -import { baseOptions } from "@/lib/layout.shared"; import { buildHead } from "@/lib/seo"; import { source } from "@/lib/source"; @@ -31,13 +31,18 @@ const clientLoader = browserCollections.docs.createClientLoader({ id: "docs", component({ toc, frontmatter, default: MDX }) { return ( - - {frontmatter.title} - {frontmatter.description} - - - - +
    +
    +

    {frontmatter.title}

    + {frontmatter.description && ( +

    {frontmatter.description}

    + )} + + + +
    + +
    ); }, }); @@ -65,9 +70,14 @@ function Page() { return ( - - {clientLoader.useContent(data.path)} - +
    +
    + +
    + {clientLoader.useContent(data.path)} +
    +
    +
    ); } diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 5d893ce..d00f2a1 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -8,12 +8,10 @@ @source "../node_modules/fumadocs-ui/dist/**/*.js"; /* Remap Fumadocs' own design tokens onto the stanza (shadcn) palette so the - * docs chrome inherits the site's exact colors. The `#nd-sidebar` selector is - * included to override fumadocs' neutral.css sidebar tweaks at equal - * specificity; values follow stanza's `:root`/`.dark` tokens automatically. */ + * docs content (prose, code blocks, callouts) inherits the site's exact colors. + * Values follow stanza's `:root`/`.dark` tokens automatically. */ :root, -.dark, -.dark #nd-sidebar { +.dark { --color-fd-background: var(--background); --color-fd-foreground: var(--foreground); --color-fd-muted: var(--muted); diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 137b59d..a43d245 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -17,5 +17,11 @@ export default defineConfig({ ], resolve: { tsconfigPaths: true, + alias: { + // tslib's CJS UMD sets __esModule: true without providing a default + // export, which breaks Vite 8 / Rolldown's consistent CJS interop. + // Alias to the native ESM build to avoid the interop entirely. + tslib: "tslib/tslib.es6.mjs", + }, }, }); diff --git a/packages/create-stanza/package.json b/packages/create-stanza/package.json index bd59e8b..4286b98 100644 --- a/packages/create-stanza/package.json +++ b/packages/create-stanza/package.json @@ -18,14 +18,14 @@ "url": "git+https://github.com/jakejarvis/stanza.git", "directory": "packages/create-stanza" }, - "bin": { - "create-stanza": "./dist/bin.mjs" - }, "files": [ "dist" ], "type": "module", "publishConfig": { + "bin": { + "create-stanza": "./dist/bin.mjs" + }, "access": "public" }, "scripts": {