@kolkrabbi/kol-workshop 0.31.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.31.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",
@@ -14,8 +14,8 @@
14
14
  "dependencies": {
15
15
  "d3": "^7.9.0",
16
16
  "@kolkrabbi/kol-search": "^0.2.0",
17
- "@kolkrabbi/kol-brand": "^0.1.3",
18
- "@kolkrabbi/kol-markdown": "^0.1.2"
17
+ "@kolkrabbi/kol-markdown": "^0.1.2",
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
- "@kolkrabbi/kol-framework": "^0.46.0",
32
- "@kolkrabbi/kol-theme": "^0.157.0",
33
- "@kolkrabbi/kol-icons": "^0.29.1"
30
+ "@kolkrabbi/kol-icons": "^0.30.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,9 +79,29 @@ 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
- const NavColumn = ({ children }) => (
83
- <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">
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`} /> }
99
+ }
100
+
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">
84
103
  {children}
104
+ {grip}
85
105
  </aside>
86
106
  )
87
107
 
@@ -118,8 +138,9 @@ const MainColumn = ({ children, fullHeight, width = 'canvas', padStart, padEnd }
118
138
  /* `xl`, not `lg` — the grid only declares a third column at xl (gridCols
119
139
  * below). The breakpoint here and the one in gridCols are one decision and
120
140
  * must not be stated twice differently. */
121
- const TocColumn = ({ children }) => (
122
- <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">
143
+ {grip}
123
144
  {/* The width is the grid track (--kol-shell-toc-w). An empty rail still
124
145
  * holds its column (user ruling 2026-08-01): a rail that disappears
125
146
  * re-flows main and the same page ends up at two widths. */}
@@ -164,7 +185,10 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
164
185
  const [prefs, setPrefs] = useState(readSettings)
165
186
  const setPref = (key, value) => setPrefs((p) => { const next = { ...p, [key]: value }; writeSettings(next); return next })
166
187
  const [navCollapsed, setNavCollapsed] = useState(() => readSettings().navHidden === true)
188
+
167
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 }) })
168
192
  const [isSettingsOpen, setIsSettingsOpen] = useState(false)
169
193
  /* ONE QUERY (user ruling 2026-08-01). The palette's text used to live here
170
194
  * while tags lived in TagModeContext — two states, and therefore two
@@ -437,6 +461,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
437
461
  isActive={isActive}
438
462
  onNavigate={handleNavigate}
439
463
  actions={searchTrigger}
464
+ menuBelowLg
440
465
  onMenuClick={() => {
441
466
  if (window.matchMedia('(min-width: 1024px)').matches) {
442
467
  /* Both rails to ONE target state — independent `!p` flips made
@@ -464,7 +489,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
464
489
  >
465
490
  <div className={`shell-content-grid grid ${gridCols}`} data-layout={layoutType} style={{ paddingInline: 'var(--kol-pad-chrome-x)' }}>
466
491
  {showNav && (
467
- <NavColumn>
492
+ <NavColumn railRef={navGrab.ref} grip={navGrab.grab}>
468
493
  {renderSidebar ? renderSidebar({ activeRoute }) : <ShellSidebar routes={routes} basePath={basePath} />}
469
494
  </NavColumn>
470
495
  )}
@@ -482,7 +507,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
482
507
  * the grid's third track so main keeps one width across
483
508
  * every route (user ruling 2026-08-01). */}
484
509
  {showToc && (
485
- <TocColumn>
510
+ <TocColumn railRef={tocGrab.ref} grip={tocGrab.grab}>
486
511
  {effectiveTocContent}
487
512
  </TocColumn>
488
513
  )}
@@ -549,13 +574,12 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
549
574
  * settings page or settings sidebar … to offload some functional
550
575
  * settings, search settings, sidenav settings"). A right drawer over
551
576
  * the page, the same rows the apps' settings use (SettingsSections). */}
552
- <ShellDrawer open={isSettingsOpen} onClose={() => setIsSettingsOpen(false)} side="right" width={360}>
553
- {/* the drawer draws its own close — no second one here */}
554
- <div className="flex flex-col gap-6 p-6">
555
- <span className="kol-doc-eyebrow">Settings</span>
556
- <SettingsSections sections={settingsSections} labelWidth="auto" />
557
- </div>
558
- </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>
559
583
  </div>
560
584
  </ShellNavCollapsedContext.Provider>
561
585
  </ShellTocCollapsedContext.Provider>