@kolkrabbi/kol-shell 0.46.0 → 0.48.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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/src/CatalogPage.jsx +59 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-shell",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "private": false,
5
5
  "description": "KOL application shell — fixed 48px NavRail + AppShell layout root, PageShell/PageHeader scaffolds, ContentFilters catalog organism, GridCard, SettingsScaffold, WalkthroughPanel, ShortcutsOverlay. App chrome (kol-framework owns site chrome). Nav items, content, shortcuts and settings are consumer-injected. Sits above @kolkrabbi/kol-{theme,component,framework}.",
6
6
  "license": "MIT",
@@ -14,16 +14,16 @@
14
14
  "peerDependencies": {
15
15
  "@kolkrabbi/kol-component": ">=0.174.0",
16
16
  "@kolkrabbi/kol-icons": ">=0.16.0",
17
- "@kolkrabbi/kol-theme": ">=0.137.0",
17
+ "@kolkrabbi/kol-theme": ">=0.138.0",
18
18
  "gsap": "^3.13.0",
19
19
  "react": "^18.3.0 || ^19.0.0",
20
20
  "react-dom": "^18.3.0 || ^19.0.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@kolkrabbi/kol-component": "^0.176.0",
24
- "@kolkrabbi/kol-framework": "^0.41.0",
25
23
  "@kolkrabbi/kol-icons": "^0.25.0",
26
- "@kolkrabbi/kol-theme": "^0.137.0"
24
+ "@kolkrabbi/kol-component": "^0.177.0",
25
+ "@kolkrabbi/kol-framework": "^0.42.0",
26
+ "@kolkrabbi/kol-theme": "^0.138.0"
27
27
  },
28
28
  "files": [
29
29
  "src",
@@ -1,3 +1,4 @@
1
+ import { useCallback, useRef, useState } from 'react'
1
2
  import { ContentCard, ContentFilters, ContentRow } from '@kolkrabbi/kol-component'
2
3
  import PageShell from './PageShell.jsx'
3
4
  import { PageHeader } from '@kolkrabbi/kol-component'
@@ -18,8 +19,11 @@ import WalkthroughPanel from './WalkthroughPanel.jsx'
18
19
  *
19
20
  * Semantics are the consumer's: what RECENT / SAVED mean, what an item is,
20
21
  * what a click does. `toCard(item, { view, layout })` → `{ key, title, detail,
21
- * media, actions, onClick, href, onNavigate, expanded, expandedContent, fit, ratio }` is
22
- * the whole contract `expanded` / `expandedContent` reach `ContentCard catalog`'s
22
+ * …every prop ContentCard / ContentRow takes }` `title`, `detail`, `media`,
23
+ * `actions`, `onClick`, `href`, `onNavigate`, `expanded`, `expandedContent`, `fit`,
24
+ * `ratio`, `date`, `size`, `meta`, `tags`, `selected`, `variant`, … — is the whole
25
+ * contract: the return is SPREAD onto the card or the row (user ruling 2026-09-03,
26
+ * a composition forwards the whole contract of what it composes) — `expanded` / `expandedContent` reach `ContentCard catalog`'s
23
27
  * 2×2 cell (ShellHomeSystemMonitorGaps, kol-monitor 2026-08-27) — and the page
24
28
  * HIDES the cell's three neighbours itself (CatalogPageMonitorParity: the
25
29
  * consumer never sees the filtered row order, so it cannot; monitor's
@@ -50,7 +54,10 @@ import WalkthroughPanel from './WalkthroughPanel.jsx'
50
54
  * framework container — the site tier, so a brand book adopting this page keeps its geometry
51
55
  * @param {ReactNode} trailingActions the header's RIGHT slot (ContentFilters' — MediaLibrary's SELECT / FLAT); never empty beside the divider
52
56
  * @param {number} minColumn the grid track's floor in px (default 160); a shelf of three decks passes 280 — the six-track ceiling still applies
53
- * @param {'catalog'|'file'} rowVariant the list row (default `catalog`, the 36px no-thumb row); `file` = the 48px thumb row, for a catalog whose grid shows a cover
57
+ * @param {string} cardVariant the grid card's variant, per page (default `catalog`); `toCard` may return `variant` per card
58
+ * @param {'catalog'|'file'} rowVariant the list row (default `catalog`, the 36px no-thumb row); `file` = the 48px thumb row, for a catalog whose grid shows a cover —
59
+ * its slots (`date` · `size` · `meta` · `selected`) come from `toCard` like everything else
60
+ * @param {'grid'|'stack'} listLayout the list's container (default `grid`, rows four across — monitor's ruling); `stack` = one row per line
54
61
  * @param {ElementType} iconComponent icon renderer seam for ContentFilters
55
62
  * @param {boolean} showCountOnlyWhenFiltering (default true)
56
63
  * @param {Object} filtersProps spread onto ContentFilters last — e.g. `{ mutuallyExclusiveFilters: ['category', 'u_label'] }`
@@ -103,6 +110,13 @@ export default function CatalogPage({
103
110
  passes `file`: the 48px thumb row, so the list shows the same cover small
104
111
  (kol-client-olina 2026-09-03; user, on the bare row: "ugly ugly ugly"). */
105
112
  rowVariant = 'catalog',
113
+ /* the GRID card's variant, per page; `toCard` may return `variant` per card */
114
+ cardVariant = 'catalog',
115
+ /* the LIST's container. `grid` (default) is monitor's ruling — rows four across
116
+ on a 240 floor, gap 8 (the documented `repeat(4, 1fr)`). `stack` is one row per
117
+ line, which is what a list of files is (MediaLibrary stacks); olina's deck list
118
+ rendered three 300px rows side by side (user: "why is it tiny?"). */
119
+ listLayout = 'grid',
106
120
  iconComponent,
107
121
  showCountOnlyWhenFiltering = true,
108
122
  filtersProps,
@@ -111,8 +125,32 @@ export default function CatalogPage({
111
125
  style,
112
126
  }) {
113
127
  const open = walkthrough?.open === true
128
+ /* THE TRACK COUNT, MEASURED (CatalogFilterFirstGroupTrackCount, 2026-09-03).
129
+ * `.kol-catalog-grid` and `.kol-filters-first` both read `--kol-catalog-n`
130
+ * from the page: up to 6 tracks, none narrower than `minColumn`, gap 24 —
131
+ * `min(6, floor((W + 24) / (minColumn + 24)))`, exactly what the old inline
132
+ * `auto-fill` resolved to, now a number the filter row can read too. In JS
133
+ * because the CSS form (`round()` over a length ÷ length) is Chromium-only —
134
+ * theme 0.137.0 rendered ONE column in Firefox. `undefined` until measured
135
+ * → the theme's fallback of 6, the pre-0.137.0 geometry. */
136
+ const [cols, setCols] = useState(null)
137
+ const observer = useRef(null)
138
+ /* a CALLBACK ref: the grid mounts and unmounts with the layout toggle, and a
139
+ * callback ref sees both — an effect keyed on a value out of this scope does
140
+ * not (the first cut referenced `renderItem`'s `layout` here and threw) */
141
+ const gridRef = useCallback((el) => {
142
+ observer.current?.disconnect()
143
+ observer.current = null
144
+ if (!el) return
145
+ const measure = (W) => setCols((c) => { const n = Math.max(1, Math.min(6, Math.floor((W + 24) / (minColumn + 24)))); return c === n ? c : n })
146
+ measure(el.getBoundingClientRect().width)
147
+ const ro = new ResizeObserver(([entry]) => measure(entry.contentRect.width))
148
+ ro.observe(el)
149
+ observer.current = ro
150
+ }, [minColumn])
151
+
114
152
  return (
115
- <PageShell width={width} className={className} style={{ overflow: 'hidden', '--kol-catalog-min': `${minColumn}px`, ...style }}>
153
+ <PageShell width={width} className={className} style={{ overflow: 'hidden', '--kol-catalog-n': cols ?? undefined, ...style }}>
116
154
  {header && <PageHeader {...header} />}
117
155
  <div style={{ flex: 1, position: 'relative' }}>
118
156
  {open && <WalkthroughPanel steps={walkthrough.steps} iconComponent={walkthrough.iconComponent} />}
@@ -148,17 +186,28 @@ export default function CatalogPage({
148
186
  * six-column ruling — below the ceiling the hide-set is
149
187
  * desktop-only geometry. */
150
188
  return (
151
- /* the catalog grid is a CLASS (`.kol-catalog-grid`, kol-theme ≥0.137.0) so the
152
- * filter row's first group can read the track count it renders; `minColumn`
153
- * reaches it as `--kol-catalog-min` on the page (set on PageShell above) */
154
- <div className={layout === 'list' ? undefined : 'kol-catalog-grid'} style={layout === 'list' ? { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, max(240px, calc((100% - 3 * 8px) / 4))), 1fr))', gap: 8 } : undefined}>
189
+ /* the catalog grid is a CLASS (`.kol-catalog-grid`, kol-theme ≥0.138.0) reading
190
+ * `--kol-catalog-n`, the count measured above and set on PageShell the same
191
+ * number the filter row's first group reads */
192
+ <div ref={layout === 'list' ? undefined : gridRef} className={layout === 'list' ? undefined : 'kol-catalog-grid'} style={layout === 'list' ? (listLayout === 'stack' ? { display: 'flex', flexDirection: 'column', gap: 8 } : { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, max(240px, calc((100% - 3 * 8px) / 4))), 1fr))', gap: 8 }) : undefined}>
155
193
  {rows.map((item, i) => {
156
194
  if (hidden.has(i)) return null
157
195
  const c = cards[i]
158
196
  const key = c.key ?? item.key ?? item.id ?? item.name ?? i
197
+ /* A COMPOSITION FORWARDS THE WHOLE CONTRACT OF WHAT IT COMPOSES (user
198
+ * 2026-09-03: "why wouldn't we as standard practice ALLOW VARIANT CHANGE
199
+ * EVERY TIME WE USE THOSE CONTENT CARDS?"). Five tickets on this file in
200
+ * one day were the same defect — a key missing from `toCard` because no
201
+ * prior consumer had needed it: `ratio`, `trailingActions`, `rowVariant`,
202
+ * `date` / `size`, the list container. So `toCard`'s return is SPREAD
203
+ * onto the card and the row: every prop either takes is reachable per
204
+ * card, today and for props not written yet; `variant` per card beats
205
+ * the page's `cardVariant` / `rowVariant`; `fit` keeps its `cover`
206
+ * default. `key` is React's and is lifted out first. */
207
+ const { key: _key, ...forwarded } = c
159
208
  return layout === 'list'
160
- ? <ContentRow key={key} variant={rowVariant} media={c.media} ratio={c.ratio} title={c.title} detail={c.detail} actions={c.actions} onClick={c.onClick} href={c.href} onNavigate={c.onNavigate} />
161
- : <ContentCard key={key} variant="catalog" fit={c.fit ?? 'cover'} ratio={c.ratio} title={c.title} detail={c.detail} media={c.media} actions={c.actions} onClick={c.onClick} href={c.href} onNavigate={c.onNavigate} expanded={c.expanded} expandedContent={c.expandedContent} />
209
+ ? <ContentRow key={key} {...forwarded} variant={c.variant ?? rowVariant} />
210
+ : <ContentCard key={key} {...forwarded} variant={c.variant ?? cardVariant} fit={c.fit ?? 'cover'} />
162
211
  })}
163
212
  </div>
164
213
  )