fix(chrome): port sidebar / package selector / TOC / preview to plain JS so static deploys work - #26
Conversation
- Updated ComponentPreview and PreviewFrame components to streamline the expand/collapse functionality and source/code viewing. - Removed unnecessary state management and JavaScript interop for showing/hiding source and code, replacing it with data attributes for better performance and maintainability. - Enhanced accessibility by ensuring buttons have appropriate aria-labels and visual feedback for copy actions.
There was a problem hiding this comment.
🟡 Changes recommended
The preview components now always render both collapsed and expanded UI regions (and both copy/check icons) without corresponding CSS/JS visibility logic, which will produce incorrect UI in practice.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR restores key “chrome” interactivity (sidebar toggles, package dropdown, TOC scroll-spy, and preview source expand/copy) for static-prerendered deployments by moving event handling from Blazor @onclick state to delegated DOM-based JavaScript behavior in shelldocs.js, while updating affected Razor components to emit stable DOM hooks (data-* attributes / CSS classes).
Changes:
- Added a
window.shelldocsChromeJS module with delegated click handlers and TOC auto-init onDOMContentLoaded/enhancedload. - Updated Sidebar, PackageSelector, TOC, and Preview components to rely on DOM state (
data-open,.collapsed/.expanded,data-preview-*) instead of Blazor runtime state. - Bumped version to
0.1.7-alphaand documented the change set inCHANGELOG.md.
File summaries
| File | Description |
|---|---|
| src/ShellDocs.Components/wwwroot/shelldocs.js | Adds delegated JS handlers for sidebar/package/preview and re-inits TOC on navigation events. |
| src/ShellDocs.Components/Chrome/DocsSidebarNode.razor | Removes Blazor click toggling and relies on JS + data-open for section expand/collapse. |
| src/ShellDocs.Components/Chrome/PackageSelector.razor | Converts dropdown behavior to JS-driven open/close and uses native link navigation for selection. |
| src/ShellDocs.Components/Chrome/PackageSelector.razor.css | Makes menu always renderable and toggles visibility via parent [data-open]. |
| src/ShellDocs.Components/Chrome/TableOfContents.razor | Emits data-toc-* for JS to attach scroll-spy without Blazor lifecycle hooks. |
| src/ShellDocs.Components/Content/PreviewFrame.razor | Switches expand/collapse + copy to JS-driven toggles via data-preview-*. |
| src/ShellDocs.Components/Content/ComponentPreview.razor | Switches source expand/collapse + copy to JS-driven toggles via data-preview-*. |
| CHANGELOG.md | Adds a 0.1.7-alpha entry describing the static interactivity fixes and removed code paths. |
| Directory.Build.props | Bumps package version from 0.1.6-alpha to 0.1.7-alpha. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <button type="button" class="pkg-trigger" aria-haspopup="listbox" aria-expanded="false"> | ||
| <span class="pkg-mark"> | ||
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"> | ||
| <path d="@(Selected.IconPath ?? DefaultIcon)"/> | ||
| </svg> |
| <pre class="component-preview-source language-razor"><code class="language-razor">@_source</code></pre> | ||
| <div class="component-preview-fade"> | ||
| <button type="button" class="component-preview-expand" data-preview-toggle="expand">View source</button> | ||
| </div> | ||
| <div class="component-preview-actions"> | ||
| <button type="button" class="component-preview-copy" data-preview-copy aria-label="Copy source"> | ||
| <svg class="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> | ||
| <svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> | ||
| </button> | ||
| <button type="button" class="component-preview-hide" data-preview-toggle="collapse">Hide</button> | ||
| </div> |
| <pre class="preview-code language-razor"><code class="language-razor">@Preview!.Code</code></pre> | ||
| <div class="preview-fade"> | ||
| <button type="button" class="preview-expand" data-preview-toggle="expand">View Code</button> | ||
| </div> | ||
| <div class="preview-code-actions"> | ||
| <button type="button" class="preview-copy" data-preview-copy aria-label="Copy code"> | ||
| <svg class="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> | ||
| <svg class="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> | ||
| </button> | ||
| <button type="button" class="preview-collapse" data-preview-toggle="collapse">Collapse</button> | ||
| </div> |
| var code = frame.querySelector('pre code'); | ||
| if (!code) return; | ||
| var text = code.innerText; | ||
| var writeText = navigator.clipboard && navigator.clipboard.writeText | ||
| ? navigator.clipboard.writeText(text) | ||
| : Promise.reject(new Error('clipboard unavailable')); | ||
| writeText.then(function () { | ||
| copy.classList.add('copied'); | ||
| setTimeout(function () { copy.classList.remove('copied'); }, 1400); | ||
| }).catch(function () { /* silent */ }); | ||
| } |
| // Toggle handled by shelldocs.js — kept as a no-op just in case the | ||
| // Blazor lifecycle re-renders and needs the initial state stable. | ||
|
|
Summary
Fixes the interactivity gap the
0.1.5-alphastatic-prerender pipeline opened up. Four chrome interactions — sidebar section expand/collapse, package selector dropdown,TableOfContentsscroll-spy,PreviewFramesource view — were written as ordinary interactive Razor components with@onclickhandlers that mutate[Parameter] boolstate and re-render viaStateHasChanged(). Prerendered HTML captured only the initial state; on a static host with no Blazor runtime, every one of those interactions was dead on the deployed site.The
0.1.5-alphaCHANGELOG glossed this as "SignalR-backed component state doesn't survive the static build — but a docs site doesn't need it." That was wrong. A docs site's primary navigation surface is the sidebar; if you can't expand a section, you can't reach the pages under it.This PR ports each interaction to plain JS in
shelldocs.js, following the same pattern the four already-working chrome features use (theme toggle, Cmd-K search, code-block copy, CodeGroup tabs). The DOM emits the same initial state Blazor's server-side render produces; delegated document-level click listeners handle the mutations on top. Works identically under a live Blazor runtime (dev mode, Server-hosted deploys) and on plain static hosts (GH Pages, Cloudflare Pages, Netlify).What's in
Fixed
Sidebar section expand/collapse works on static-hosted builds.
DocsSidebarNode.razorno longer routes clicks through Blazor's@onclick="Toggle"+_isOpenstate.shelldocs.jsattaches a delegatedclicklistener on.sidebar-section-toggleand flips[data-open]on the ancestor.sidebar-sectionand its.sidebar-section-shellchild. The initial[data-open]value (fromOnParametersSet's active-path check) still comes from server rendering — the ancestor of the current page pre-expands correctly on first paint. CSS unchanged; already selected on[data-open]for both the chevron rotation and the grid-rows animation.Package selector dropdown opens/closes on static-hosted builds.
PackageSelector.razoralways renders the.pkg-menunow (previously conditional on_open); CSS hides it underdisplay: noneunless.pkg[data-open="true"]. Delegated JS handler on.pkg-triggerflips[data-open]; outside-click closes any open menu. Options render as plain<a href={RootUrl}>— native navigation, no Blazor round-trip on selection.TableOfContentsscroll-spy attaches on static-hosted builds. Removed theOnAfterRenderAsync→shelldocsToc.attachinvocation (which only fires with a live Blazor runtime). The TOC list emits[data-toc-list]+[data-toc-ids="id1,id2,..."];shelldocs.jsscans for these onDOMContentLoadedand after Blazorenhancedloadand callsshelldocsToc.attachitself. Anchor click uses nativehref="#id"navigation.PreviewFrameandComponentPreviewsource-view expand/collapse work on static-hosted builds. Removed@onclick="Expand"/Collapse/Show/Hideand the_expanded/_showSourcestate fields. Buttons carrydata-preview-toggle="expand|collapse";shelldocs.jstoggles the same.expanded/.collapsedclasses the Blazor state used to toggle. The copy button follows the same pattern ([data-preview-copy]).How the JS is wired
New
shelldocsChromemodule inwwwroot/shelldocs.js. Delegated document-levelclicklisteners for the sidebar, package selector, and preview interactions — they survive Blazor's enhanced-nav DOM swap without re-attaching. TOC scroll-spy is the exception: heading IDs change per page, soinitToc()re-runs onenhancedload.Removed (dead code)
DocsSidebarNode.Toggle().PackageSelector._open,Toggle(),Choose(),OnBlur().TableOfContents._handle/_sig/Scroll(),IJSObjectReferencefield,IAsyncDisposableimplementation.PreviewFrame._expanded/_copied/_highlighted/_codeEl,Expand()/Collapse()/Copy().ComponentPreview._showSource/_copied/_highlighted/_sourceEl,Show()/Hide()/Copy().Files
Modified — 9:
src/ShellDocs.Components/wwwroot/shelldocs.js— newshelldocsChromemodule (~85 lines)src/ShellDocs.Components/Chrome/DocsSidebarNode.razorsrc/ShellDocs.Components/Chrome/PackageSelector.razorsrc/ShellDocs.Components/Chrome/PackageSelector.razor.csssrc/ShellDocs.Components/Chrome/TableOfContents.razorsrc/ShellDocs.Components/Content/PreviewFrame.razorsrc/ShellDocs.Components/Content/ComponentPreview.razorCHANGELOG.md— new[0.1.7-alpha]sectionDirectory.Build.props—0.1.6-alpha→0.1.7-alpha