@kolkrabbi/kol-shell 0.58.0 → 0.59.1
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/README.md +1 -0
- package/package.json +7 -7
- package/src/AppHub.jsx +25 -9
- package/src/AppShell.jsx +27 -5
- package/src/AppStudio.jsx +101 -0
- package/src/CatalogPage.jsx +2 -1
- package/src/HubHome.jsx +3 -3
- package/src/NavRail.jsx +3 -1
- package/src/PhoneNav.jsx +78 -0
- package/src/SettingsScaffold.jsx +1 -1
- package/src/WalkthroughPanel.jsx +1 -1
- package/src/index.js +3 -0
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ Same consumer contract as every KOL package — raw `.jsx` source, so:
|
|
|
36
36
|
| `WalkthroughPanel` | Centred stepped intro card; steps/illustrations/actions are content |
|
|
37
37
|
| `ShortcutsOverlay` | Blurred scrim + flat 2-col shortcut sheet at `--kol-z-modal` |
|
|
38
38
|
| `Logomark` | Fetch-and-inline SVG mark (currentColor-safe in dark mode) |
|
|
39
|
+
| `AppStudio` | The workstation — the Hub plus Home · Library · Create · Use · opt-in pages · Settings, in that order, mono by default; each slot renames with `{ path, label, icon }` (reference app: `apps/studio`) |
|
|
39
40
|
|
|
40
41
|
## Wiring (react-router example)
|
|
41
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.1",
|
|
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,18 +12,18 @@
|
|
|
12
12
|
"./src/*": "./src/*"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@kolkrabbi/kol-component": ">=0.
|
|
15
|
+
"@kolkrabbi/kol-component": ">=0.229.0",
|
|
16
16
|
"@kolkrabbi/kol-icons": ">=0.16.0",
|
|
17
|
-
"@kolkrabbi/kol-theme": ">=0.
|
|
17
|
+
"@kolkrabbi/kol-theme": ">=0.156.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-
|
|
24
|
-
"@kolkrabbi/kol-
|
|
25
|
-
"@kolkrabbi/kol-icons": "^0.29.
|
|
26
|
-
"@kolkrabbi/kol-theme": "^0.
|
|
23
|
+
"@kolkrabbi/kol-framework": "^0.46.0",
|
|
24
|
+
"@kolkrabbi/kol-component": "^0.230.0",
|
|
25
|
+
"@kolkrabbi/kol-icons": "^0.29.1",
|
|
26
|
+
"@kolkrabbi/kol-theme": "^0.157.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src",
|
package/src/AppHub.jsx
CHANGED
|
@@ -16,7 +16,7 @@ import { ShortcutsOverlay } from '@kolkrabbi/kol-component'
|
|
|
16
16
|
* those decisions made once. An app describes itself and supplies its tool.
|
|
17
17
|
*
|
|
18
18
|
* ROUTES: `homePath` (default `/`) is Home, `settingsPath` is Settings, every other path renders
|
|
19
|
-
* `children` — the tool. A tool that should be what LOADS moves Home off `/` (media-
|
|
19
|
+
* `children` — the tool. A tool that should be what LOADS moves Home off `/` (media-hub, user
|
|
20
20
|
* 2026-09-26: "the point of this is not to bury media, the column browser is what we want loaded"):
|
|
21
21
|
* `homePath="/library"`, a rail row for it, and the mark still goes to `/` — the tool.
|
|
22
22
|
* Router-agnostic like AppShell: pass `currentPath` + `onNavigate`, and your router's element
|
|
@@ -29,12 +29,19 @@ import { ShortcutsOverlay } from '@kolkrabbi/kol-component'
|
|
|
29
29
|
* @param {Array} items the tool's rail rows `{ icon, path, label }` — Home is the mark, Settings is pinned
|
|
30
30
|
* @param {string} currentPath · {Function} onNavigate · {ElementType} iconComponent as AppShell
|
|
31
31
|
* @param {string} homePath where Home lives (default `/`); anything else puts the tool at `/`
|
|
32
|
-
* @param {Object} home HubHome's props (`items`, `toCard`, `views`, `actions`, `filterGroups`, …)
|
|
32
|
+
* @param {Object} home HubHome's props (`items`, `toCard`, `views`, `actions`, `filterGroups`, …).
|
|
33
|
+
* OPT-IN (apps review 2026-09-29): no `home`, no Home page — `homePath` renders the
|
|
34
|
+
* tool and the mark goes there (media-hub: the column browser IS the app)
|
|
33
35
|
* @param {Array} walkthrough the steps Home's Walkthrough button opens
|
|
34
|
-
* @param {Object} settings HubSettings' props (`content`, `tabs`, `picker`, `onOpenSettings`, `comboLabel`)
|
|
36
|
+
* @param {Object} settings HubSettings' props (`content`, `tabs`, `picker`, `onOpenSettings`, `comboLabel`).
|
|
37
|
+
* OPT-IN like AppShell's `bottomItems` (apps review 2026-09-29: *"not sure it makes sense
|
|
38
|
+
* to neccessarily have settings manditory part of the rail"*) — no `settings`, no rail
|
|
39
|
+
* row, no route, no `,` key. It was pinned whether the app had settings or not.
|
|
35
40
|
* @param {Array} shortcuts `[{ section, items: [{ id, label, combo }] }]` — the sheet AND Settings
|
|
36
41
|
* @param {ReactNode} themeToggle Settings' masthead toggle (a node — kol-framework is not a peer)
|
|
37
42
|
* @param {string} shortcutsKey default `s`; `null` turns the sheet key off (a tool that owns S)
|
|
43
|
+
* @param {'display'|'mono'} masthead the app's one masthead (AppShell's) — set, Home and Settings follow it; unset
|
|
44
|
+
* (the default), they keep the Hub's mono title (2026-09-29)
|
|
38
45
|
* @param {Object} shell any AppShell prop, spread last (`touch`, `pageWash`, `drawerOpenOn`, `railComponent`, …)
|
|
39
46
|
*/
|
|
40
47
|
const SETTINGS_ROW = (path) => [{ icon: 'nav-settings', path, label: 'Settings' }]
|
|
@@ -53,6 +60,7 @@ export default function AppHub({
|
|
|
53
60
|
settingsPath = '/settings',
|
|
54
61
|
homePath = '/',
|
|
55
62
|
shortcutsKey = 's',
|
|
63
|
+
masthead,
|
|
56
64
|
shell,
|
|
57
65
|
children,
|
|
58
66
|
}) {
|
|
@@ -71,26 +79,34 @@ export default function AppHub({
|
|
|
71
79
|
return () => window.removeEventListener('keydown', onKey)
|
|
72
80
|
}, [shortcutsKey])
|
|
73
81
|
|
|
74
|
-
const page = currentPath === homePath
|
|
82
|
+
const page = home && currentPath === homePath
|
|
75
83
|
? <HubHome app={app} iconComponent={iconComponent} walkthrough={walkthrough} {...home} />
|
|
76
|
-
: currentPath === settingsPath
|
|
84
|
+
: settings && currentPath === settingsPath
|
|
77
85
|
? <HubSettings app={app} shortcuts={shortcuts} themeToggle={themeToggle} {...settings} />
|
|
78
86
|
: children
|
|
87
|
+
const bottom = settings ? SETTINGS_ROW(settingsPath) : []
|
|
88
|
+
/* the phone bar lists Home as a tab — the rail draws it as the mark, the bar has no mark */
|
|
89
|
+
const barItems = [...(home ? [{ icon: 'home-01', path: homePath, label: 'Home' }] : []), ...items, ...bottom]
|
|
79
90
|
|
|
80
91
|
return (
|
|
81
92
|
<AppShell
|
|
82
93
|
items={items}
|
|
83
|
-
bottomItems={
|
|
94
|
+
bottomItems={bottom}
|
|
95
|
+
barItems={barItems}
|
|
84
96
|
logomark={app.logomark ? { svgUrl: app.logomark, title: app.name } : undefined}
|
|
85
97
|
currentPath={currentPath}
|
|
86
98
|
onNavigate={onNavigate}
|
|
87
99
|
iconComponent={iconComponent}
|
|
88
100
|
appName={app.name}
|
|
89
101
|
railToggleKey={'\\'}
|
|
90
|
-
settingsPath={settingsPath}
|
|
91
|
-
settingsKey=
|
|
102
|
+
settingsPath={settings ? settingsPath : undefined}
|
|
103
|
+
settingsKey={settings ? ',' : undefined}
|
|
92
104
|
navKeys
|
|
93
|
-
|
|
105
|
+
/* THE PHONE BAR, not the drawer (apps review 2026-09-29): the drawer's hamburger is fixed
|
|
106
|
+
* top-right, over the masthead's own controls — validate:render caught it on every Hub page
|
|
107
|
+
* with a gear. `shell={{ touch: 'drawer' }}` still gets the drawer. */
|
|
108
|
+
touch="bar"
|
|
109
|
+
masthead={masthead}
|
|
94
110
|
pageWash="var(--kol-fg-02)"
|
|
95
111
|
{...shell}
|
|
96
112
|
>
|
package/src/AppShell.jsx
CHANGED
|
@@ -4,6 +4,8 @@ import NavRail from './NavRail.jsx'
|
|
|
4
4
|
import { NavHiddenContext } from './navHidden.js'
|
|
5
5
|
import { SettingsToggleContext } from './settingsToggle.js'
|
|
6
6
|
import TouchDeviceOverlay, { useTouchPrimary } from './TouchDeviceOverlay.jsx'
|
|
7
|
+
import PhoneNav from './PhoneNav.jsx'
|
|
8
|
+
import { TABBAR_H, MastheadContext } from '@kolkrabbi/kol-component'
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* AppShell — layout root: the rail + the content column. No header, no
|
|
@@ -42,7 +44,7 @@ import TouchDeviceOverlay, { useTouchPrimary } from './TouchDeviceOverlay.jsx'
|
|
|
42
44
|
* toggle lives on the settings page, not in the rail (user, 2026-08-28).
|
|
43
45
|
* @param {string} props.railToggleKey a key that toggles the rail (e.g. '\\') — ignored while typing in a field;
|
|
44
46
|
* the rail comes back on every `currentPath` change (ShellHomeSystem, 2026-08-27)
|
|
45
|
-
* @param {'shell'|'bare'|'overlay'|'drawer'} props.touch the touch-primary policy (default 'shell' = the rail regardless):
|
|
47
|
+
* @param {'shell'|'bare'|'overlay'|'drawer'|'bar'} props.touch the touch-primary policy (default 'shell' = the rail regardless):
|
|
46
48
|
* 'drawer' takes the rail OFF-CANVAS below `drawerBelow`, hands its width
|
|
47
49
|
* back to the content, and renders a trigger that brings it in over a scrim.
|
|
48
50
|
* Tapping a destination closes it. (ShellRailNoDrawerOnMobile, kol-chess
|
|
@@ -50,6 +52,20 @@ import TouchDeviceOverlay, { useTouchPrimary } from './TouchDeviceOverlay.jsx'
|
|
|
50
52
|
* `railToggleKey` is a KEY — a phone has no keyboard, so on the device where
|
|
51
53
|
* the rail costs most it could not be dismissed at all. `bare` was the only
|
|
52
54
|
* other way to reclaim the width and it throws navigation away entirely.)
|
|
55
|
+
* 'bar' (apps review 2026-09-29) takes the rail off below `drawerBelow` too, and
|
|
56
|
+
* puts it in a BOTTOM BAR (`PhoneNav`, on kol-component's MobileTabBar): up to five
|
|
57
|
+
* destinations, else four + More, a sheet with the rest and Settings. No
|
|
58
|
+
* hamburger over the page's own controls, and ten pages still fit. The content
|
|
59
|
+
* gets the bar's height as bottom padding. `barItems` overrides what it lists.
|
|
60
|
+
* @param {'display'|'mono'} [props.masthead] THE APP'S MASTHEAD, set once here and read by every
|
|
61
|
+
* page inside (apps review 2026-09-29, ruling D3): `PageHeader`, the Hub's
|
|
62
|
+
* pages, `CatalogPage` and a tool's own title. `display` = the display
|
|
63
|
+
* voice, no description (MEDIA, CURVES); `mono` = the mono title with its
|
|
64
|
+
* description. One app, one header — media-hub wore both. NO DEFAULT (user, 2026-09-29): unset,
|
|
65
|
+
* every page renders as its own props say, so a consumer's bump moves nothing
|
|
66
|
+
* (fxr · mirror · monitor are mono by ruling).
|
|
67
|
+
* @param {Array} [props.barItems] the bar's destinations under `touch="bar"` (default items + bottomItems) —
|
|
68
|
+
* a Hub passes Home first, which the rail draws as the mark instead
|
|
53
69
|
* @param {number} [props.drawerBelow=768] viewport width under which `touch="drawer"` folds. A width, not a
|
|
54
70
|
* pointer test: an iPad is coarse and has room, a narrow desktop window is
|
|
55
71
|
* fine-pointered and does not.
|
|
@@ -74,7 +90,7 @@ import TouchDeviceOverlay, { useTouchPrimary } from './TouchDeviceOverlay.jsx'
|
|
|
74
90
|
* tab switch. Matched on `e.code` (`Digit1`…) because Opt+digit yields
|
|
75
91
|
* `¡ ™ £ ¢` as `e.key` on macOS; ignored while typing in a field; only
|
|
76
92
|
* digits with an item; `preventDefault` on a match. Default off.
|
|
77
|
-
* @param {string} props.pageWash a CSS
|
|
93
|
+
* @param {string} props.pageWash a CSS color painted by `PageShell` OVER the shell's primary back
|
|
78
94
|
* (ShellPageWash, kol-monitor 2026-08-27 — user: "the back of the back
|
|
79
95
|
* should be primary — then you can just add transparent on top to step
|
|
80
96
|
* up the lightness"): `'var(--kol-fg-12)'` steps the page a rung lighter
|
|
@@ -123,6 +139,8 @@ export default function AppShell({
|
|
|
123
139
|
settingsKey,
|
|
124
140
|
drawerBelow = 768,
|
|
125
141
|
drawerOpenOn = NO_PATHS,
|
|
142
|
+
barItems,
|
|
143
|
+
masthead,
|
|
126
144
|
children,
|
|
127
145
|
}) {
|
|
128
146
|
const [navHidden, setNavHidden] = useState(false)
|
|
@@ -141,6 +159,7 @@ export default function AppShell({
|
|
|
141
159
|
return () => mq.removeEventListener('change', on)
|
|
142
160
|
}, [drawerBelow])
|
|
143
161
|
const drawer = touch === 'drawer' && narrow
|
|
162
|
+
const bar = touch === 'bar' && narrow
|
|
144
163
|
const [drawerOpen, setDrawerOpen] = useState(false)
|
|
145
164
|
useEffect(() => { if (!drawer) setDrawerOpen(false) }, [drawer])
|
|
146
165
|
useEffect(() => {
|
|
@@ -299,6 +318,7 @@ export default function AppShell({
|
|
|
299
318
|
if (touch === 'bare' && coarse && !wantsDesktop) return <div className="kol-app-shell contents" style={{ '--kol-shell-page-wash': pageWash }}>{children}</div>
|
|
300
319
|
|
|
301
320
|
return (
|
|
321
|
+
<MastheadContext.Provider value={masthead}>
|
|
302
322
|
<NavHiddenContext.Provider value={{ navHidden, setNavHidden }}>
|
|
303
323
|
<SettingsToggleContext.Provider value={toggleSettings}>
|
|
304
324
|
{/* `kol-app-shell` = the app tier: neutral ::selection (kol-theme).
|
|
@@ -311,7 +331,7 @@ export default function AppShell({
|
|
|
311
331
|
* margin for `navHidden` closes it here — the rail then takes its own
|
|
312
332
|
* `--kol-shell-drawer-width` rather than this token. */
|
|
313
333
|
data-rail-drawer={drawer ? (drawerOpen ? 'open' : 'closed') : undefined}
|
|
314
|
-
style={navHidden || drawer ? { '--kol-shell-rail-width': '0px' } : undefined}
|
|
334
|
+
style={navHidden || drawer || bar ? { '--kol-shell-rail-width': '0px' } : undefined}
|
|
315
335
|
>
|
|
316
336
|
{touch === 'overlay' && <TouchDeviceOverlay appName={appName} />}
|
|
317
337
|
{/* THE TRIGGER SHIPS HERE, not in every consumer's page header — the rail
|
|
@@ -339,7 +359,8 @@ export default function AppShell({
|
|
|
339
359
|
onClick={() => setDrawerOpen(false)}
|
|
340
360
|
/>
|
|
341
361
|
)}
|
|
342
|
-
{
|
|
362
|
+
{bar && <PhoneNav items={barItems ?? [...(items ?? []), ...(bottomItems ?? [])]} currentPath={currentPath} onNavigate={navigate} />}
|
|
363
|
+
{!navHidden && !bar && (
|
|
343
364
|
<Rail
|
|
344
365
|
items={items}
|
|
345
366
|
bottomItems={bottomItems}
|
|
@@ -355,11 +376,12 @@ export default function AppShell({
|
|
|
355
376
|
* page paints its wash over it (ShellPageWash). */}
|
|
356
377
|
{/* the margin IS the rail's live width, so the content is pushed
|
|
357
378
|
* through the drag and the snap (RailFlatGrabOpen) */}
|
|
358
|
-
<div className="bg-surface-primary min-w-0" style={{ marginLeft: `var(--kol-shell-rail-width)`, '--kol-shell-page-wash': pageWash }}>
|
|
379
|
+
<div className="bg-surface-primary min-w-0" style={{ marginLeft: `var(--kol-shell-rail-width)`, '--kol-shell-page-wash': pageWash, ...(bar && { '--kol-shell-bar-h': `calc(${TABBAR_H}px + env(safe-area-inset-bottom, 0px))`, paddingBottom: 'var(--kol-shell-bar-h)' }) }}>
|
|
359
380
|
{children}
|
|
360
381
|
</div>
|
|
361
382
|
</div>
|
|
362
383
|
</SettingsToggleContext.Provider>
|
|
363
384
|
</NavHiddenContext.Provider>
|
|
385
|
+
</MastheadContext.Provider>
|
|
364
386
|
)
|
|
365
387
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { PageHeader } from '@kolkrabbi/kol-component'
|
|
2
|
+
import AppHub from './AppHub.jsx'
|
|
3
|
+
import CatalogPage from './CatalogPage.jsx'
|
|
4
|
+
import PageShell from './PageShell.jsx'
|
|
5
|
+
|
|
6
|
+
/* taxonomy-ok: organism — nests AppHub / CatalogPage / PageShell (relative) + kol-component's PageHeader */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AppStudio — the WORKSTATION: the Hub plus a fixed page set (apps review §6c-4, 2026-09-29).
|
|
10
|
+
*
|
|
11
|
+
* fxr, mirror and monitor each hand-build the same shape — a landing Catalog, a Library of what the
|
|
12
|
+
* tool makes, a page that composes a new one, the tool itself full-bleed, a page or two of their own,
|
|
13
|
+
* Settings. monitor.kolkrabbi.io is the reference: Home · Library (patches, modules) · Create (CASE ·
|
|
14
|
+
* MODULES) · Rack · Stage · Settings. The Studio is that order, those keys, that masthead and that
|
|
15
|
+
* phone bar made once; an app passes the pages and nothing about how they sit.
|
|
16
|
+
*
|
|
17
|
+
* Home / `home` — HubHome: RECENT · SAVED, New ‹thing›, Walkthrough; the mark goes here
|
|
18
|
+
* Library /library `library` — a CatalogPage of the content
|
|
19
|
+
* Create /create `create` — a Catalog-headed editor page: `header` + the editor as `children`
|
|
20
|
+
* Use /use `use` — the tool, full-bleed: `children`, no page padding, no wash
|
|
21
|
+
* … pages[].path `pages` — opt-in pages (monitor: Stage), each `{ path, icon, label, children }`
|
|
22
|
+
* Settings /settings `settings` — HubSettings, pinned at the rail's foot
|
|
23
|
+
*
|
|
24
|
+
* Library, Create and Use each take `{ path, label, icon }` to rename a slot (monitor calls Use
|
|
25
|
+
* "Rack" at /rack) — the ORDER is the Studio's, not the app's. Omit one and it is not there.
|
|
26
|
+
* MONO by default (fxr · mirror · monitor are mono by ruling); `masthead="display"` overrides.
|
|
27
|
+
* Router-agnostic like AppShell: `currentPath` + `onNavigate`. Everything else is AppHub's.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} app AppHub's `{ name, subtitle, logomark, about, links }`
|
|
30
|
+
* @param {Object} home HubHome's props — the landing Catalog (required: a Studio opens on Home)
|
|
31
|
+
* @param {Object} [library] CatalogPage's props, plus `{ path, label, icon }`
|
|
32
|
+
* @param {Object} [create] `{ header (PageHeader props), children, path, label, icon }`
|
|
33
|
+
* @param {Object} [use] `{ children, path, label, icon }` — the tool
|
|
34
|
+
* @param {Array} [pages] `[{ path, icon, label, children }]` — opt-in pages, after Use
|
|
35
|
+
* @param {Object} [settings] HubSettings' props
|
|
36
|
+
* @param {'mono'|'display'} [masthead='mono']
|
|
37
|
+
* @param {Object} [shell] any AppShell prop, spread last
|
|
38
|
+
*/
|
|
39
|
+
export default function AppStudio({
|
|
40
|
+
app,
|
|
41
|
+
currentPath,
|
|
42
|
+
onNavigate,
|
|
43
|
+
iconComponent,
|
|
44
|
+
home,
|
|
45
|
+
library,
|
|
46
|
+
create,
|
|
47
|
+
use,
|
|
48
|
+
pages = [],
|
|
49
|
+
settings,
|
|
50
|
+
walkthrough,
|
|
51
|
+
shortcuts,
|
|
52
|
+
themeToggle,
|
|
53
|
+
shortcutsKey,
|
|
54
|
+
masthead = 'mono',
|
|
55
|
+
shell,
|
|
56
|
+
}) {
|
|
57
|
+
const slot = (cfg, path, label, icon) => cfg && { ...cfg, path: cfg.path ?? path, label: cfg.label ?? label, icon: cfg.icon ?? icon }
|
|
58
|
+
const lib = slot(library, '/library', 'Library', 'nav-library')
|
|
59
|
+
const make = slot(create, '/create', 'Create', 'nav-create')
|
|
60
|
+
const tool = slot(use, '/use', 'Use', 'nav-rack')
|
|
61
|
+
const rows = [lib, make, tool, ...pages].filter(Boolean)
|
|
62
|
+
const at = (p) => currentPath === p || currentPath.startsWith(`${p}/`)
|
|
63
|
+
|
|
64
|
+
let page = null
|
|
65
|
+
if (lib && at(lib.path)) {
|
|
66
|
+
const { path: _p, label: _l, icon: _i, ...props } = lib
|
|
67
|
+
page = <CatalogPage {...props} />
|
|
68
|
+
} else if (make && at(make.path)) {
|
|
69
|
+
page = (
|
|
70
|
+
<PageShell>
|
|
71
|
+
{make.header && <PageHeader size="sm" {...make.header} />}
|
|
72
|
+
{make.children}
|
|
73
|
+
</PageShell>
|
|
74
|
+
)
|
|
75
|
+
} else if (tool && at(tool.path)) {
|
|
76
|
+
page = tool.children
|
|
77
|
+
} else {
|
|
78
|
+
page = pages.find((p) => at(p.path))?.children ?? null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<AppHub
|
|
83
|
+
app={app}
|
|
84
|
+
items={rows.map(({ path, icon, label }) => ({ path, icon, label }))}
|
|
85
|
+
currentPath={currentPath}
|
|
86
|
+
onNavigate={onNavigate}
|
|
87
|
+
iconComponent={iconComponent}
|
|
88
|
+
home={home}
|
|
89
|
+
walkthrough={walkthrough}
|
|
90
|
+
settings={settings}
|
|
91
|
+
shortcuts={shortcuts}
|
|
92
|
+
themeToggle={themeToggle}
|
|
93
|
+
shortcutsKey={shortcutsKey}
|
|
94
|
+
masthead={masthead}
|
|
95
|
+
/* the tool sits on bare surface-primary, full-bleed; every other page keeps the Hub's wash */
|
|
96
|
+
shell={{ ...(tool && at(tool.path) ? { pageWash: null } : null), ...shell }}
|
|
97
|
+
>
|
|
98
|
+
{page}
|
|
99
|
+
</AppHub>
|
|
100
|
+
)
|
|
101
|
+
}
|
package/src/CatalogPage.jsx
CHANGED
|
@@ -42,7 +42,8 @@ import WalkthroughPanel from './WalkthroughPanel.jsx'
|
|
|
42
42
|
* list · `kol-tone-secondary` on the root · the All / Recent view strip; `toCard` returns
|
|
43
43
|
* the deck's fields and handlers — `title date bytes count cover href onNavigate onDownload
|
|
44
44
|
* onFavourite onDelete favourited` — and the page renders the slots. Explicit props win.
|
|
45
|
-
* @param {Object} header PageHeader props — `{ title, subtitle, size, voice, eyebrow }
|
|
45
|
+
* @param {Object} header PageHeader props — `{ title, subtitle, size, voice, eyebrow, masthead }`; inside a
|
|
46
|
+
* Shell the app's masthead wins, and a Catalog with NO Shell sets `masthead` here (2026-09-29)
|
|
46
47
|
* @param {Array} items the objects `ContentFilters` filters and searches
|
|
47
48
|
* @param {Function} toCard (item, { view, layout }) => card props (see above)
|
|
48
49
|
* @param {string} filtersTitle ContentFilters' title (e.g. "All Chromes")
|
package/src/HubHome.jsx
CHANGED
|
@@ -11,13 +11,13 @@ import CatalogPage from './CatalogPage.jsx'
|
|
|
11
11
|
* that button's Close label, a "Get started" last step — and the case of the view
|
|
12
12
|
* labels had already drifted. They are made here once.
|
|
13
13
|
*
|
|
14
|
-
* THE WALKTHROUGH IS OPT-IN (user, 2026-09-26, on media-
|
|
14
|
+
* THE WALKTHROUGH IS OPT-IN (user, 2026-09-26, on media-hub): it never opens by
|
|
15
15
|
* itself. The Walkthrough button at the end of the action row opens it; the X inside
|
|
16
16
|
* the card and the same button close it.
|
|
17
17
|
*
|
|
18
18
|
* @param {Object} app `{ name, subtitle }` — the masthead, when Home is the app's front door
|
|
19
19
|
* @param {string} title · subtitle Home's own masthead instead — for a Home that is not the front
|
|
20
|
-
* door (media-
|
|
20
|
+
* door (media-hub's Library at `/library`, 2026-09-26: it said "Media")
|
|
21
21
|
* @param {Array|Function} items the catalog's items, or `(view) => items` — RECENT
|
|
22
22
|
* and SAVED are different sets in every app
|
|
23
23
|
* @param {Array} views the view strip (default RECENT · SAVED); rename a set by
|
|
@@ -25,7 +25,7 @@ import CatalogPage from './CatalogPage.jsx'
|
|
|
25
25
|
* @param {Array} walkthrough steps (WalkthroughPanel's); a step whose `actions` is a
|
|
26
26
|
* FUNCTION gets `close` — the "Get started" step's buttons
|
|
27
27
|
* @param {ReactNode} actions the app's buttons; the Walkthrough button follows them
|
|
28
|
-
* THE LIST IS THE FILE ROW, one per line (user, 2026-09-26, on apps/
|
|
28
|
+
* THE LIST IS THE FILE ROW, one per line (user, 2026-09-26, on apps/hub: CatalogPage's
|
|
29
29
|
* 36px `catalog` row four across "both a bad hover state and using columns wrong"; brand's
|
|
30
30
|
* library row is the reference) — `rowVariant="file"` + `listLayout="stack"`: the ruled
|
|
31
31
|
* 48px thumb row, `date` · `size` · `actions` from `toCard`. Only the Hub's default —
|
package/src/NavRail.jsx
CHANGED
|
@@ -221,7 +221,9 @@ function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconCompone
|
|
|
221
221
|
iconSize={16}
|
|
222
222
|
iconComponent={iconComponent}
|
|
223
223
|
variant="nav"
|
|
224
|
-
|
|
224
|
+
/* md, the row's rung — at sm it stood 26 beside the 32 glyph button (render gate R1,
|
|
225
|
+
* one row one size, found on apps/editor's labs rail 2026-09-29) */
|
|
226
|
+
size="md"
|
|
225
227
|
className="shrink-0"
|
|
226
228
|
style={{ color: 'var(--kol-oq-96)' }}
|
|
227
229
|
aria-expanded={open}
|
package/src/PhoneNav.jsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { Button, MobileTabBar } from '@kolkrabbi/kol-component'
|
|
3
|
+
|
|
4
|
+
/* taxonomy-ok: molecule — MobileTabBar + a More sheet of Buttons */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PhoneNav — the rail, on a phone, as a bottom bar (AppShell `touch="bar"`, apps review
|
|
8
|
+
* 2026-09-29). The user: *"what about when there are like 10 pages? … this seems like something
|
|
9
|
+
* shell or hub should account for and ship, but not have per app fixes."*
|
|
10
|
+
*
|
|
11
|
+
* Up to FIVE destinations sit in the bar. More than five → the first FOUR, then **More**, a sheet
|
|
12
|
+
* holding the rest (Settings included). Ten pages = four + More. The bar is kol-component's
|
|
13
|
+
* `MobileTabBar` — the pill media already floated — not a second bar.
|
|
14
|
+
*
|
|
15
|
+
* Active = the rail's rule: `/` exact, anything else by prefix, the longest match wins. A
|
|
16
|
+
* destination inside More lights More.
|
|
17
|
+
*
|
|
18
|
+
* @param {Array} items `{ icon, path, label }` in rail order, bottom rows last
|
|
19
|
+
* @param {string} currentPath
|
|
20
|
+
* @param {Function} onNavigate `(path) => void`
|
|
21
|
+
*/
|
|
22
|
+
const MORE = '__more'
|
|
23
|
+
const MAX = 5
|
|
24
|
+
|
|
25
|
+
const activePath = (items, path) => items
|
|
26
|
+
.filter((i) => (i.path === '/' ? path === '/' : path === i.path || path.startsWith(`${i.path}/`)))
|
|
27
|
+
.sort((a, b) => b.path.length - a.path.length)[0]?.path
|
|
28
|
+
|
|
29
|
+
export default function PhoneNav({ items = [], currentPath = '/', onNavigate }) {
|
|
30
|
+
const [open, setOpen] = useState(false)
|
|
31
|
+
useEffect(() => { setOpen(false) }, [currentPath])
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!open) return undefined
|
|
34
|
+
const onKey = (e) => { if (e.key === 'Escape') setOpen(false) }
|
|
35
|
+
window.addEventListener('keydown', onKey)
|
|
36
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
37
|
+
}, [open])
|
|
38
|
+
|
|
39
|
+
const overflow = items.length > MAX
|
|
40
|
+
const inBar = overflow ? items.slice(0, MAX - 1) : items
|
|
41
|
+
const inMore = overflow ? items.slice(MAX - 1) : []
|
|
42
|
+
const active = activePath(items, currentPath)
|
|
43
|
+
const value = open || inMore.some((i) => i.path === active) ? MORE : active
|
|
44
|
+
|
|
45
|
+
const tabs = inBar.map((i) => ({ value: i.path, label: i.label, icon: i.icon }))
|
|
46
|
+
if (overflow) tabs.push({ value: MORE, label: 'More', icon: 'more' })
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
{open && (
|
|
51
|
+
/* a BUTTON scrim — iOS does not bubble a tap from a div (OverlayScrimTapDismiss, 2026-09-01) */
|
|
52
|
+
<button type="button" aria-label="Close more" className="kol-phone-nav-scrim kol-overlay-scrim" onClick={() => setOpen(false)} />
|
|
53
|
+
)}
|
|
54
|
+
{open && (
|
|
55
|
+
<div className="kol-phone-nav-more" role="menu" aria-label="More">
|
|
56
|
+
{inMore.map((i) => (
|
|
57
|
+
<Button
|
|
58
|
+
key={i.path}
|
|
59
|
+
variant="nav"
|
|
60
|
+
size="md"
|
|
61
|
+
iconLeft={i.icon}
|
|
62
|
+
aria-current={i.path === active ? 'page' : undefined}
|
|
63
|
+
className="w-full justify-start"
|
|
64
|
+
onClick={() => { setOpen(false); onNavigate?.(i.path) }}
|
|
65
|
+
>
|
|
66
|
+
{i.label}
|
|
67
|
+
</Button>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
<MobileTabBar
|
|
72
|
+
tabs={tabs}
|
|
73
|
+
value={value}
|
|
74
|
+
onChange={(v) => (v === MORE ? setOpen((o) => !o) : onNavigate?.(v))}
|
|
75
|
+
/>
|
|
76
|
+
</>
|
|
77
|
+
)
|
|
78
|
+
}
|
package/src/SettingsScaffold.jsx
CHANGED
package/src/WalkthroughPanel.jsx
CHANGED
|
@@ -10,7 +10,7 @@ import { Button } from '@kolkrabbi/kol-component'
|
|
|
10
10
|
* a step with `actions` renders that node centred instead of the text/image
|
|
11
11
|
* split (the "Get started" step).
|
|
12
12
|
*
|
|
13
|
-
* `onClose` draws an X INSIDE the card, top-right (user, 2026-09-26, on media-
|
|
13
|
+
* `onClose` draws an X INSIDE the card, top-right (user, 2026-09-26, on media-hub:
|
|
14
14
|
* the panel had no close of its own, so every app put one outside the card or on a
|
|
15
15
|
* page button). Unset = no X, exactly as before.
|
|
16
16
|
*/
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export { default as AppShell } from './AppShell.jsx'
|
|
|
14
14
|
export { NavHiddenContext, useNavHidden } from './navHidden.js'
|
|
15
15
|
export { SettingsToggleContext, useSettingsToggle } from './settingsToggle.js'
|
|
16
16
|
export { default as NavRail } from './NavRail.jsx'
|
|
17
|
+
export { default as PhoneNav } from './PhoneNav.jsx'
|
|
17
18
|
export { default as PageShell, PageBleed } from './PageShell.jsx'
|
|
18
19
|
/* PageHeader MOVED to @kolkrabbi/kol-component 2026-09-03 (page-header-one-masthead).
|
|
19
20
|
* Not re-exported: every kol-shell consumer already has kol-component as a peer,
|
|
@@ -36,3 +37,5 @@ export { default as TouchDeviceOverlay, useTouchPrimary } from './TouchDeviceOve
|
|
|
36
37
|
export { default as AppHub } from './AppHub.jsx'
|
|
37
38
|
export { default as HubHome } from './HubHome.jsx'
|
|
38
39
|
export { default as HubSettings } from './HubSettings.jsx'
|
|
40
|
+
// the Studio — the Hub + Home · Library · Create · Use · pages · Settings, the workstation (apps review §6c, 2026-09-29)
|
|
41
|
+
export { default as AppStudio } from './AppStudio.jsx'
|