@kolkrabbi/kol-workshop 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/exhibit/ExhibitCard.jsx +29 -0
- package/src/exhibit/ExhibitLinkCard.jsx +69 -0
- package/src/exhibit/ExhibitOverview.jsx +69 -0
- package/src/exhibit/ExhibitPage.jsx +80 -0
- package/src/exhibit/ExhibitSidebar.jsx +92 -0
- package/src/exhibit/index.js +24 -0
- package/src/exhibit/useExhibitToc.js +32 -0
- package/src/index.js +5 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-workshop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "KOL workshop/docs system — a handrolled markdown engine, search, tag system (incl. a d3 tag graph), docs viewer, and docs shell. Sits above @kolkrabbi/kol-{theme,icons,component,framework}; content is consumer-injected (the package never globs docs).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"d3": "^7.9.0",
|
|
16
|
-
"@kolkrabbi/kol-
|
|
17
|
-
"@kolkrabbi/kol-
|
|
18
|
-
"@kolkrabbi/kol-
|
|
19
|
-
"@kolkrabbi/kol-
|
|
20
|
-
"@kolkrabbi/kol-
|
|
16
|
+
"@kolkrabbi/kol-component": "^0.39.0",
|
|
17
|
+
"@kolkrabbi/kol-icons": "^0.16.0",
|
|
18
|
+
"@kolkrabbi/kol-framework": "^0.20.0",
|
|
19
|
+
"@kolkrabbi/kol-theme": "^0.41.0",
|
|
20
|
+
"@kolkrabbi/kol-brand": "^0.1.2"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": "^18.3.0 || ^19.0.0",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExhibitCard — the specimen header inside an exhibit section: what this
|
|
3
|
+
* variant is called, what it does, and (optionally) its technical line and its
|
|
4
|
+
* prop signature. Recreated from kol-website's local `DesCard` (37L), which
|
|
5
|
+
* every showcase page in that repo imported by relative path.
|
|
6
|
+
*
|
|
7
|
+
* Card-level hierarchy, one rung under the section's own PageSection title:
|
|
8
|
+
* name `kol-card-value` · description `kol-mono-12` · details/code `kol-mono-10`.
|
|
9
|
+
* The type comes from the role classes, never from a utility stack at the call
|
|
10
|
+
* site — the 2026-07-30 law that a component's type lives in its own rule.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} name variant name — ".text-auto", "Default state"
|
|
13
|
+
* @param {string} [description] one line on what the variant is for
|
|
14
|
+
* @param {string} [details] technical line — variant trigger, classes used
|
|
15
|
+
* @param {string} [code] the prop signature, rendered as code
|
|
16
|
+
*/
|
|
17
|
+
export default function ExhibitCard({ name, description, details, code }) {
|
|
18
|
+
return (
|
|
19
|
+
<div>
|
|
20
|
+
<h4 className="kol-card-value">{name}</h4>
|
|
21
|
+
|
|
22
|
+
{description && <p className="kol-mono-12 text-fg-64 mt-2">{description}</p>}
|
|
23
|
+
|
|
24
|
+
{details && <p className="kol-mono-10 text-fg-48 mt-1">{details}</p>}
|
|
25
|
+
|
|
26
|
+
{code && <code className="block kol-mono-10 text-fg-48 mt-2">{code}</code>}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom'
|
|
2
|
+
import { Icon } from '@kolkrabbi/kol-component'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ExhibitLinkCard — the card an exhibit's landing page uses for each child
|
|
6
|
+
* page. Recreated from kol-website's `OverviewCard`, which carried a standing
|
|
7
|
+
* note on itself: *"vendored verbatim from elder @kol/ui (no DS twin as of
|
|
8
|
+
* kol-dashboards 0.2.0); lobby to the DS or fold into kol-dashboards if a
|
|
9
|
+
* second consumer app appears."* This is that lobby arriving.
|
|
10
|
+
*
|
|
11
|
+
* Internal hrefs render as a router `<Link>`, external ones as a plain anchor —
|
|
12
|
+
* an exhibit's landing grid routinely mixes both (a sub-page beside the live
|
|
13
|
+
* production instance it documents).
|
|
14
|
+
*
|
|
15
|
+
* @param {string} label card title
|
|
16
|
+
* @param {string} [subtitle] one line under the title
|
|
17
|
+
* @param {string} [description] footer line, clamped to one row
|
|
18
|
+
* @param {string} icon glyph name — the corner mark and the empty-state fill
|
|
19
|
+
* @param {string} [image] cover image; replaces the oversized glyph
|
|
20
|
+
* @param {string} href internal path ("/workshop/…") or an absolute URL
|
|
21
|
+
*/
|
|
22
|
+
export default function ExhibitLinkCard({
|
|
23
|
+
label,
|
|
24
|
+
subtitle,
|
|
25
|
+
description,
|
|
26
|
+
icon,
|
|
27
|
+
image,
|
|
28
|
+
href,
|
|
29
|
+
target,
|
|
30
|
+
rel,
|
|
31
|
+
className = '',
|
|
32
|
+
}) {
|
|
33
|
+
const internal = href?.startsWith('/')
|
|
34
|
+
const Tag = internal ? Link : 'a'
|
|
35
|
+
const linkProps = internal ? { to: href } : { href, target, rel }
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Tag
|
|
39
|
+
{...linkProps}
|
|
40
|
+
className={`group flex aspect-[4/3] flex-col gap-3 rounded bg-surface-inverse p-4 ${className}`}
|
|
41
|
+
>
|
|
42
|
+
<div className="flex items-start justify-between">
|
|
43
|
+
<div>
|
|
44
|
+
<h3 className="kol-card-kicker">{label}</h3>
|
|
45
|
+
{subtitle && <p className="kol-mono-10 mt-1 italic text-fg-64">{subtitle}</p>}
|
|
46
|
+
</div>
|
|
47
|
+
<Icon name={icon} size={16} />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div className="flex flex-1 items-center justify-center overflow-hidden rounded border border-fg-08">
|
|
51
|
+
{image ? (
|
|
52
|
+
<img
|
|
53
|
+
src={image}
|
|
54
|
+
alt={label}
|
|
55
|
+
className="h-full w-full scale-100 object-cover object-center transition-transform duration-300 group-hover:scale-105"
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<Icon
|
|
59
|
+
name={icon}
|
|
60
|
+
size={64}
|
|
61
|
+
className="text-auto transition-transform duration-300 group-hover:scale-105"
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{description && <p className="kol-card-tag line-clamp-1 text-fg-48">{description}</p>}
|
|
67
|
+
</Tag>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { PageSection } from '@kolkrabbi/kol-framework'
|
|
2
|
+
import ExhibitLinkCard from './ExhibitLinkCard.jsx'
|
|
3
|
+
import useExhibitToc from './useExhibitToc.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ExhibitOverview — an exhibit's landing page: what this section is, then every
|
|
7
|
+
* child page as a card.
|
|
8
|
+
*
|
|
9
|
+
* The overview contract kol-website's DashboardOverview implements by hand —
|
|
10
|
+
* concept in the intro (with room for a single external opt-in, the live
|
|
11
|
+
* instance the exhibit documents), children as a grid below.
|
|
12
|
+
*
|
|
13
|
+
* ```jsx
|
|
14
|
+
* <ExhibitOverview
|
|
15
|
+
* toc={{ links: DOC_LINKS }}
|
|
16
|
+
* label="Scope: Dashboard — Overview"
|
|
17
|
+
* title="Dashboard"
|
|
18
|
+
* body="Modular dashboards, charts and KPI components…"
|
|
19
|
+
* action={<Button variant="primary" href="https://kolkrabbi.io/metrics">Open metrics</Button>}
|
|
20
|
+
* cards={[{ id: 'setup', label: 'Setup', subtitle: '…', icon: 'book-open', href: '/workshop/dashboard/setup' }]}
|
|
21
|
+
* />
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param {string} title · @param {string} label · @param {string} body the intro section
|
|
25
|
+
* @param {node} [action] one call-to-action under the intro
|
|
26
|
+
* @param {Array} cards child pages: ExhibitLinkCard props, each with an `id`
|
|
27
|
+
* @param {string} [cardsLabel='Pages'] · @param {string} [cardsTitle='Explore'] · @param {string} [cardsBody]
|
|
28
|
+
* @param {object} [toc] ExhibitSidebar props; omit to leave the rail alone
|
|
29
|
+
* @param {string} [id='overview'] anchor id of the intro section
|
|
30
|
+
*/
|
|
31
|
+
export default function ExhibitOverview({
|
|
32
|
+
id = 'overview',
|
|
33
|
+
label,
|
|
34
|
+
title,
|
|
35
|
+
body,
|
|
36
|
+
action,
|
|
37
|
+
cards = [],
|
|
38
|
+
cardsId = 'sections',
|
|
39
|
+
cardsLabel = 'Pages',
|
|
40
|
+
cardsTitle = 'Explore',
|
|
41
|
+
cardsBody,
|
|
42
|
+
toc,
|
|
43
|
+
}) {
|
|
44
|
+
useExhibitToc(toc ?? {})
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
<PageSection id={id} label={label} title={title} body={body}>
|
|
49
|
+
{action && <div className="mt-6">{action}</div>}
|
|
50
|
+
</PageSection>
|
|
51
|
+
|
|
52
|
+
{cards.length > 0 && (
|
|
53
|
+
<PageSection id={cardsId} label={cardsLabel} title={cardsTitle} body={cardsBody}>
|
|
54
|
+
{/* auto-fill from a 17.5rem minimum — the reference implementation's
|
|
55
|
+
* grid, so an exhibit's landing reflows the same everywhere. */}
|
|
56
|
+
<div className="mt-8 grid gap-6 grid-cols-[repeat(auto-fill,minmax(17.5rem,1fr))]">
|
|
57
|
+
{cards.map((card) => (
|
|
58
|
+
<ExhibitLinkCard
|
|
59
|
+
key={card.id}
|
|
60
|
+
{...card}
|
|
61
|
+
description={card.description ?? `Open ${card.label} in the workshop`}
|
|
62
|
+
/>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</PageSection>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PageSection } from '@kolkrabbi/kol-framework'
|
|
2
|
+
import ExhibitCard from './ExhibitCard.jsx'
|
|
3
|
+
import useExhibitToc from './useExhibitToc.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ExhibitPage — an exhibit's inner page, declared as CONTENT.
|
|
7
|
+
*
|
|
8
|
+
* The two page shapes kol-website hand-built per section collapse into one
|
|
9
|
+
* component, because they were never structurally different: a showcase page is
|
|
10
|
+
* a list of PageSections whose bodies are specimen grids, and a prose companion
|
|
11
|
+
* is a list of PageSections whose bodies are paragraphs. A section picks its
|
|
12
|
+
* body by which field it carries.
|
|
13
|
+
*
|
|
14
|
+
* ```jsx
|
|
15
|
+
* <ExhibitPage
|
|
16
|
+
* toc={{ links: DOC_LINKS }}
|
|
17
|
+
* sections={[
|
|
18
|
+
* { id: 'intro', label: 'Dashboard', title: 'Dashboard components',
|
|
19
|
+
* body: 'Cards for metrics, charts and data.' },
|
|
20
|
+
*
|
|
21
|
+
* { id: 'metric-cards', label: 'Metric cards', title: 'DashMetricCard',
|
|
22
|
+
* body: 'Label, value, delta.',
|
|
23
|
+
* specimens: [
|
|
24
|
+
* { name: 'KPI variant', description: 'Left border accent.',
|
|
25
|
+
* details: 'Variant: borderColor present', code: 'label value delta borderColor',
|
|
26
|
+
* demo: <DashMetricCard label="GAMES" value="2,847" borderColor="…" /> },
|
|
27
|
+
* ] },
|
|
28
|
+
*
|
|
29
|
+
* { id: 'tracking', label: 'Tracking', title: 'Umami', prose: true,
|
|
30
|
+
* children: <><p>…</p><p>…</p></> },
|
|
31
|
+
* ]}
|
|
32
|
+
* />
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @param {Array} sections the page, in order. Each: `{ id, label, title, body }`
|
|
36
|
+
* plus at most one body shape — `specimens` (a grid of ExhibitCard-headed
|
|
37
|
+
* demos), or `children` (anything), optionally with `prose: true` for the
|
|
38
|
+
* reading measure. `columns` overrides the specimen grid width (default 2).
|
|
39
|
+
* @param {object} [toc] ExhibitSidebar props — `{ links, sections, basePath, docHref }`.
|
|
40
|
+
* Omit entirely to leave the shell's rail alone.
|
|
41
|
+
*/
|
|
42
|
+
export default function ExhibitPage({ sections = [], toc }) {
|
|
43
|
+
useExhibitToc(toc ?? {})
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div>
|
|
47
|
+
{sections.map(({ id, label, title, body, specimens, children, prose, columns = 2 }) => (
|
|
48
|
+
<PageSection key={id} id={id} label={label} title={title} body={body}>
|
|
49
|
+
{specimens?.length > 0 && (
|
|
50
|
+
<div
|
|
51
|
+
className="mt-8 grid gap-6 items-start"
|
|
52
|
+
style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}
|
|
53
|
+
>
|
|
54
|
+
{specimens.map((s) => (
|
|
55
|
+
<div key={s.name} className="space-y-4">
|
|
56
|
+
<ExhibitCard
|
|
57
|
+
name={s.name}
|
|
58
|
+
description={s.description}
|
|
59
|
+
details={s.details}
|
|
60
|
+
code={s.code}
|
|
61
|
+
/>
|
|
62
|
+
{s.demo}
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{/* The reading measure is the reference implementation's, kept
|
|
69
|
+
* verbatim — kol-website's prose companions all wrap at 60ch on the
|
|
70
|
+
* body-02 rung. */}
|
|
71
|
+
{children && (
|
|
72
|
+
<div className={prose ? 'mt-8 max-w-[60ch] space-y-6 kol-sans-body-02' : 'mt-8'}>
|
|
73
|
+
{children}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</PageSection>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Link, useNavigate } from 'react-router-dom'
|
|
2
|
+
import { DocsToc, Icon } from '@kolkrabbi/kol-component'
|
|
3
|
+
import RailSection from '../shell/RailSection.jsx'
|
|
4
|
+
import RailRow from '../shell/RailRow.jsx'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ExhibitSidebar — the rail block an exhibit page registers into the shell's
|
|
8
|
+
* TOC slot: on-this-page, the section's documentation links, and quick actions.
|
|
9
|
+
* Recreated from kol-website's local `WorkshopSidebarContent`.
|
|
10
|
+
*
|
|
11
|
+
* THE DRIFT FIXED ON RECREATION. The source hand-rolled its own collapsible
|
|
12
|
+
* section: `.shell-sidebar-toggle` AND `.shell-sidebar-label` stacked on one
|
|
13
|
+
* element, inline `paddingRight`/`paddingBottom`/`justifyContent`, and its own
|
|
14
|
+
* chevron at L1. Every one of those is something RailSection exists to prevent
|
|
15
|
+
* — the eyebrow-box law in kol-components-workshop.css says an eyebrow wears
|
|
16
|
+
* ONE box class and sets no y-spacing inline, and the 2026-08-01 ruling says L1
|
|
17
|
+
* draws no chevron (it still collapses; the glyph is simply not there). The
|
|
18
|
+
* rungs come from RailSection now, so this block cannot drift from either rail.
|
|
19
|
+
* `pnpm validate:rails` R3 + R4 assert both halves.
|
|
20
|
+
*
|
|
21
|
+
* Router-agnostic in the same way as WorkshopSidebar: hrefs are built from
|
|
22
|
+
* `basePath`/`docHref`, never from an app singleton.
|
|
23
|
+
*
|
|
24
|
+
* @param {Array} [sections] on-this-page entries for DocsToc: [{ id, title, level }]
|
|
25
|
+
* @param {Array} [links] documentation links: [{ id, label }]
|
|
26
|
+
* @param {string} [basePath] workshop mount point — drives the quick-action hrefs
|
|
27
|
+
* @param {Function} [docHref] `(id) => url`; omit for `${basePath}/docs/${id}`
|
|
28
|
+
* @param {boolean} [actions=true] render the quick-actions rung
|
|
29
|
+
*/
|
|
30
|
+
export default function ExhibitSidebar({
|
|
31
|
+
sections = [],
|
|
32
|
+
links = [],
|
|
33
|
+
basePath = '/workshop',
|
|
34
|
+
docHref = (id) => (id ? `${basePath}/docs/${id}` : `${basePath}/docs`),
|
|
35
|
+
actions = true,
|
|
36
|
+
}) {
|
|
37
|
+
const navigate = useNavigate()
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="shell-rail-stack-inner">
|
|
41
|
+
{sections.length > 0 && (
|
|
42
|
+
<RailSection level={1} label="On this page" icon={Icon}>
|
|
43
|
+
<DocsToc toc={sections} />
|
|
44
|
+
</RailSection>
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
{links.length > 0 && (
|
|
48
|
+
<RailSection level={1} label="Documentation" icon={Icon}>
|
|
49
|
+
<nav className="shell-nav-items">
|
|
50
|
+
{links.map(({ id, label }) => (
|
|
51
|
+
<RailRow key={id} to={docHref(id)}>
|
|
52
|
+
{label}
|
|
53
|
+
</RailRow>
|
|
54
|
+
))}
|
|
55
|
+
</nav>
|
|
56
|
+
</RailSection>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
{actions && (
|
|
60
|
+
<RailSection level={1} label="Quick actions" icon={Icon}>
|
|
61
|
+
<div className="space-y-1">
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
className="shell-sidebar-action kol-mono-14 text-body"
|
|
65
|
+
onClick={() => navigate(-1)}
|
|
66
|
+
>
|
|
67
|
+
<Icon name="arrow-left" size={14} />
|
|
68
|
+
Back
|
|
69
|
+
</button>
|
|
70
|
+
<Link to={docHref()} className="shell-sidebar-action kol-mono-14 text-body">
|
|
71
|
+
<Icon name="book-open" size={14} />
|
|
72
|
+
All documentation
|
|
73
|
+
</Link>
|
|
74
|
+
<Link to={basePath} className="shell-sidebar-action kol-mono-14 text-body">
|
|
75
|
+
<Icon name="layout" size={14} />
|
|
76
|
+
Workshop home
|
|
77
|
+
</Link>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
className="shell-sidebar-action kol-mono-14 text-body"
|
|
81
|
+
onClick={() => navigator.clipboard?.writeText(window.location.pathname)}
|
|
82
|
+
title="Copy page path to clipboard"
|
|
83
|
+
>
|
|
84
|
+
<Icon name="copy" size={14} />
|
|
85
|
+
Copy path
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
</RailSection>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kolkrabbi/kol-workshop — exhibit sections.
|
|
3
|
+
*
|
|
4
|
+
* The scaffold an exhibit section is built from, so adding the NEXT one is
|
|
5
|
+
* content and nothing else. The shell was already shared; the sections above it
|
|
6
|
+
* were not, and every consumer rebuilt the same landing page, the same
|
|
7
|
+
* specimen-grid page, the same prose companion and the same rail block by hand.
|
|
8
|
+
*
|
|
9
|
+
* ExhibitOverview the landing — concept, one opt-in action, children as cards
|
|
10
|
+
* ExhibitPage an inner page — sections of specimen grids and/or prose
|
|
11
|
+
* ExhibitSidebar the rail block: on-this-page · doc links · quick actions
|
|
12
|
+
* useExhibitToc registers that block into the shell's TOC slot
|
|
13
|
+
* ExhibitCard the specimen header inside a section
|
|
14
|
+
* ExhibitLinkCard the child-page card on a landing grid
|
|
15
|
+
*
|
|
16
|
+
* Content is consumer-injected, like everything else in this package — the
|
|
17
|
+
* exhibit declares cards, specimens and doc links; the package renders them.
|
|
18
|
+
*/
|
|
19
|
+
export { default as ExhibitOverview } from './ExhibitOverview.jsx'
|
|
20
|
+
export { default as ExhibitPage } from './ExhibitPage.jsx'
|
|
21
|
+
export { default as ExhibitSidebar } from './ExhibitSidebar.jsx'
|
|
22
|
+
export { default as ExhibitCard } from './ExhibitCard.jsx'
|
|
23
|
+
export { default as ExhibitLinkCard } from './ExhibitLinkCard.jsx'
|
|
24
|
+
export { default as useExhibitToc } from './useExhibitToc.js'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createElement, useContext, useLayoutEffect } from 'react'
|
|
2
|
+
import { ShellTocContext } from '../shell/ShellLayout.jsx'
|
|
3
|
+
import ExhibitSidebar from './ExhibitSidebar.jsx'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* useExhibitToc — registers an ExhibitSidebar into the shell's TOC slot for as
|
|
7
|
+
* long as the page is mounted, and clears it on the way out.
|
|
8
|
+
*
|
|
9
|
+
* This is the six-line `useContext` + `useLayoutEffect` block that every exhibit
|
|
10
|
+
* page in kol-website copied verbatim. It takes DATA, not a node, on purpose:
|
|
11
|
+
* a caller passing `<Sidebar links={LINKS} />` hands over a fresh element every
|
|
12
|
+
* render, and an effect keyed on that identity would set shell state in a loop.
|
|
13
|
+
* Keying on the CONTENT of the props closes that, so a page can pass an inline
|
|
14
|
+
* array literal — which is what every one of them does.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} props — ExhibitSidebar props: { sections, links, basePath, docHref, actions }
|
|
17
|
+
*/
|
|
18
|
+
export default function useExhibitToc(props = {}) {
|
|
19
|
+
const setTocContent = useContext(ShellTocContext)
|
|
20
|
+
|
|
21
|
+
/* Content, not identity — see the note above. */
|
|
22
|
+
const key = JSON.stringify([props.sections ?? [], props.links ?? [], props.basePath, props.actions])
|
|
23
|
+
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
/* Outside a ShellLayout the context defaults to null; a page rendered
|
|
26
|
+
* standalone (a test, a preview route) must not throw over its rail. */
|
|
27
|
+
if (!setTocContent) return undefined
|
|
28
|
+
setTocContent(createElement(ExhibitSidebar, props))
|
|
29
|
+
return () => setTocContent(null)
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [setTocContent, key])
|
|
32
|
+
}
|
package/src/index.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Aggregates everything the package now owns: the shell composition
|
|
5
5
|
* (ShellLayout + ShellSidebar + their contexts), the docs viewer, the tag
|
|
6
|
-
* system, the example compositions, and the pure
|
|
7
|
-
* chrome (ShellHeader, ShellDrawer, SearchInput,
|
|
8
|
-
* ships from @kolkrabbi/kol-framework +
|
|
9
|
-
* from there, not from here.
|
|
6
|
+
* system, the exhibit-section scaffold, the example compositions, and the pure
|
|
7
|
+
* React-free engine. Duplicated chrome (ShellHeader, ShellDrawer, SearchInput,
|
|
8
|
+
* ShellSearchOverlay, DocsToc) ships from @kolkrabbi/kol-framework +
|
|
9
|
+
* @kolkrabbi/kol-component — consume it from there, not from here.
|
|
10
10
|
*/
|
|
11
11
|
export * from './shell/index.js'
|
|
12
12
|
export * from './docs/index.js'
|
|
13
13
|
export * from './tags/index.js'
|
|
14
|
+
export * from './exhibit/index.js'
|
|
14
15
|
export * from './compositions/index.js'
|
|
15
16
|
export * from './engine/index.js'
|