generated from coulomb/repo-seed
dashboard: add toc-sidebar utility; move live indicator into TOC column
Extracts the TOC-injection pattern into a reusable component:
src/components/toc-sidebar.js
injectTocTop(id, element) — prepends an element to #observablehq-toc,
removing any previous instance with the same id first so reactive cells
can re-inject on each poll without accumulating duplicates.
decisions.md now uses injectTocTop to place a single widget (live
indicator + Decision Health KPI box) into the right-column sidebar,
removing the standalone live-indicator cell and the ad-hoc id/remove
pattern that was previously inlined.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
29
dashboard/src/components/toc-sidebar.js
Normal file
29
dashboard/src/components/toc-sidebar.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* toc-sidebar — inject a persistent widget into Observable Framework's
|
||||
* right-column table-of-contents sidebar.
|
||||
*
|
||||
* Observable Framework renders a non-scrolling TOC aside (#observablehq-toc)
|
||||
* in the right column. This helper lets you prepend a custom element to it,
|
||||
* replacing any previously injected element with the same id on each call so
|
||||
* reactive cells can refresh the widget without accumulating duplicates.
|
||||
*
|
||||
* Usage:
|
||||
* import {injectTocTop} from "./components/toc-sidebar.js";
|
||||
*
|
||||
* const el = html`<div>…</div>`;
|
||||
* injectTocTop("my-widget-id", el); // call again on each reactive update
|
||||
*
|
||||
* @param {string} id Stable id used to find and remove the previous
|
||||
* instance. Must be unique per widget on the page.
|
||||
* @param {HTMLElement} element Element to inject. Its id will be set to `id`.
|
||||
* @returns {boolean} true if injected into the TOC sidebar;
|
||||
* false if #observablehq-toc was not found.
|
||||
*/
|
||||
export function injectTocTop(id, element) {
|
||||
document.getElementById(id)?.remove();
|
||||
element.id = id;
|
||||
const toc = document.querySelector("#observablehq-toc");
|
||||
if (!toc) return false;
|
||||
toc.prepend(element);
|
||||
return true;
|
||||
}
|
||||
@@ -103,7 +103,8 @@ function fmtDuration(ms) {
|
||||
# Decisions
|
||||
|
||||
```js
|
||||
import {withDocHelp} from "./components/doc-overlay.js";
|
||||
import {withDocHelp} from "./components/doc-overlay.js";
|
||||
import {injectTocTop} from "./components/toc-sidebar.js";
|
||||
|
||||
// ── KPI computation (uses full data, not filtered) ──────────────────────────
|
||||
const _resolved5 = data
|
||||
@@ -150,22 +151,16 @@ const _kpiBox = html`<div class="kpi-infobox">
|
||||
|
||||
withDocHelp(_kpiBox, "/docs/decisions-kpi");
|
||||
|
||||
// ── Inject into the TOC sidebar (right column, non-scrolling) ───────────────
|
||||
_kpiBox.id = "decisions-kpi-box";
|
||||
const _toc = document.querySelector("#observablehq-toc");
|
||||
if (_toc) {
|
||||
document.getElementById("decisions-kpi-box")?.remove();
|
||||
_toc.prepend(_kpiBox);
|
||||
} else display(html`<div style="float:right;width:215px;margin-left:1rem">${_kpiBox}</div>`);
|
||||
```
|
||||
|
||||
```js
|
||||
display(html`<div class="live-indicator">
|
||||
// ── Build sidebar widget: live indicator + KPI box ──────────────────────────
|
||||
const _liveEl = html`<div class="live-indicator">
|
||||
<span style="color:${_ok ? 'var(--theme-foreground-focus)' : 'red'}">●</span>
|
||||
${_ok
|
||||
? `Live · updated ${_ts?.toLocaleTimeString()}`
|
||||
: html`<span style="color:red">Offline — run: <code>make api</code></span>`}
|
||||
</div>`);
|
||||
</div>`;
|
||||
|
||||
const _tocInjected = injectTocTop("decisions-sidebar", html`<div>${_liveEl}${_kpiBox}</div>`);
|
||||
if (!_tocInjected) display(html`<div style="float:right;width:215px;margin-left:1rem">${_liveEl}${_kpiBox}</div>`);
|
||||
```
|
||||
|
||||
## Resolution History
|
||||
|
||||
Reference in New Issue
Block a user