@kolkrabbi/kol-workshop 0.18.1 → 0.20.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-workshop",
3
- "version": "0.18.1",
3
+ "version": "0.20.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",
@@ -14,10 +14,10 @@
14
14
  "dependencies": {
15
15
  "d3": "^7.9.0",
16
16
  "@kolkrabbi/kol-brand": "^0.1.2",
17
- "@kolkrabbi/kol-component": "0.24.0",
17
+ "@kolkrabbi/kol-component": "0.27.0",
18
18
  "@kolkrabbi/kol-icons": "0.10.0",
19
- "@kolkrabbi/kol-framework": "0.13.0",
20
- "@kolkrabbi/kol-theme": "0.30.1"
19
+ "@kolkrabbi/kol-theme": "0.31.3",
20
+ "@kolkrabbi/kol-framework": "0.14.0"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "react": "^18.3.0 || ^19.0.0",
@@ -1,3 +1,4 @@
1
+ import { useLayoutEffect, useRef, useState } from 'react'
1
2
  import { Icon, Tag } from '@kolkrabbi/kol-component'
2
3
  import { useTagMode } from '../tags'
3
4
  import TagPath from '../tags/TagPath.jsx'
@@ -157,15 +158,33 @@ const formatDate = (dateStr) => {
157
158
 
158
159
  const DocsFrontmatter = ({ metadata, docId }) => {
159
160
  const { openTagMode } = useTagMode()
160
- if (!metadata || Object.keys(metadata).length === 0) return null
161
+ /* THE PANEL COLLAPSES, THE PAGE SCROLLS (user call 2026-08-09). A long
162
+ * frontmatter (Dropdown: classes + reuses + tokens) made the panel the
163
+ * page, and its per-field scrollboxes captured the wheel on the way down.
164
+ * The whole panel now caps behind a fade and expands in normal flow — the
165
+ * toggle renders only when the content actually overflows the cap. */
166
+ const bodyRef = useRef(null)
167
+ const [expanded, setExpanded] = useState(false)
168
+ const [overflowing, setOverflowing] = useState(false)
161
169
 
162
- const fields = orderFields(metadata)
170
+ const fields = metadata ? orderFields(metadata) : []
171
+
172
+ useLayoutEffect(() => {
173
+ const el = bodyRef.current
174
+ if (el) setOverflowing(el.scrollHeight > el.clientHeight + 1)
175
+ }, [metadata, expanded])
163
176
 
164
177
  if (fields.length === 0) return null
165
178
 
179
+ const capped = !expanded
180
+
166
181
  return (
167
182
  <div className="docs-frontmatter">
168
183
  <p className="shell-sidebar-label kol-doc-eyebrow">Frontmatter</p>
184
+ <div
185
+ ref={bodyRef}
186
+ className={`docs-frontmatter-body${capped ? ' docs-frontmatter-body--capped' : ''}`}
187
+ >
169
188
  {fields.map((key) => {
170
189
  const value = metadata[key]
171
190
  const icon = FIELD_ICONS[key]
@@ -233,6 +252,18 @@ const DocsFrontmatter = ({ metadata, docId }) => {
233
252
  </div>
234
253
  )
235
254
  })}
255
+ {capped && overflowing && <div className="docs-frontmatter-fade" aria-hidden="true" />}
256
+ </div>
257
+ {(overflowing || expanded) && (
258
+ <button
259
+ type="button"
260
+ className="docs-frontmatter-toggle kol-helper-12 text-meta"
261
+ onClick={() => setExpanded((v) => !v)}
262
+ >
263
+ <Icon name={expanded ? 'chevron-up' : 'chevron-down'} size={14} />
264
+ {expanded ? 'Collapse' : 'Expand'}
265
+ </button>
266
+ )}
236
267
  </div>
237
268
  )
238
269
  }
@@ -45,7 +45,10 @@ import { Link } from 'react-router-dom'
45
45
  * @param {string} label header text, rendered verbatim (casing is authored)
46
46
  * @param {number} [count] leaf rows this section contains — L1 counts the
47
47
  * whole subtree, L2 counts its own children
48
- * @param {string} [to] make the LABEL a link; the rest of the row still toggles
48
+ * @param {string} [to] L2: make the LABEL a link (the rest of the row
49
+ * still toggles). Non-collapsible rungs: the whole
50
+ * row is the link. Ignored on a collapsible L1 —
51
+ * the eyebrow is a pure two-way toggle (2026-08-09)
49
52
  * @param {boolean} [collapsible=true] false → a static header, no toggle, no aria
50
53
  * @param {boolean} [collapsed] controlled state; omit for uncontrolled
51
54
  * @param {Function} [onToggle] required when `collapsed` is passed
@@ -103,12 +106,17 @@ export default function RailSection({
103
106
  ? <span className="kol-mono-14 text-subtle">({count})</span>
104
107
  : null
105
108
 
106
- const labelNode = to ? (
109
+ /* L1 EYEBROW IS ONE GESTURE (user ruling 2026-08-09, repealing the
110
+ * 2026-08-01 label-door at L1): the whole row toggles BOTH ways — a label
111
+ * link never collapses, and the text is most of the perceived target, so
112
+ * the eyebrow felt expand-only. A category's door lives in the header tabs.
113
+ * L2 keeps the split (2026-08-02 ruling): its label is a chapter's only
114
+ * door to the landing page, and the chevron marks the toggle. */
115
+ const labelNode = to && level === 2 ? (
107
116
  /* The label navigates; the rest of the row toggles. stopPropagation keeps
108
117
  * one click from doing both. */
109
118
  <Link
110
119
  to={to}
111
- className={level === 1 ? 'kol-doc-eyebrow' : undefined}
112
120
  onClick={(e) => {
113
121
  e.stopPropagation()
114
122
  if (isCollapsed && toggle) toggle()