@kolkrabbi/kol-shell 0.48.0 → 0.50.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 +3 -3
- package/src/CatalogPage.jsx +69 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.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",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"./src/*": "./src/*"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@kolkrabbi/kol-component": ">=0.
|
|
15
|
+
"@kolkrabbi/kol-component": ">=0.178.0",
|
|
16
16
|
"@kolkrabbi/kol-icons": ">=0.16.0",
|
|
17
17
|
"@kolkrabbi/kol-theme": ">=0.138.0",
|
|
18
18
|
"gsap": "^3.13.0",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
+
"@kolkrabbi/kol-component": "^0.178.0",
|
|
23
24
|
"@kolkrabbi/kol-icons": "^0.25.0",
|
|
24
|
-
"@kolkrabbi/kol-component": "^0.177.0",
|
|
25
25
|
"@kolkrabbi/kol-framework": "^0.42.0",
|
|
26
26
|
"@kolkrabbi/kol-theme": "^0.138.0"
|
|
27
27
|
},
|
package/src/CatalogPage.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from 'react'
|
|
2
|
-
import { ContentCard, ContentFilters, ContentRow } from '@kolkrabbi/kol-component'
|
|
2
|
+
import { ActionButton, ContentCard, ContentFilters, ContentRow, SizeOrDownload, formatSize } from '@kolkrabbi/kol-component'
|
|
3
3
|
import PageShell from './PageShell.jsx'
|
|
4
4
|
import { PageHeader } from '@kolkrabbi/kol-component'
|
|
5
5
|
import WalkthroughPanel from './WalkthroughPanel.jsx'
|
|
@@ -37,6 +37,11 @@ import WalkthroughPanel from './WalkthroughPanel.jsx'
|
|
|
37
37
|
* variant's A4, so nothing existing moves. Anything `ContentFilters` takes that the page has no
|
|
38
38
|
* prop for goes through `filtersProps` — `mutuallyExclusiveFilters` was the gap.
|
|
39
39
|
*
|
|
40
|
+
* @param {'catalog'|'shelf'} preset the whole page as one word (default `catalog` = today's defaults). `shelf` = a
|
|
41
|
+
* shelf of slide decks: capped · 3 tracks on a 280 floor · `slide` card and row · stacked
|
|
42
|
+
* list · `kol-tone-secondary` on the root · the All / Recent view strip; `toCard` returns
|
|
43
|
+
* the deck's fields and handlers — `title date bytes count cover href onNavigate onDownload
|
|
44
|
+
* onFavourite onDelete favourited` — and the page renders the slots. Explicit props win.
|
|
40
45
|
* @param {Object} header PageHeader props — `{ title, subtitle, size, voice, eyebrow }`
|
|
41
46
|
* @param {Array} items the objects `ContentFilters` filters and searches
|
|
42
47
|
* @param {Function} toCard (item, { view, layout }) => card props (see above)
|
|
@@ -53,7 +58,8 @@ import WalkthroughPanel from './WalkthroughPanel.jsx'
|
|
|
53
58
|
* `bleed` (default) fills the window — the app tier; `capped` centres on the
|
|
54
59
|
* framework container — the site tier, so a brand book adopting this page keeps its geometry
|
|
55
60
|
* @param {ReactNode} trailingActions the header's RIGHT slot (ContentFilters' — MediaLibrary's SELECT / FLAT); never empty beside the divider
|
|
56
|
-
* @param {number} minColumn the grid track's floor in px (default 160)
|
|
61
|
+
* @param {number} minColumn the grid track's floor in px (default 160) — a minimum, never a count
|
|
62
|
+
* @param {number} maxColumns the grid's ceiling (default 6, the ruling); a three-deck shelf passes 3 and the tracks share the width
|
|
57
63
|
* @param {string} cardVariant the grid card's variant, per page (default `catalog`); `toCard` may return `variant` per card
|
|
58
64
|
* @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
65
|
* its slots (`date` · `size` · `meta` · `selected`) come from `toCard` like everything else
|
|
@@ -81,18 +87,64 @@ function computeHiddenSet(items, expandedIdx) {
|
|
|
81
87
|
return hiddenSet
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
/* PRESETS — a whole page frozen as one word (slide-variant-and-shelf-preset,
|
|
91
|
+
* kol-client-olina 2026-09-03; user, after building /slide-deck by hand through
|
|
92
|
+
* eleven props: "this is a look I DO NOT want to do this again… this is a
|
|
93
|
+
* layout SET" · "freeze this so I can use AS IS again"). `catalog` is today's
|
|
94
|
+
* defaults, byte-identical. `shelf` is olina's /slide-deck as it stood, every
|
|
95
|
+
* value read off the render. An explicit prop always wins over its preset. */
|
|
96
|
+
const PRESETS = {
|
|
97
|
+
catalog: {},
|
|
98
|
+
shelf: {
|
|
99
|
+
width: 'capped', maxColumns: 3, minColumn: 280,
|
|
100
|
+
cardVariant: 'slide', rowVariant: 'slide', listLayout: 'stack',
|
|
101
|
+
/* one word tones every control on the page (tone-is-the-ground-axis) */
|
|
102
|
+
className: 'kol-tone-secondary',
|
|
103
|
+
/* the header's right slot is the view strip — ContentFilters' own idiom */
|
|
104
|
+
views: [{ value: 'all', label: 'All' }, { value: 'recent', label: 'Recent' }], defaultView: 'all',
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
const stop = (e) => { e.preventDefault(); e.stopPropagation() }
|
|
108
|
+
/* the shelf's DATA → the slide variant's slots, the media admin's idiom
|
|
109
|
+
* (MediaLibraryPages:704) verbatim from olina's /slide-deck: download is the
|
|
110
|
+
* control ON the image (chrome="media"), the plate carries star (toggle) and
|
|
111
|
+
* trash (confirm) in a column, the row carries download · star · trash inline;
|
|
112
|
+
* the size cell is SizeOrDownload; `count` is the slide count. A slot the
|
|
113
|
+
* consumer supplies itself is left alone. The consumer writes no JSX for
|
|
114
|
+
* actions — that is the point of the preset. */
|
|
115
|
+
function toSlideCard(c, { layout }) {
|
|
116
|
+
const { bytes, count, cover, favourited, onDownload, onFavourite, onDelete, downloadHref, ...rest } = c
|
|
117
|
+
const list = layout === 'list'
|
|
118
|
+
return {
|
|
119
|
+
...rest,
|
|
120
|
+
media: rest.media ?? (cover ? <img src={cover} alt="" loading="lazy" /> : null),
|
|
121
|
+
size: rest.size ?? (bytes != null ? <SizeOrDownload href={downloadHref ?? '#'}>{formatSize(bytes)}</SizeOrDownload> : undefined),
|
|
122
|
+
meta: rest.meta ?? (count ? `${count} slides` : undefined),
|
|
123
|
+
control: rest.control ?? (!list && onDownload ? <ActionButton chrome="media" icon="download" confirmIcon="check" label="Download" confirmLabel="Downloaded" onAction={onDownload} /> : undefined),
|
|
124
|
+
actions: rest.actions ?? ((onFavourite || onDelete || (list && onDownload)) ? (
|
|
125
|
+
<div className={list ? 'flex items-center gap-2' : 'flex h-full flex-col items-center justify-between'} onClick={stop}>
|
|
126
|
+
{list && onDownload && <ActionButton chrome="inline" size="sm" icon="download" label="Download" onAction={onDownload} />}
|
|
127
|
+
{onFavourite && <ActionButton chrome="inline" size="sm" icon={favourited ? 'star-solid' : 'star'} label="Favourite" toggle onAction={onFavourite} />}
|
|
128
|
+
{onDelete && <ActionButton chrome="inline" size="sm" icon="trash" confirmIcon="check" label="Delete" confirmLabel="Deleted" onAction={onDelete} />}
|
|
129
|
+
</div>
|
|
130
|
+
) : undefined),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
84
134
|
export default function CatalogPage({
|
|
135
|
+
/* FIRST, so every default below can read it */
|
|
136
|
+
preset = 'catalog',
|
|
85
137
|
header,
|
|
86
|
-
width = 'bleed',
|
|
138
|
+
width = PRESETS[preset]?.width ?? 'bleed',
|
|
87
139
|
items = [],
|
|
88
140
|
toCard,
|
|
89
141
|
filtersTitle,
|
|
90
142
|
filterGroups,
|
|
91
143
|
searchKeys = ['title', 'name'],
|
|
92
|
-
views,
|
|
144
|
+
views = PRESETS[preset]?.views,
|
|
93
145
|
view,
|
|
94
146
|
onViewChange,
|
|
95
|
-
defaultView,
|
|
147
|
+
defaultView = PRESETS[preset]?.defaultView,
|
|
96
148
|
layouts = LAYOUTS,
|
|
97
149
|
defaultLayout = 'grid',
|
|
98
150
|
walkthrough,
|
|
@@ -103,20 +155,24 @@ export default function CatalogPage({
|
|
|
103
155
|
trailingActions,
|
|
104
156
|
/* the grid track's floor. 160 is a 26-tile catalog's; a shelf of three decks
|
|
105
157
|
wants ~280 so the cards read at all. A ceiling of 6 tracks still applies. */
|
|
106
|
-
minColumn = 160,
|
|
158
|
+
minColumn = PRESETS[preset]?.minColumn ?? 160,
|
|
159
|
+
/* the ceiling — the six-column ruling is a MAXIMUM; a three-deck shelf says 3
|
|
160
|
+
(kol-client-olina 2026-09-03; user: "why are the cards so small? they could
|
|
161
|
+
use 2 columns each" — raising the floor to force it was backwards) */
|
|
162
|
+
maxColumns = PRESETS[preset]?.maxColumns ?? 6,
|
|
107
163
|
/* the LIST row's variant. `catalog` (default) is the 36px GridCard row — title
|
|
108
164
|
and detail on one line, no thumb — which is what every app-tier list was
|
|
109
165
|
ruled on (CatalogPageMonitorParity). A catalog whose grid shows a cover
|
|
110
166
|
passes `file`: the 48px thumb row, so the list shows the same cover small
|
|
111
167
|
(kol-client-olina 2026-09-03; user, on the bare row: "ugly ugly ugly"). */
|
|
112
|
-
rowVariant = 'catalog',
|
|
168
|
+
rowVariant = PRESETS[preset]?.rowVariant ?? 'catalog',
|
|
113
169
|
/* the GRID card's variant, per page; `toCard` may return `variant` per card */
|
|
114
|
-
cardVariant = 'catalog',
|
|
170
|
+
cardVariant = PRESETS[preset]?.cardVariant ?? 'catalog',
|
|
115
171
|
/* the LIST's container. `grid` (default) is monitor's ruling — rows four across
|
|
116
172
|
on a 240 floor, gap 8 (the documented `repeat(4, 1fr)`). `stack` is one row per
|
|
117
173
|
line, which is what a list of files is (MediaLibrary stacks); olina's deck list
|
|
118
174
|
rendered three 300px rows side by side (user: "why is it tiny?"). */
|
|
119
|
-
listLayout = 'grid',
|
|
175
|
+
listLayout = PRESETS[preset]?.listLayout ?? 'grid',
|
|
120
176
|
iconComponent,
|
|
121
177
|
showCountOnlyWhenFiltering = true,
|
|
122
178
|
filtersProps,
|
|
@@ -142,15 +198,15 @@ export default function CatalogPage({
|
|
|
142
198
|
observer.current?.disconnect()
|
|
143
199
|
observer.current = null
|
|
144
200
|
if (!el) return
|
|
145
|
-
const measure = (W) => setCols((c) => { const n = Math.max(1, Math.min(
|
|
201
|
+
const measure = (W) => setCols((c) => { const n = Math.max(1, Math.min(maxColumns, Math.floor((W + 24) / (minColumn + 24)))); return c === n ? c : n })
|
|
146
202
|
measure(el.getBoundingClientRect().width)
|
|
147
203
|
const ro = new ResizeObserver(([entry]) => measure(entry.contentRect.width))
|
|
148
204
|
ro.observe(el)
|
|
149
205
|
observer.current = ro
|
|
150
|
-
}, [minColumn])
|
|
206
|
+
}, [minColumn, maxColumns])
|
|
151
207
|
|
|
152
208
|
return (
|
|
153
|
-
<PageShell width={width} className={className} style={{ overflow: 'hidden', '--kol-catalog-n': cols ?? undefined, ...style }}>
|
|
209
|
+
<PageShell width={width} className={`${PRESETS[preset]?.className ?? ''} ${className}`.trim()} style={{ overflow: 'hidden', '--kol-catalog-n': cols ?? undefined, ...style }}>
|
|
154
210
|
{header && <PageHeader {...header} />}
|
|
155
211
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
156
212
|
{open && <WalkthroughPanel steps={walkthrough.steps} iconComponent={walkthrough.iconComponent} />}
|
|
@@ -171,7 +227,7 @@ export default function CatalogPage({
|
|
|
171
227
|
defaultLayout={defaultLayout}
|
|
172
228
|
renderItem={(rows, viewMode, layout) => {
|
|
173
229
|
if (open) return null
|
|
174
|
-
const cards = rows.map((item) => toCard ? toCard(item, { view: viewMode, layout }) : { title: item.title ?? item.name, detail: item.detail })
|
|
230
|
+
const cards = rows.map((item) => { const c = toCard ? toCard(item, { view: viewMode, layout }) : { title: item.title ?? item.name, detail: item.detail }; return preset === 'shelf' ? toSlideCard(c, { layout }) : c })
|
|
175
231
|
const hidden = layout === 'list' ? new Set() : computeHiddenSet(rows, cards.findIndex((c) => c.expanded))
|
|
176
232
|
/* COLS IS A CEILING, NOT A COMMAND (CatalogPageMobileColumns,
|
|
177
233
|
* kol-monitor 2026-09-01) — the third home of the defect
|