@kolkrabbi/kol-workshop 0.32.0 → 0.33.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.32.0",
3
+ "version": "0.33.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,9 +13,9 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "d3": "^7.9.0",
16
- "@kolkrabbi/kol-brand": "^0.1.3",
16
+ "@kolkrabbi/kol-search": "^0.2.0",
17
17
  "@kolkrabbi/kol-markdown": "^0.1.2",
18
- "@kolkrabbi/kol-search": "^0.2.0"
18
+ "@kolkrabbi/kol-brand": "^0.1.3"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "@kolkrabbi/kol-component": ">=0.228.0",
@@ -27,10 +27,10 @@
27
27
  "react-router-dom": "^6.0.0 || ^7.0.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@kolkrabbi/kol-component": "^0.230.0",
31
30
  "@kolkrabbi/kol-icons": "^0.30.0",
32
- "@kolkrabbi/kol-theme": "^0.158.0",
33
- "@kolkrabbi/kol-framework": "^0.47.0"
31
+ "@kolkrabbi/kol-framework": "^0.48.0",
32
+ "@kolkrabbi/kol-theme": "^0.159.0",
33
+ "@kolkrabbi/kol-component": "^0.230.0"
34
34
  },
35
35
  "files": [
36
36
  "src",
package/src/index.js CHANGED
@@ -14,4 +14,6 @@ export * from './tags/index.js'
14
14
  export * from './exhibit/index.js'
15
15
  export * from './compositions/index.js'
16
16
  export * from './search/index.js'
17
+ /* retiring — ContentRow replaces it (retirement ledger, 2026-09-30) */
18
+ export { default as ResultRow } from './search/ResultRow.jsx'
17
19
  export * from './engine/index.js'
@@ -1,33 +1,28 @@
1
- import { Link } from 'react-router-dom'
2
-
3
1
  /**
4
- * ResultRow — one search result: a title, a meta line, an optional description, the whole row a
5
- * link (2026-09-30, open questions Round 2 — the user: *"I like both, is this a component? if it
6
- * is I would just have it available as variants … underline is more appealing currently"*).
7
- *
8
- * Two variants, each a complete pair — the wash needs its x padding, the underline must not have
9
- * one (Round 1: x padding without a wash was drawn wrong):
10
- * `underline` (default) — no ground; the title underlines and the row inks up on hover
11
- * `wash` — x padding and a ground wash on hover
12
- *
13
- * @param {string} to the route the row opens
14
- * @param {ReactNode} title the result's name (a highlighted node is fine)
15
- * @param {string} [meta] kind · category · space · date
16
- * @param {ReactNode} [description]
17
- * @param {'underline'|'wash'} [variant='underline']
2
+ * @deprecated — use kol-component's `ContentRow` (variant `article`, `media={false}`). ResultRow was
3
+ * minted 2026-09-30 for the search page while the design system already shipped its row; it
4
+ * forwards to ContentRow and drops at 30 days on the retirement ledger. `variant` is ignored.
18
5
  */
19
- const VARIANTS = {
20
- underline: { row: 'group flex flex-col gap-1 py-3 opacity-80 hover:opacity-100 focus-visible:opacity-100', title: 'group-hover:underline underline-offset-4' },
21
- wash: { row: 'flex flex-col gap-1 px-3 py-3 hover:bg-fg-04', title: '' },
22
- }
23
6
 
24
- export default function ResultRow({ to, title, meta, description, variant = 'underline' }) {
25
- const v = VARIANTS[variant] ?? VARIANTS.underline
7
+ import { useNavigate } from 'react-router-dom'
8
+ import { ContentRow } from '@kolkrabbi/kol-component'
9
+
10
+ /**
11
+ * @deprecated — use kol-component's `ContentRow` (variant `article`, `media={false}`). ResultRow was
12
+ * minted 2026-09-30 for the search page while the design system already shipped its row; it
13
+ * forwards to ContentRow and drops at 30 days on the retirement ledger. `variant` is ignored.
14
+ */
15
+ export default function ResultRow({ to, title, meta, description }) {
16
+ const navigate = useNavigate()
26
17
  return (
27
- <Link to={to} className={v.row}>
28
- <span className={`kol-doc-body text-emphasis ${v.title}`.trim()}>{title}</span>
29
- {meta && <span className="kol-helper-12 text-subtle">{meta}</span>}
30
- {description && <span className="kol-doc-body">{description}</span>}
31
- </Link>
18
+ <ContentRow
19
+ variant="article"
20
+ media={false}
21
+ href={to}
22
+ onNavigate={(e) => { e.preventDefault(); navigate(to) }}
23
+ eyebrow={meta}
24
+ title={title}
25
+ body={description}
26
+ />
32
27
  )
33
28
  }
@@ -1,7 +1,6 @@
1
1
  import { Fragment, useMemo } from 'react'
2
- import { useSearchParams } from 'react-router-dom'
3
- import { Button, EmptyState, Input, Pill } from '@kolkrabbi/kol-component'
4
- import ResultRow from './ResultRow.jsx'
2
+ import { useNavigate, useSearchParams } from 'react-router-dom'
3
+ import { Button, ContentRow, EmptyState, Input, Pill } from '@kolkrabbi/kol-component'
5
4
  import { createIndex, search, parseQuery } from '@kolkrabbi/kol-search'
6
5
  import { DocHeader, DocSection } from '../docs/DocKit.jsx'
7
6
 
@@ -45,7 +44,8 @@ const readToken = (t) => (t.kind === 'term'
45
44
  ? `${t.field} ${t.value}`
46
45
  : `${t.negate ? 'not ' : ''}${t.field === 'tags' ? 'tag' : t.field}: ${t.value}${t.via === 'smart' ? ' (from the word)' : ''}`)
47
46
 
48
- export default function SearchPage({ items = [], spaces = [], facets = DEFAULT_FACETS, limit = 60, rowVariant = 'underline' }) {
47
+ export default function SearchPage({ items = [], spaces = [], facets = DEFAULT_FACETS, limit = 60 }) {
48
+ const navigate = useNavigate()
49
49
  const [params, setParams] = useSearchParams()
50
50
  const q = params.get('q') ?? ''
51
51
  const index = useMemo(() => createIndex(items), [items])
@@ -125,12 +125,16 @@ export default function SearchPage({ items = [], spaces = [], facets = DEFAULT_F
125
125
  <ol className="flex flex-col">
126
126
  {out.results.map((r) => (
127
127
  <li key={r.item.id} className="border-t border-fg-08 first:border-t-0">
128
- <ResultRow
129
- to={r.item.href}
130
- variant={rowVariant}
128
+ {/* THE SHIPPED ROW (2026-09-30): kol-component's ContentRow, the row form of the
129
+ * content-card system — not a row minted here. Article form, no cover. */}
130
+ <ContentRow
131
+ variant="article"
132
+ media={false}
133
+ href={r.item.href}
134
+ onNavigate={(e) => { e.preventDefault(); navigate(r.item.href) }}
135
+ eyebrow={[r.item.kind, r.item.category, r.item.space, r.item.date].filter(Boolean).join(' · ')}
131
136
  title={<Highlight text={r.item.title} ranges={r.highlights.title} />}
132
- meta={[r.item.kind, r.item.category, r.item.space, r.item.date].filter(Boolean).join(' · ')}
133
- description={r.item.description}
137
+ body={r.item.description}
134
138
  />
135
139
  </li>
136
140
  ))}
@@ -1,2 +1 @@
1
1
  export { default as SearchPage } from './SearchPage.jsx'
2
- export { default as ResultRow } from './ResultRow.jsx'
@@ -2,7 +2,7 @@ import { createContext, useContext, useMemo, useRef, useState, useEffect, Suspen
2
2
  import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom'
3
3
  import { ShellHeader } from '@kolkrabbi/kol-framework'
4
4
  import ShellSidebar, { RAIL_FOLD_EVENT } from './ShellSidebar.jsx'
5
- import { IconFrame, ShellDrawer, ShellSearchOverlay, ShortcutsOverlay, SettingsSections, SettingsSwitch, Tooltip } from '@kolkrabbi/kol-component'
5
+ import { IconFrame, ShellDrawer, ShellSearchOverlay, ShortcutsOverlay, SettingsPanel, SettingsSections, SettingsSwitch, Tooltip, useDragResize } from '@kolkrabbi/kol-component'
6
6
  import { createIndex, search } from '@kolkrabbi/kol-search'
7
7
  import { useTagMode } from '../tags/TagModeContext.jsx'
8
8
  import TagModeOverlay from '../tags/TagModeOverlay.jsx'
@@ -79,37 +79,27 @@ export const SHELL_SCROLL_ROOT = '#shell-scroll'
79
79
  * overflow (`.shell-rail`, kol-components-workshop.css): exactly one region
80
80
  * tall, a seam toward the page, the chrome inset inside the seam.
81
81
  * overflow-x-hidden (2026-07-30): long tree rows must not scroll sideways. */
82
- /* RESIZABLE RAILS (2026-09-30, the names audit; Round 3 Q3 — decided on the recommendation, the
83
- * user asleep, for review). A 6px grab strip on the edge facing the page: drag to set the width
84
- * (200–420, remembered), double-click to reset to the shell's 256. The width is the shell's own
85
- * token, `--kol-shell-nav-w` / `--kol-shell-toc-w`, set on the shell root, so the grid tracks and
86
- * the rail's own box follow it. */
87
- const RAIL_MIN = 200
88
- const RAIL_MAX = 420
89
- const RailGrip = ({ side, width, onChange }) => {
90
- const start = (e) => {
91
- e.preventDefault()
92
- const x0 = e.clientX
93
- const w0 = width
94
- const move = (m) => onChange(Math.round(Math.min(RAIL_MAX, Math.max(RAIL_MIN, w0 + (side === 'end' ? m.clientX - x0 : x0 - m.clientX)))))
95
- const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up) }
96
- window.addEventListener('pointermove', move)
97
- window.addEventListener('pointerup', up)
98
- }
99
- return (
100
- <div
101
- role="separator"
102
- aria-orientation="vertical"
103
- aria-label="Resize rail"
104
- onPointerDown={start}
105
- onDoubleClick={() => onChange(null)}
106
- className={`absolute top-0 hidden h-full w-1.5 cursor-col-resize hover:bg-oq-08 lg:block ${side === 'end' ? 'right-0' : 'left-0'}`}
107
- />
108
- )
82
+ /* RESIZABLE RAILS (2026-09-30) — THE DS GESTURE, not a local one: `useDragResize` (the grab edge
83
+ * SideNav and EditorShell wear — drag resizes, a click or a drag under the snap collapses, arrows
84
+ * step, release near the default snaps back) and the `.kol-rail-grab` pill. Each rail has its own
85
+ * token family (`kol-shell-nav` · `kol-shell-toc`, tokens in kol-framework.css) so the two never
86
+ * drag together; width lasts the session (persistWidth off, the 2026-09-03 ruling). A collapse
87
+ * from the grab is the shell's own "hide the rail" — the same state `[` / `]` toggle. */
88
+ const useRailGrab = (token, side, hide) => {
89
+ const ref = useRef(null)
90
+ const { collapsed, toggleCollapsed, grabProps } = useDragResize(ref, { token, side })
91
+ useEffect(() => {
92
+ if (!collapsed) return
93
+ hide()
94
+ toggleCollapsed() // un-stamp the hook's collapse — the shell's own state now holds it
95
+ document.documentElement.style.removeProperty(`--${token}-w`) // it returns at the stylesheet width
96
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
+ }, [collapsed])
98
+ return { ref, grab: <div {...grabProps} className={`kol-rail-grab kol-rail-grab--${side} hidden lg:block`} /> }
109
99
  }
110
100
 
111
- const NavColumn = ({ children, grip }) => (
112
- <aside aria-label="Navigation" className="shell-rail shell-rail--nav shell-sidebar-sticky hidden lg:block pt-6 md:pt-6 lg:pt-8 pb-8">
101
+ const NavColumn = ({ children, grip, railRef }) => (
102
+ <aside ref={railRef} aria-label="Navigation" className="shell-rail shell-rail--nav shell-sidebar-sticky hidden lg:block pt-6 md:pt-6 lg:pt-8 pb-8">
113
103
  {children}
114
104
  {grip}
115
105
  </aside>
@@ -148,8 +138,8 @@ const MainColumn = ({ children, fullHeight, width = 'canvas', padStart, padEnd }
148
138
  /* `xl`, not `lg` — the grid only declares a third column at xl (gridCols
149
139
  * below). The breakpoint here and the one in gridCols are one decision and
150
140
  * must not be stated twice differently. */
151
- const TocColumn = ({ children, grip }) => (
152
- <aside aria-label="Table of contents" className="shell-rail shell-rail--toc shell-sidebar-sticky hidden xl:block pt-6 md:pt-6 lg:pt-8 pb-8">
141
+ const TocColumn = ({ children, grip, railRef }) => (
142
+ <aside ref={railRef} aria-label="Table of contents" className="shell-rail shell-rail--toc shell-sidebar-sticky hidden xl:block pt-6 md:pt-6 lg:pt-8 pb-8">
153
143
  {grip}
154
144
  {/* The width is the grid track (--kol-shell-toc-w). An empty rail still
155
145
  * holds its column (user ruling 2026-08-01): a rail that disappears
@@ -195,11 +185,10 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
195
185
  const [prefs, setPrefs] = useState(readSettings)
196
186
  const setPref = (key, value) => setPrefs((p) => { const next = { ...p, [key]: value }; writeSettings(next); return next })
197
187
  const [navCollapsed, setNavCollapsed] = useState(() => readSettings().navHidden === true)
198
- /* rail widths — null = the shell's token; a number is the viewer's own (remembered) */
199
- const [navW, setNavW] = useState(() => readSettings().navW ?? null)
200
- const [tocW, setTocW] = useState(() => readSettings().tocW ?? null)
201
- const setRailW = (key, set) => (w) => { set(w); writeSettings({ ...readSettings(), [key]: w }) }
188
+
202
189
  const [tocCollapsed, setTocCollapsed] = useState(() => readSettings().tocHidden === true)
190
+ const navGrab = useRailGrab('kol-shell-nav', 'left', () => { setNavCollapsed(true); writeSettings({ ...readSettings(), navHidden: true }) })
191
+ const tocGrab = useRailGrab('kol-shell-toc', 'right', () => { setTocCollapsed(true); writeSettings({ ...readSettings(), tocHidden: true }) })
203
192
  const [isSettingsOpen, setIsSettingsOpen] = useState(false)
204
193
  /* ONE QUERY (user ruling 2026-08-01). The palette's text used to live here
205
194
  * while tags lived in TagModeContext — two states, and therefore two
@@ -465,10 +454,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
465
454
  <ShellContentWidthContext.Provider value={setContentWidth}>
466
455
  <ShellTocCollapsedContext.Provider value={setTocCollapsed}>
467
456
  <ShellNavCollapsedContext.Provider value={setNavCollapsed}>
468
- <div
469
- className="fixed inset-0 flex flex-col bg-surface-primary text-auto"
470
- style={{ ...(navW ? { '--kol-shell-nav-w': `${navW}px` } : {}), ...(tocW ? { '--kol-shell-toc-w': `${tocW}px` } : {}) }}
471
- >
457
+ <div className="fixed inset-0 flex flex-col bg-surface-primary text-auto">
472
458
  <ShellHeader
473
459
  brand={brand}
474
460
  nav={navItems}
@@ -503,7 +489,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
503
489
  >
504
490
  <div className={`shell-content-grid grid ${gridCols}`} data-layout={layoutType} style={{ paddingInline: 'var(--kol-pad-chrome-x)' }}>
505
491
  {showNav && (
506
- <NavColumn grip={<RailGrip side="end" width={navW ?? 256} onChange={setRailW('navW', setNavW)} />}>
492
+ <NavColumn railRef={navGrab.ref} grip={navGrab.grab}>
507
493
  {renderSidebar ? renderSidebar({ activeRoute }) : <ShellSidebar routes={routes} basePath={basePath} />}
508
494
  </NavColumn>
509
495
  )}
@@ -521,7 +507,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
521
507
  * the grid's third track so main keeps one width across
522
508
  * every route (user ruling 2026-08-01). */}
523
509
  {showToc && (
524
- <TocColumn grip={<RailGrip side="start" width={tocW ?? 256} onChange={setRailW('tocW', setTocW)} />}>
510
+ <TocColumn railRef={tocGrab.ref} grip={tocGrab.grab}>
525
511
  {effectiveTocContent}
526
512
  </TocColumn>
527
513
  )}
@@ -588,13 +574,12 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
588
574
  * settings page or settings sidebar … to offload some functional
589
575
  * settings, search settings, sidenav settings"). A right drawer over
590
576
  * the page, the same rows the apps' settings use (SettingsSections). */}
591
- <ShellDrawer open={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} side="right" width={360}>
592
- {/* the drawer draws its own close — no second one here */}
593
- <div className="flex flex-col gap-6 p-6">
594
- <span className="kol-doc-eyebrow">Settings</span>
595
- <SettingsSections sections={settingsSections} labelWidth="auto" />
596
- </div>
597
- </ShellDrawer>
577
+ {/* THE APPROVED DRAWER (2026-09-30): kol-component's `SettingsPanel` — the composition locked
578
+ * 2026-08-27 that media's Display settings and Trash already wear: title header, divided
579
+ * sections. This was a hand-built ShellDrawer + eyebrow + undivided sections. */}
580
+ <SettingsPanel open={isSettingsOpen} variant="drawer" title="Settings" onClose={() => setIsSettingsOpen(false)}>
581
+ <SettingsSections sections={settingsSections} divided />
582
+ </SettingsPanel>
598
583
  </div>
599
584
  </ShellNavCollapsedContext.Provider>
600
585
  </ShellTocCollapsedContext.Provider>