@kolkrabbi/kol-shell 0.49.0 → 0.51.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 +62 -11
- package/src/ShortcutsOverlay.jsx +6 -2
- package/src/TouchDeviceOverlay.jsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.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.179.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)
|
|
@@ -82,18 +87,64 @@ function computeHiddenSet(items, expandedIdx) {
|
|
|
82
87
|
return hiddenSet
|
|
83
88
|
}
|
|
84
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
|
+
|
|
85
134
|
export default function CatalogPage({
|
|
135
|
+
/* FIRST, so every default below can read it */
|
|
136
|
+
preset = 'catalog',
|
|
86
137
|
header,
|
|
87
|
-
width = 'bleed',
|
|
138
|
+
width = PRESETS[preset]?.width ?? 'bleed',
|
|
88
139
|
items = [],
|
|
89
140
|
toCard,
|
|
90
141
|
filtersTitle,
|
|
91
142
|
filterGroups,
|
|
92
143
|
searchKeys = ['title', 'name'],
|
|
93
|
-
views,
|
|
144
|
+
views = PRESETS[preset]?.views,
|
|
94
145
|
view,
|
|
95
146
|
onViewChange,
|
|
96
|
-
defaultView,
|
|
147
|
+
defaultView = PRESETS[preset]?.defaultView,
|
|
97
148
|
layouts = LAYOUTS,
|
|
98
149
|
defaultLayout = 'grid',
|
|
99
150
|
walkthrough,
|
|
@@ -104,24 +155,24 @@ export default function CatalogPage({
|
|
|
104
155
|
trailingActions,
|
|
105
156
|
/* the grid track's floor. 160 is a 26-tile catalog's; a shelf of three decks
|
|
106
157
|
wants ~280 so the cards read at all. A ceiling of 6 tracks still applies. */
|
|
107
|
-
minColumn = 160,
|
|
158
|
+
minColumn = PRESETS[preset]?.minColumn ?? 160,
|
|
108
159
|
/* the ceiling — the six-column ruling is a MAXIMUM; a three-deck shelf says 3
|
|
109
160
|
(kol-client-olina 2026-09-03; user: "why are the cards so small? they could
|
|
110
161
|
use 2 columns each" — raising the floor to force it was backwards) */
|
|
111
|
-
maxColumns = 6,
|
|
162
|
+
maxColumns = PRESETS[preset]?.maxColumns ?? 6,
|
|
112
163
|
/* the LIST row's variant. `catalog` (default) is the 36px GridCard row — title
|
|
113
164
|
and detail on one line, no thumb — which is what every app-tier list was
|
|
114
165
|
ruled on (CatalogPageMonitorParity). A catalog whose grid shows a cover
|
|
115
166
|
passes `file`: the 48px thumb row, so the list shows the same cover small
|
|
116
167
|
(kol-client-olina 2026-09-03; user, on the bare row: "ugly ugly ugly"). */
|
|
117
|
-
rowVariant = 'catalog',
|
|
168
|
+
rowVariant = PRESETS[preset]?.rowVariant ?? 'catalog',
|
|
118
169
|
/* the GRID card's variant, per page; `toCard` may return `variant` per card */
|
|
119
|
-
cardVariant = 'catalog',
|
|
170
|
+
cardVariant = PRESETS[preset]?.cardVariant ?? 'catalog',
|
|
120
171
|
/* the LIST's container. `grid` (default) is monitor's ruling — rows four across
|
|
121
172
|
on a 240 floor, gap 8 (the documented `repeat(4, 1fr)`). `stack` is one row per
|
|
122
173
|
line, which is what a list of files is (MediaLibrary stacks); olina's deck list
|
|
123
174
|
rendered three 300px rows side by side (user: "why is it tiny?"). */
|
|
124
|
-
listLayout = 'grid',
|
|
175
|
+
listLayout = PRESETS[preset]?.listLayout ?? 'grid',
|
|
125
176
|
iconComponent,
|
|
126
177
|
showCountOnlyWhenFiltering = true,
|
|
127
178
|
filtersProps,
|
|
@@ -155,7 +206,7 @@ export default function CatalogPage({
|
|
|
155
206
|
}, [minColumn, maxColumns])
|
|
156
207
|
|
|
157
208
|
return (
|
|
158
|
-
<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 }}>
|
|
159
210
|
{header && <PageHeader {...header} />}
|
|
160
211
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
161
212
|
{open && <WalkthroughPanel steps={walkthrough.steps} iconComponent={walkthrough.iconComponent} />}
|
|
@@ -176,7 +227,7 @@ export default function CatalogPage({
|
|
|
176
227
|
defaultLayout={defaultLayout}
|
|
177
228
|
renderItem={(rows, viewMode, layout) => {
|
|
178
229
|
if (open) return null
|
|
179
|
-
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 })
|
|
180
231
|
const hidden = layout === 'list' ? new Set() : computeHiddenSet(rows, cards.findIndex((c) => c.expanded))
|
|
181
232
|
/* COLS IS A CEILING, NOT A COMMAND (CatalogPageMobileColumns,
|
|
182
233
|
* kol-monitor 2026-09-01) — the third home of the defect
|
package/src/ShortcutsOverlay.jsx
CHANGED
|
@@ -58,8 +58,12 @@ export default function ShortcutsOverlay({ shortcuts = [], onClose }) {
|
|
|
58
58
|
return (
|
|
59
59
|
<div
|
|
60
60
|
onClick={onClose}
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/* THE scrim (overlay-scrim-outliers, kol-client-olina 2026-09-03; user: "we
|
|
62
|
+
removed the blur and put just color on the background … we missed this").
|
|
63
|
+
This drew its own — an 8 % inverse wash plus a 2px blur — after the
|
|
64
|
+
2026-09-01 no-blur ruling and the one-tint-48 class. */
|
|
65
|
+
className="fixed inset-0 select-none kol-overlay-scrim"
|
|
66
|
+
style={{ display: 'grid', placeItems: 'center', zIndex: 'var(--kol-z-modal)' }}
|
|
63
67
|
>
|
|
64
68
|
<div className="text-fg-64 kol-helper-12 bg-surface-primary border border-fg-16" style={{ display: 'grid', gridTemplateColumns: 'auto auto', gap: '10px 62px', padding: 24, borderRadius: 4 }}>
|
|
65
69
|
{sectioned
|
|
@@ -34,8 +34,10 @@ export default function TouchDeviceOverlay({ appName = 'This app', message }) {
|
|
|
34
34
|
try { localStorage.setItem(STORAGE_KEY, '1') } catch { /* storage blocked */ }
|
|
35
35
|
setDismissed(true)
|
|
36
36
|
}
|
|
37
|
+
/* THE scrim, on the z ladder (overlay-scrim-outliers, 2026-09-03): was an 8 %
|
|
38
|
+
inverse wash + a 2px blur on a literal 100 */
|
|
37
39
|
return (
|
|
38
|
-
<div className="fixed inset-0 select-none
|
|
40
|
+
<div className="fixed inset-0 select-none kol-overlay-scrim" style={{ display: 'grid', placeItems: 'center', zIndex: 'var(--kol-z-modal)' }}>
|
|
39
41
|
<div className="bg-surface-primary border border-oq-08" style={{ width: 360, borderRadius: 4, padding: '20px 20px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
40
42
|
<div className="kol-helper-14 text-fg-96">Desktop recommended</div>
|
|
41
43
|
<div className="kol-mono-12 text-fg-64">
|