Skip to content

Commit 57603f7

Browse files
authored
Merge pull request #4 from shellui-dev/feat/components-shell
feat: components shell — sidebar, TOC, mobile nav, live razor:preview
2 parents a12fc62 + 54cfc83 commit 57603f7

56 files changed

Lines changed: 3936 additions & 41 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/ROADMAP.md

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ See [DESIGN.md](DESIGN.md) for the high-level design and [ARCHITECTURE.md](ARCHI
2626
| `ShellDocs.Core` | Navigation graph, search index model, routing helpers |
2727
| `ShellDocs.Markdown` | Markdig pipeline + frontmatter + Razor component embedding |
2828
| `ShellDocs.Components` | RCL — UI primitives (DocsLayout, CodeBlock, SearchDialog, etc.) |
29+
| `ShellDocs.Tokens` | Shared CSS variable definitions — the palette + spacing scale that both ShellDocs and ShellUI-in-docs consume. Single source of truth for `--background`, `--foreground`, `--primary`, `--radius`, etc. |
2930
| `ShellDocs.CLI` | `shelldocs init`, `shelldocs new`, `shelldocs dev`, `shelldocs build` |
3031
| `ShellDocs.Templates` | Content for CLI scaffolding — starter markdown, meta.json, .csproj patches |
3132
| `ShellDocs.Xml` | v2 — extract `<TypeTable>` from XML doc comments |
@@ -38,17 +39,17 @@ Each branch below annotates which package it touches. Multi-package branches cal
3839

3940
Goal: a consumer can `shelldocs init` an empty Blazor WASM project and get a working docs site with sidebar, header, code blocks, and one theme.
4041

41-
### `chore/repo-scaffolding`
42+
### `chore/repo-scaffolding` — shipped
4243
Bootstrap the monorepo.
4344

44-
- `.sln` with the six package projects (`ShellDocs.Core`, `ShellDocs.Markdown`, `ShellDocs.Components`, `ShellDocs.CLI`, `ShellDocs.Templates`, plus `ShellDocs.Tests`)
45+
- `.slnx` (.NET 10 XML solution format) with all package projects + tests
4546
- `.csproj` files with correct `TargetFramework`, `IsPackable`, `PackageId`
4647
- `.gitignore`, `Directory.Build.props`, `Directory.Packages.props` for central package management
4748
- `.github/workflows/ci.yml` — build + test on every push
4849
- `.github/workflows/release.yml` — pack + push to NuGet on tag
4950
- Nothing shipped; groundwork only
5051

51-
### `feat/core-navigation-graph`
52+
### `feat/core-navigation-graph` — shipped
5253
Ships to `ShellDocs.Core`.
5354

5455
- `NavigationGraph` — tree of `NavigationNode { Url, Title, Description, Category, Order, Headings, Path }`
@@ -57,7 +58,7 @@ Ships to `ShellDocs.Core`.
5758
- Route resolution — `NavigationGraph.ResolveByUrl("/docs/button")` → node
5859
- Unit tests: markdown → tree, meta.json ordering, deep-nested folders
5960

60-
### `feat/markdown-pipeline`
61+
### `feat/markdown-pipeline` — shipped
6162
Ships to `ShellDocs.Markdown`.
6263

6364
- Markdig extension for YAML frontmatter (YamlDotNet)
@@ -67,15 +68,31 @@ Ships to `ShellDocs.Markdown`.
6768
- Type registry — `RegisterComponent<T>()` API for consumer to expose their components to inline tags
6869
- Unit tests: frontmatter parsing, fenced block replacement, tag resolution, unknown-tag graceful degradation
6970

70-
### `feat/components-shell`
71+
### `feat/components-shell` — shipped
7172
Ships to `ShellDocs.Components`.
7273

73-
- `DocsLayout` — full-page grid: header + sidebar + main + TOC placeholder + footer
74-
- `DocsHeader` — logo, primary nav, search-button placeholder, theme toggle, GitHub link
75-
- `DocsSidebar` — grouped nav from navigation graph, collapsible sections, active highlighting
74+
- `DocsLayout` — full-page grid: header + sidebar + main + TOC + footer, fumadocs-shaped
75+
- `DocsHeader` — logo, primary nav with hover mega-menu (icon cards), search-button placeholder, theme toggle, GitHub link, hamburger for mobile
76+
- `DocsSidebar` — grouped nav from navigation graph, collapsible sections (fumadocs pattern — closed by default, active-path auto-open), lucide-style icons per section/page, package selector (ShellDocs · Markdown · Core · CLI · Components), footer bar with GitHub + theme toggle
7677
- `MarkdownContent` — renders a doc page from a `.md` path via `MarkdownRenderer`
77-
- One theme preset baked in (`Shadcn`)
78-
- No search, no TOC, no code highlighting yet — those come in later branches
78+
- `TableOfContents` — right-rail nav (h2/h3), scroll-tracked via multi-active headings so the thumb slides smoothly, SVG-mask + coloured thumb pattern lifted from fumadocs' `ClerkTOCItems`
79+
- `PrevNextNav` — bordered cards, arrow icon square, translate-on-hover
80+
- `MobileNavState` service + fixed drawer + backdrop + auto-close on route change
81+
- Prism.js syntax highlighting (via CDN for now — Shiki lands in the next branch)
82+
- Neutral fumadocs-shaped palette (`--background`, `--foreground`, `--primary`, `--muted`, `--accent`, `--border`) — will move to `ShellDocs.Tokens` in the next branch
83+
84+
### `feat/design-tokens`**next**
85+
Ships to new package `ShellDocs.Tokens`.
86+
87+
Extracts the palette + scale from `ShellDocs.Components/wwwroot/shelldocs-theme.css` into its own package so ShellUI (and any third-party consumer) can depend on the *tokens* without pulling in the whole components RCL.
88+
89+
- New `ShellDocs.Tokens` project — RCL that ships a single `wwwroot/tokens.css` with `:root` + `:root.dark` variable definitions
90+
- `ShellDocs.Components` and `ShellDocs.Preview` update their `App.razor` link to `_content/ShellDocs.Tokens/tokens.css` and remove the inline theme file
91+
- Add a `tokens-full.css` variant for consumers who want the extended set (semantic + chart colors), and a `tokens-base.css` for consumers who only want the core palette
92+
- Document the token contract in `docs/TOKENS.md`: which names are stable, which are internal, and how to override
93+
- **ShellUI integration path (Tailwind install):** ShellUI's Tailwind config reads the same `--primary`, `--background`, `--border` etc. — nothing changes on their side. Consumer just references `tokens.css` and both design systems light up together.
94+
- **ShellUI integration path (NuGet install):** ShellUI's RCL detects `ShellDocs.Tokens` at runtime and skips emitting its own token file. Deferred to Phase 3 — needs a small opt-in flag on `AddShellUI()`.
95+
- Unit tests: token file emits, dark-mode class toggling, no duplicate declarations across bundles
7996

8097
### `feat/codeblock-shiki`
8198
Ships to `ShellDocs.Components`.
@@ -303,6 +320,14 @@ ShellDocs depends on `ShellUI.Components` for base primitives:
303320

304321
**Locked ShellUI version:** ShellDocs targets `ShellUI.Components >= 0.5.0` (the version that ships `feat/data-selection-suite` — CommandPalette is required). Bumps require a ShellDocs major/minor.
305322

323+
### Install path — Tailwind-first, NuGet later
324+
325+
Mirrors how shadcn interops with fumadocs: shared CSS variables on `:root`, both design systems read them, one visual language.
326+
327+
- **Phase 1–2 (now):** ShellUI ships as a Tailwind consumer. Doc site owners install ShellUI the same way they would in any Blazor app — via `shellui add card` etc. — and the components read the same `--primary`, `--muted`, `--border` tokens that ShellDocs emits. Zero interop work; a `<Card>` written mid-markdown just picks up the ShellDocs palette. `ShellDocs.Tokens` (next branch) formalizes the contract so both packages point at the same source of truth.
328+
- **Phase 3+ (`feat/shellui-nuget-interop`):** teach the ShellUI NuGet RCL to defer to `ShellDocs.Tokens` when it's on the classpath, so shipping both packages doesn't double-emit `:root` blocks. Small change — an `AddShellUI(o => o.UseSharedTokens())` opt-in. Deferred because it's not blocking for real docs sites — Tailwind consumers get 90% of the value today, and the NuGet story only matters for pure-server projects that don't run Tailwind.
329+
- **Not doing:** shipping a "ShellUI-NuGet-only" install story for now. It'd double the QA surface for zero customer wins on day one. Revisit when a real consumer asks.
330+
306331
---
307332

308333
## Effort estimates (rough)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@using ShellDocs.Preview.Components
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>ShellDocs — the docs framework for .NET</title>
8+
<base href="/" />
9+
<link rel="stylesheet" href="_content/ShellDocs.Components/shelldocs-theme.css" />
10+
<link rel="stylesheet" href="ShellDocs.Preview.styles.css" />
11+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" media="(prefers-color-scheme: dark)" />
12+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" media="(prefers-color-scheme: light)" />
13+
<script>
14+
(function () {
15+
var q = new URLSearchParams(location.search).get('theme');
16+
var saved = null;
17+
try { saved = localStorage.getItem('shelldocs-theme'); } catch (e) {}
18+
var systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
19+
var mode = q || saved || (systemDark ? 'dark' : 'light');
20+
if (mode === 'dark') document.documentElement.classList.add('dark');
21+
})();
22+
</script>
23+
<HeadOutlet @rendermode="RenderMode.InteractiveServer" />
24+
</head>
25+
<body>
26+
<Routes @rendermode="RenderMode.InteractiveServer" />
27+
<script src="_content/ShellDocs.Components/shelldocs.js"></script>
28+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
29+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-csharp.min.js"></script>
30+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
31+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
32+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
33+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup-templating.min.js"></script>
34+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
35+
<script src="_framework/blazor.web.js"></script>
36+
</body>
37+
</html>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
@* Demo Callout — a temporary implementation local to the preview app.
2+
The production <Callout> ships in feat/content-primitives (Phase 2). *@
3+
4+
<div class="demo-callout demo-callout-@Variant.ToLowerInvariant()">
5+
<span class="demo-callout-icon" aria-hidden="true">
6+
@switch (Variant.ToLowerInvariant())
7+
{
8+
case "warning":
9+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
10+
break;
11+
case "danger":
12+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
13+
break;
14+
case "tip":
15+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7c.5.5 1 1.3 1 2.3v1h6v-1c0-1 .5-1.8 1-2.3A7 7 0 0 0 12 2z"/></svg>
16+
break;
17+
default:
18+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
19+
break;
20+
}
21+
</span>
22+
<div class="demo-callout-body">
23+
@if (!string.IsNullOrEmpty(Title))
24+
{
25+
<div class="demo-callout-title">@Title</div>
26+
}
27+
<div class="demo-callout-content">
28+
@if (ChildContent is not null) { @ChildContent }
29+
else { @Text }
30+
</div>
31+
</div>
32+
</div>
33+
34+
@code {
35+
[Parameter] public string Variant { get; set; } = "info";
36+
[Parameter] public string? Title { get; set; }
37+
[Parameter] public string? Text { get; set; }
38+
[Parameter] public RenderFragment? ChildContent { get; set; }
39+
}
40+
41+
<style>
42+
.demo-callout {
43+
display: flex;
44+
gap: 0.85rem;
45+
padding: 0.9rem 1.1rem;
46+
border: 1px solid var(--border);
47+
border-radius: calc(var(--radius) + 2px);
48+
background: color-mix(in oklch, var(--card) 70%, var(--background));
49+
line-height: 1.6;
50+
font-size: 0.9rem;
51+
}
52+
.demo-callout-icon { flex-shrink: 0; display: inline-flex; align-items: flex-start; padding-top: 0.05rem; }
53+
.demo-callout-icon svg { width: 1.05rem; height: 1.05rem; }
54+
.demo-callout-body { flex: 1; min-width: 0; }
55+
.demo-callout-title { font-weight: 600; margin-bottom: 0.2rem; letter-spacing: -0.005em; }
56+
.demo-callout-content { color: var(--muted-foreground); }
57+
.demo-callout-content > *:first-child { margin-top: 0; }
58+
.demo-callout-content > *:last-child { margin-bottom: 0; }
59+
60+
.demo-callout-info { border-color: color-mix(in oklch, var(--info) 30%, var(--border)); }
61+
.demo-callout-info .demo-callout-icon { color: var(--info); }
62+
.demo-callout-warning { border-color: color-mix(in oklch, var(--warning) 35%, var(--border)); }
63+
.demo-callout-warning .demo-callout-icon { color: var(--warning); }
64+
.demo-callout-danger { border-color: color-mix(in oklch, var(--error) 35%, var(--border)); }
65+
.demo-callout-danger .demo-callout-icon { color: var(--error); }
66+
.demo-callout-tip { border-color: color-mix(in oklch, var(--success) 30%, var(--border)); }
67+
.demo-callout-tip .demo-callout-icon { color: var(--success); }
68+
</style>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
@page "/blog"
2+
@layout HomeLayout
3+
4+
<PageTitle>Blog — ShellDocs</PageTitle>
5+
6+
<div class="blog-wrap">
7+
<header class="blog-hero">
8+
<span class="eyebrow">Blog</span>
9+
<h1>Notes from the ShellDocs team</h1>
10+
<p>Release notes, deep-dives, and the occasional postmortem. Written by the same people building the framework.</p>
11+
</header>
12+
13+
<div class="posts">
14+
@foreach (var post in _posts)
15+
{
16+
<a class="post" href="@post.Href">
17+
<div class="post-meta">
18+
<span class="post-tag">@post.Tag</span>
19+
<time>@post.Date</time>
20+
</div>
21+
<h2>@post.Title</h2>
22+
<p>@post.Excerpt</p>
23+
<div class="post-foot">
24+
<div class="post-author">
25+
<span class="post-avatar" style="background:@post.AvatarBg">@post.AuthorInitials</span>
26+
<span>@post.Author</span>
27+
</div>
28+
<span class="post-read">@post.ReadTime</span>
29+
</div>
30+
</a>
31+
}
32+
</div>
33+
</div>
34+
35+
@code {
36+
private record Post(string Title, string Excerpt, string Tag, string Date, string Author, string AuthorInitials, string AvatarBg, string ReadTime, string Href);
37+
38+
private static readonly List<Post> _posts = new()
39+
{
40+
new("Announcing ShellDocs 0.1.0-alpha",
41+
"The docs framework we wanted for .NET. Markdown-first, Razor-native, Cmd+K searchable, deploys anywhere. Here's what shipped and what's next on the roadmap.",
42+
"Release", "Jul 12 · 2026", "Shephard Tseisi", "ST", "oklch(0.65 0.18 260)", "6 min read", "#"),
43+
44+
new("Why we built the TOC three times",
45+
"The story of every fumadocs-inspired scroll spy we shipped, why they broke on Blazor Server, and the 50-line version we ended up with. A lesson in doing less.",
46+
"Engineering", "Jul 9 · 2026", "Shephard Tseisi", "ST", "oklch(0.7 0.18 200)", "9 min read", "#"),
47+
48+
new("Frontmatter, not folders",
49+
"How ShellDocs decides sidebar ordering, breadcrumbs, and prev/next navigation from a single YAML block. Plus: the meta.json escape hatch for when you need it.",
50+
"Design", "Jul 3 · 2026", "Shephard Tseisi", "ST", "oklch(0.72 0.19 320)", "5 min read", "#"),
51+
52+
new("Razor components mid-markdown, live",
53+
"A short walkthrough of how <Button /> mid-markdown becomes a real Blazor component at render time. Includes the parsing rules and why we chose them.",
54+
"Engineering", "Jun 27 · 2026", "Shephard Tseisi", "ST", "oklch(0.7 0.18 200)", "7 min read", "#"),
55+
56+
new("The Blazor Server signalR trap",
57+
"Why round-tripping every scroll event through SignalR is a bad idea, and the pattern we use to keep Blazor out of hot animation loops. Applies to any Blazor Server app.",
58+
"Engineering", "Jun 20 · 2026", "Shephard Tseisi", "ST", "oklch(0.7 0.18 200)", "8 min read", "#"),
59+
60+
new("How shellui.dev is being rebuilt on ShellDocs",
61+
"A dogfood tour: the ShellUI docs site is moving from custom Blazor pages to ShellDocs' primitives. Screenshots, migration notes, and every rough edge we hit.",
62+
"Case study", "Jun 14 · 2026", "Shephard Tseisi", "ST", "oklch(0.68 0.2 30)", "10 min read", "#")
63+
};
64+
}
65+
66+
<style>
67+
.blog-wrap { max-width: 60rem; margin: 0 auto; padding: 5rem 1.5rem 6rem; }
68+
69+
.blog-hero { text-align: center; margin-bottom: 3.5rem; }
70+
.blog-hero .eyebrow {
71+
display: inline-block;
72+
font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em;
73+
color: var(--muted-foreground); font-weight: 600;
74+
padding: 0.2rem 0.6rem; border: 1px solid var(--border); border-radius: 9999px;
75+
margin-bottom: 1rem;
76+
}
77+
.blog-hero h1 { font-size: 2.5rem; font-weight: 700; letter-spacing: -0.03em; margin: 0 0 0.75rem; line-height: 1.1; }
78+
.blog-hero p { margin: 0 auto; max-width: 34rem; color: var(--muted-foreground); font-size: 1rem; line-height: 1.6; }
79+
@@media (min-width: 768px) { .blog-hero h1 { font-size: 3rem; } }
80+
81+
.posts { display: flex; flex-direction: column; gap: 1rem; }
82+
.post {
83+
display: flex; flex-direction: column; gap: 0.5rem;
84+
padding: 1.5rem;
85+
border: 1px solid var(--border);
86+
border-radius: calc(var(--radius) + 2px);
87+
background: var(--card);
88+
color: var(--foreground);
89+
text-decoration: none;
90+
transition: border-color 200ms, background 200ms, transform 200ms;
91+
}
92+
.post:hover { border-color: var(--border-strong); background: var(--muted); transform: translateY(-2px); }
93+
.post-meta {
94+
display: flex; align-items: center; gap: 0.75rem;
95+
font-size: 0.75rem; color: var(--muted-foreground);
96+
}
97+
.post-tag {
98+
display: inline-flex; align-items: center;
99+
padding: 0.15rem 0.55rem;
100+
background: var(--muted); border: 1px solid var(--border); border-radius: 9999px;
101+
font-size: 0.7rem; font-weight: 500; color: var(--foreground);
102+
letter-spacing: -0.005em;
103+
}
104+
.post h2 { margin: 0; font-size: 1.25rem; font-weight: 600; letter-spacing: -0.015em; line-height: 1.3; }
105+
.post p { margin: 0; color: var(--muted-foreground); font-size: 0.9rem; line-height: 1.55; }
106+
.post-foot {
107+
display: flex; align-items: center; justify-content: space-between;
108+
margin-top: 0.5rem; padding-top: 0.9rem;
109+
border-top: 1px solid var(--border);
110+
font-size: 0.75rem; color: var(--muted-foreground);
111+
}
112+
.post-author { display: inline-flex; align-items: center; gap: 0.5rem; }
113+
.post-avatar {
114+
display: inline-flex; align-items: center; justify-content: center;
115+
width: 1.4rem; height: 1.4rem;
116+
border-radius: 9999px;
117+
font-size: 0.6rem; font-weight: 700; color: white;
118+
letter-spacing: 0;
119+
}
120+
</style>

0 commit comments

Comments
 (0)