Skip to content

Commit b85dc09

Browse files
committed
docs: versioned docs, component previews and DemoPreview in README and ARCHITECTURE
1 parent ac0f2a2 commit b85dc09

2 files changed

Lines changed: 59 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,46 @@ That's a working docs site. See [shelldocs.dev/docs/getting-started/quick-start]
3939
```
4040
- **Static site output.** `shelldocs build` produces static HTML ready for GitHub Pages, Vercel, Netlify, Cloudflare, anywhere. Base-href rewrite + SPA 404 fallback included.
4141

42+
## Versioned docs
43+
44+
Put each version in its own content folder with the same sub-structure (`content/docs/v0.3/...`, `content/docs/v0.2.1/...`; dots in folder names are fine) and register them:
45+
46+
```csharp
47+
o.AddVersion("v0.3", "v0.3.0", "/docs/v0.3", "Current stable", latest: true);
48+
o.AddVersion("v0.2.1", "v0.2.1", "/docs/v0.2.1", "Previous release");
49+
50+
// "{version}" in a package root resolves to the current version's Id.
51+
o.AddPackage("shellui.cli", "ShellUI.CLI", "Command line", "/docs/{version}/cli", icon);
52+
```
53+
54+
The current version is the one whose `RootUrl` prefixes the path (segment-aware), otherwise the `latest` one. With 2+ versions a `<VersionSelector />` renders under the package selector (and in the mobile drawer; in the `TopNav` layout it sits in the header). Switching versions keeps the same page when it exists, else the current package's root, else the version's first page. Switching packages keeps the version. Inside a version the sidebar shows only that version's tree, prev/next never crosses into another version, search shows the current version plus pages outside every version, and the version folder is left out of the breadcrumb. Everything is server-rendered `<a href>`s plus `shelldocs.js` delegation, so it works on static hosts.
55+
56+
> Routes declared as `/docs/{*Path:nonfile}` don't match a URL whose **last** segment has a dot (`/docs/v0.2.1`). Pages below it (`/docs/v0.2.1/introduction`) are fine, and the selectors never link to a bare version root unless it has an `index.md`. If you add one, drop `:nonfile` from the route.
57+
58+
## Component previews
59+
60+
- **`razor:preview` fences render everything** — several sibling components, HTML wrappers (`<div class="flex gap-2">…</div>`) and text, in order, inside one frame. The source tab shows the whole fence.
61+
- **Razor-shaped attribute values work:** `Variant="ButtonVariant.Destructive"`, `@ButtonVariant.Destructive`, `@true`, `@42`, `[Flags]` values as `Bold | Italic`.
62+
- **Attributes a static preview can't evaluate are skipped, not fatal:** `OnClick="HandleClick"`, `@onclick`, `@bind-*`, `@ref`, non-primitive parameter types, unparseable values. Each logs a warning and the component still renders.
63+
- **Stateful demos from real files.** For demos that need `@code` (dialogs, bound selects, toasts, charts with data), write a `.razor` component, register it, and drop `<DemoPreview Component="ButtonClickDemo" Title="Optional" />` into markdown. The source tab shows `{DemoSourceRoot}/**/ButtonClickDemo.razor`:
64+
65+
```csharp
66+
o.RegisterComponentsFromAssembly<App>("MyDocs.Demos");
67+
o.DemoSourceRoot = Path.Combine(builder.Environment.ContentRootPath, "Demos");
68+
```
69+
70+
The Razor SDK drops `.razor` files from build/publish output, so ship them explicitly. Use a `None` item, because `Content Update` doesn't survive publish:
71+
72+
```xml
73+
<None Include="Demos/**/*.razor" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
74+
```
75+
4276
## Package family
4377

4478
| Package | Purpose |
4579
|---|---|
4680
| [`ShellDocs.CLI`](src/ShellDocs.CLI) | Global tool. `shelldocs init`, `add`, `dev`, `build` |
47-
| [`ShellDocs.Components`](src/ShellDocs.Components) | RCL. Chrome (layout, sidebar, header, search) + content primitives (Callout, Card, Steps, CodeGroup, FileTree, TypeTable, ComponentPreview) |
81+
| [`ShellDocs.Components`](src/ShellDocs.Components) | RCL. Chrome (layout, sidebar, header, search, version/package selectors) + content primitives (Callout, Card, Steps, CodeGroup, FileTree, TypeTable, ComponentPreview, DemoPreview). Icons via [ShellIcons.Blazor](https://www.nuget.org/packages/ShellIcons.Blazor) |
4882
| [`ShellDocs.Markdown`](src/ShellDocs.Markdown) | Markdig pipeline. Frontmatter parser, `razor:preview` fence extractor, inline Razor tag extractor, per-property type coercion |
4983
| [`ShellDocs.Core`](src/ShellDocs.Core) | Navigation graph, search index, plain-text extraction. No UI |
5084
| [`ShellDocs.Tokens`](src/ShellDocs.Tokens) | Design-system CSS variables. shadcn-compatible names for interop with ShellUI and Tailwind-shaped design systems |

‎docs/ARCHITECTURE.md‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,25 @@ Cache result — rebuild only when `dev` mode detects a change.
260260
`NavigationGraph.GetBreadcrumb(node)`:
261261
- Walk up `Parent` chain; O(depth)
262262

263+
`NavigationGraph.GetPrevNext(node, inScope)` / `FindFolder(url)` / `FirstPageUnder(url)`:
264+
- Scoped prev/next (skips out-of-scope neighbours), folder-section lookup by URL, first page under a URL prefix.
265+
266+
### Versions
267+
268+
`ShellDocsOptions.Versions` (`AddVersion(id, label, rootUrl, description, latest)`) maps each docs version to a content folder URL. `DocsVersionResolver` (singleton, stateless — the current path is always passed in) answers every version question the chrome asks:
269+
270+
| Question | Answer |
271+
|---|---|
272+
| Current version | Version whose `RootUrl` is a segment-aware prefix of the path (`UrlPath.IsUnder`), longest wins → else `latest` → else `Versions[0]` |
273+
| Sidebar nodes | Inside a version: that version folder node's `Children` (`FindFolder`). Otherwise `Root.Children` |
274+
| Version switch href | Same relative page in the target version → current package's root in the target version → target version's first page |
275+
| Package href | `{version}` token replaced with the current version's `Id`; a versioned root that isn't a page lands on its first page |
276+
| Prev/next | `GetPrevNext(node, n => InSameScope(n.Url, node.Url))` |
277+
| Search | Entries in the current version + entries outside every version root |
278+
| Breadcrumb | Version folder node removed |
279+
280+
All answers are computed at render time and emitted as plain `<a href>`, so prerendered/static pages behave identically; dropdown open/close is `shelldocs.js` delegation on `.pkg`/`.ver`.
281+
263282
### `meta.json` schema
264283

265284
```json
@@ -338,7 +357,11 @@ Standard Blazor primitive — takes `Type` + `IReadOnlyDictionary<string, object
338357

339358
Then the plain HTML with `data-slot` placeholders is emitted alongside; the DOM ends up interleaved. Details in the [markdown pipeline notes](#markdown-pipeline-internals).
340359

341-
**Parameter serialization:** frontmatter values are strings from YAML. `TypeRegistry` inspects each component's `[Parameter]` properties to know the target type and coerces (int, bool, enum, string). Complex types (records, DTOs) need JSON literals in the markdown.
360+
**Parameter serialization:** attribute values are strings. `SlotRenderer.BuildParameters` matches them to `[Parameter]` properties (case-insensitively) and coerces string, bool, char, numeric primitives and enums, accepting Razor forms: a leading `@`, `@( … )`, `Type.Member` enum values, `A | B` flags, numeric suffixes, `@null`. Anything else is skipped with a logged warning instead of throwing: directive attributes (`@onclick`, `@bind-*`, `@ref`), delegate/`EventCallback` params, unsupported types, bad values, and unknown attributes on components without a `CaptureUnmatchedValues` catch-all.
361+
362+
**`razor:preview` fences** are parsed by `PreviewParser` (a hand-rolled, quote- and `@( … )`-aware tag scanner) into `PreviewSlot.Nodes`: component, element and text nodes in source order. `SlotRenderer.RenderNodes` emits them as real render-tree elements and components, so wrappers keep their children under interactive re-renders too. Razor-only constructs (`@code { }`, `@* *@`, `@using` lines) are skipped for rendering but stay in the source view.
363+
364+
**`<DemoPreview Component="X" />`** renders registered component `X` inside `PreviewFrame` and reads `{DemoSourceRoot}/**/X.razor` (shallowest match, cached and re-read when the file's write time changes) for the source tab.
342365

343366
---
344367

0 commit comments

Comments
 (0)