@kolkrabbi/kol-shell 0.63.0 → 0.65.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 +5 -5
- package/src/AppHub.jsx +14 -3
- package/src/AppStudio.jsx +12 -5
- package/src/HubDev.jsx +46 -0
- package/src/NavRail.jsx +21 -1
- package/src/index.js +4 -1
- package/src/railSlot.jsx +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The application shell",
|
|
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.265.0",
|
|
16
16
|
"@kolkrabbi/kol-icons": ">=0.16.0",
|
|
17
17
|
"@kolkrabbi/kol-theme": ">=0.165.0",
|
|
18
18
|
"gsap": "^3.13.0",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@kolkrabbi/kol-component": "^0.
|
|
24
|
-
"@kolkrabbi/kol-icons": "^0.
|
|
23
|
+
"@kolkrabbi/kol-component": "^0.265.0",
|
|
24
|
+
"@kolkrabbi/kol-icons": "^0.39.0",
|
|
25
25
|
"@kolkrabbi/kol-framework": "^0.49.0",
|
|
26
|
-
"@kolkrabbi/kol-theme": "^0.
|
|
26
|
+
"@kolkrabbi/kol-theme": "^0.174.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src",
|
package/src/AppHub.jsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
|
|
|
2
2
|
import AppShell from './AppShell.jsx'
|
|
3
3
|
import HubHome from './HubHome.jsx'
|
|
4
4
|
import HubSettings from './HubSettings.jsx'
|
|
5
|
+
import HubDev from './HubDev.jsx'
|
|
5
6
|
import { ShortcutsOverlay } from '@kolkrabbi/kol-component'
|
|
6
7
|
|
|
7
8
|
/* taxonomy-ok: organism — nests AppShell / HubHome / HubSettings / ShortcutsOverlay (relative) */
|
|
@@ -37,6 +38,9 @@ import { ShortcutsOverlay } from '@kolkrabbi/kol-component'
|
|
|
37
38
|
* OPT-IN like AppShell's `bottomItems` (apps review 2026-09-29: *"not sure it makes sense
|
|
38
39
|
* to neccessarily have settings manditory part of the rail"*) — no `settings`, no rail
|
|
39
40
|
* row, no route, no `,` key. It was pinned whether the app had settings or not.
|
|
41
|
+
* @param {Object} dev HubDev's props (`tabs`, `title`, `subtitle`) + `path` (default `/dev`). OPT-IN, and only on
|
|
42
|
+
* a dev server (`import.meta.env.DEV`; `always: true` overrides) — a `code` row above
|
|
43
|
+
* Settings (CompareGridDevTab, kol-monitor 2026-10-17)
|
|
40
44
|
* @param {Array} shortcuts `[{ section, items: [{ id, label, combo }] }]` — the sheet AND Settings
|
|
41
45
|
* @param {ReactNode} themeToggle Settings' masthead toggle (a node — kol-framework is not a peer)
|
|
42
46
|
* @param {string} shortcutsKey default `s`; `null` turns the sheet key off (a tool that owns S)
|
|
@@ -55,6 +59,7 @@ export default function AppHub({
|
|
|
55
59
|
home,
|
|
56
60
|
walkthrough,
|
|
57
61
|
settings,
|
|
62
|
+
dev,
|
|
58
63
|
shortcuts = [],
|
|
59
64
|
themeToggle,
|
|
60
65
|
settingsPath = '/settings',
|
|
@@ -79,12 +84,18 @@ export default function AppHub({
|
|
|
79
84
|
return () => window.removeEventListener('keydown', onKey)
|
|
80
85
|
}, [shortcutsKey])
|
|
81
86
|
|
|
87
|
+
/* the dev page is a dev server's only — a production build never routes it or draws its row */
|
|
88
|
+
const devOn = !!dev && (dev.always || !!import.meta.env?.DEV)
|
|
89
|
+
const devPath = dev?.path ?? '/dev'
|
|
90
|
+
const { path: _dp, always: _da, ...devProps } = dev ?? {}
|
|
82
91
|
const page = home && currentPath === homePath
|
|
83
92
|
? <HubHome app={app} iconComponent={iconComponent} walkthrough={walkthrough} {...home} />
|
|
84
93
|
: settings && currentPath === settingsPath
|
|
85
94
|
? <HubSettings app={app} shortcuts={shortcuts} themeToggle={themeToggle} {...settings} />
|
|
86
|
-
:
|
|
87
|
-
|
|
95
|
+
: devOn && (currentPath === devPath || currentPath?.startsWith(`${devPath}/`))
|
|
96
|
+
? <HubDev storageKey={`kol-dev-tab:${app.name ?? 'app'}`} {...devProps} />
|
|
97
|
+
: children
|
|
98
|
+
const bottom = [...(devOn ? [{ icon: 'code', path: devPath, label: 'Dev' }] : []), ...(settings ? SETTINGS_ROW(settingsPath) : [])]
|
|
88
99
|
/* the phone bar lists Home as a tab — the rail draws it as the mark, the bar has no mark */
|
|
89
100
|
const barItems = [...(home ? [{ icon: 'home-01', path: homePath, label: 'Home' }] : []), ...items, ...bottom]
|
|
90
101
|
|
|
@@ -111,7 +122,7 @@ export default function AppHub({
|
|
|
111
122
|
{...shell}
|
|
112
123
|
>
|
|
113
124
|
{page}
|
|
114
|
-
{sheet && <ShortcutsOverlay shortcuts={shortcuts} onClose={() => setSheet(false)} />}
|
|
125
|
+
{sheet && <ShortcutsOverlay shortcuts={shortcuts} currentPath={currentPath} onClose={() => setSheet(false)} />}
|
|
115
126
|
</AppShell>
|
|
116
127
|
)
|
|
117
128
|
}
|
package/src/AppStudio.jsx
CHANGED
|
@@ -28,11 +28,16 @@ import PageShell from './PageShell.jsx'
|
|
|
28
28
|
*
|
|
29
29
|
* @param {Object} app AppHub's `{ name, subtitle, logomark, about, links }`
|
|
30
30
|
* @param {Object} home HubHome's props — the landing Catalog (required: a Studio opens on Home)
|
|
31
|
-
* @param {Object} [library] CatalogPage's props
|
|
32
|
-
*
|
|
31
|
+
* @param {Object} [library] CatalogPage's props — or `children` (a router, for pages under the
|
|
32
|
+
* slot: `/library/:id`), which renders in place of the CatalogPage —
|
|
33
|
+
* plus `{ path, label, icon }`
|
|
34
|
+
* @param {Object} [create] `{ header (PageHeader props), children, path, label, icon }`;
|
|
35
|
+
* `shell: false` renders `children` bare — for a Create that owns its
|
|
36
|
+
* PageShell (a fixed canvas beside a scrolling list)
|
|
33
37
|
* @param {Object} [use] `{ children, path, label, icon }` — the tool
|
|
34
38
|
* @param {Array} [pages] `[{ path, icon, label, children }]` — opt-in pages, after Use
|
|
35
39
|
* @param {Object} [settings] HubSettings' props
|
|
40
|
+
* @param {Object} [dev] HubDev's props — AppHub's `dev`: a dev server's `code` page at `/dev`
|
|
36
41
|
* @param {'mono'|'display'} [masthead='mono']
|
|
37
42
|
* @param {Object} [shell] any AppShell prop, spread last
|
|
38
43
|
*/
|
|
@@ -47,6 +52,7 @@ export default function AppStudio({
|
|
|
47
52
|
use,
|
|
48
53
|
pages = [],
|
|
49
54
|
settings,
|
|
55
|
+
dev,
|
|
50
56
|
walkthrough,
|
|
51
57
|
shortcuts,
|
|
52
58
|
themeToggle,
|
|
@@ -63,10 +69,10 @@ export default function AppStudio({
|
|
|
63
69
|
|
|
64
70
|
let page = null
|
|
65
71
|
if (lib && at(lib.path)) {
|
|
66
|
-
const { path: _p, label: _l, icon: _i, ...props } = lib
|
|
67
|
-
page = <CatalogPage {...props} />
|
|
72
|
+
const { path: _p, label: _l, icon: _i, children, ...props } = lib
|
|
73
|
+
page = children ?? <CatalogPage {...props} />
|
|
68
74
|
} else if (make && at(make.path)) {
|
|
69
|
-
page = (
|
|
75
|
+
page = make.shell === false ? make.children : (
|
|
70
76
|
<PageShell>
|
|
71
77
|
{make.header && <PageHeader size="sm" {...make.header} />}
|
|
72
78
|
{make.children}
|
|
@@ -88,6 +94,7 @@ export default function AppStudio({
|
|
|
88
94
|
home={home}
|
|
89
95
|
walkthrough={walkthrough}
|
|
90
96
|
settings={settings}
|
|
97
|
+
dev={dev}
|
|
91
98
|
shortcuts={shortcuts}
|
|
92
99
|
themeToggle={themeToggle}
|
|
93
100
|
shortcutsKey={shortcutsKey}
|
package/src/HubDev.jsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { PageHeader } from '@kolkrabbi/kol-component'
|
|
3
|
+
import PageShell from './PageShell.jsx'
|
|
4
|
+
import TabStrip from './TabStrip.jsx'
|
|
5
|
+
|
|
6
|
+
/* taxonomy-ok: organism — nests PageShell / TabStrip (relative) + kol-component's PageHeader */
|
|
7
|
+
|
|
8
|
+
const read = (key, fallback) => { try { return localStorage.getItem(key) ?? fallback } catch { return fallback } }
|
|
9
|
+
const write = (key, value) => { try { localStorage.setItem(key, value) } catch { /* private mode: the tab just resets */ } }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* HubDev — A hub's development page. The one rail row for everything that is not the app yet: experiments,
|
|
13
|
+
* references, comparisons (CompareGridDevTab, kol-monitor 2026-10-17 — user: *"I would also maybe add
|
|
14
|
+
* instructions for making the dev tab to the hub … localhost only using the </> icon"*). Each app had built
|
|
15
|
+
* this page by hand and each read differently; this is the page made once, for reading.
|
|
16
|
+
*
|
|
17
|
+
* The shape: a small mono masthead with one line saying what the page is, the tabs on their own row under
|
|
18
|
+
* it, the active tab's one-line description, then the tab's content at full width. Detail belongs in the
|
|
19
|
+
* content's own toggles, not in more text here. The open tab is remembered per app.
|
|
20
|
+
*
|
|
21
|
+
* Rendered by `AppHub` / `AppStudio` from their `dev` prop, only on a dev server (`import.meta.env.DEV`),
|
|
22
|
+
* at `/dev`, behind the `code` rail row.
|
|
23
|
+
*
|
|
24
|
+
* @param {Array} tabs `[{ value, label, description?, content }]`
|
|
25
|
+
* @param {string} [title='Dev']
|
|
26
|
+
* @param {string} [subtitle] one line: what the page is
|
|
27
|
+
* @param {string} [storageKey] where the open tab is remembered
|
|
28
|
+
*/
|
|
29
|
+
export default function HubDev({ tabs = [], title = 'Dev', subtitle = 'Experiments and reference, not the app', storageKey = 'kol-dev-tab' }) {
|
|
30
|
+
const [stored, setStored] = useState(() => read(storageKey, tabs[0]?.value))
|
|
31
|
+
const active = tabs.find((t) => t.value === stored) ?? tabs[0]
|
|
32
|
+
const pick = (v) => { setStored(v); write(storageKey, v) }
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<PageShell className="gap-6">
|
|
36
|
+
<PageHeader size="sm" voice="mono" title={title} subtitle={subtitle} />
|
|
37
|
+
{tabs.length > 1 && (
|
|
38
|
+
<div className="flex flex-col gap-2 border-b border-fg-08 pb-4">
|
|
39
|
+
<TabStrip options={tabs.map(({ value, label }) => ({ value, label }))} value={active?.value} onChange={pick} tracked />
|
|
40
|
+
{active?.description && <p className="kol-helper-12 text-meta">{active.description}</p>}
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
{active && <div className="min-w-0">{active.content}</div>}
|
|
44
|
+
</PageShell>
|
|
45
|
+
)
|
|
46
|
+
}
|
package/src/NavRail.jsx
CHANGED
|
@@ -9,6 +9,7 @@ import { GRAB } from '@kolkrabbi/kol-component/utilities/motion'
|
|
|
9
9
|
import { useGrabEdge } from '@kolkrabbi/kol-component'
|
|
10
10
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
11
11
|
import Logomark from './Logomark.jsx'
|
|
12
|
+
import { useRailSlot } from './railSlot.jsx'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* NavRail — the flat rail. ONE fixed column, every child a direct child of it
|
|
@@ -70,6 +71,11 @@ import Logomark from './Logomark.jsx'
|
|
|
70
71
|
* @param {boolean} drawer off-canvas mode: no grab, no drag, no token writes, rows always
|
|
71
72
|
* labelled, and the rail sizes itself from `--kol-shell-drawer-width` (default 240px).
|
|
72
73
|
* AppShell sets this from `touch="drawer"` — see ShellRailNoDrawerOnMobile, 2026-08-31.
|
|
74
|
+
* Under a `RailSlotProvider` the opened width also carries a CONSUMER SLOT below the rows
|
|
75
|
+
* (NavRailConsumerSlot, kol-monitor 2026-10-15) — content that is not a row list (monitor's module
|
|
76
|
+
* catalog: rows that insert, not paths). Always mounted so portalled content keeps its state across
|
|
77
|
+
* open/close; zero tall and clipped while closed. No provider → no slot, the render unchanged.
|
|
78
|
+
*
|
|
73
79
|
* @param {boolean} [fade=true] the list fades out at its foot — the workshop rail's fade
|
|
74
80
|
* (2026-10-01), here too on the user's word (2026-10-02). The rows sit in their own scroll
|
|
75
81
|
* region between the mark and the pinned bottom rows, so a list longer than the window scrolls
|
|
@@ -299,6 +305,13 @@ export default function NavRail({
|
|
|
299
305
|
* ten rows each, which is where an accordion stops reading. */
|
|
300
306
|
const [entered, setEntered] = useState(null)
|
|
301
307
|
const snapOpenRef = useRef(null)
|
|
308
|
+
const slotRef = useRef(null)
|
|
309
|
+
const ctx = useRailSlot()
|
|
310
|
+
const setSlot = ctx?.setSlot
|
|
311
|
+
const setCtxOpen = ctx?.setRailOpen
|
|
312
|
+
const slotOpen = drawer || railOpen
|
|
313
|
+
useEffect(() => { setSlot?.(slotRef.current); return () => setSlot?.(null) }, [setSlot, hidden])
|
|
314
|
+
useEffect(() => { setCtxOpen?.(slotOpen) }, [setCtxOpen, slotOpen])
|
|
302
315
|
/* the grab strip is not rendered in drawer mode, so the hook's ref stays null
|
|
303
316
|
* and it no-ops on its own — no second argument needed */
|
|
304
317
|
useGrabEdge(grabRef)
|
|
@@ -363,7 +376,7 @@ export default function NavRail({
|
|
|
363
376
|
{/* THE LIST IS ITS OWN REGION (2026-10-02): it takes the room between the mark and the pinned
|
|
364
377
|
* bottom rows — what the bare spacer used to hold — scrolls when the rows outrun the window,
|
|
365
378
|
* and fades at its foot unless `fade={false}`. Same column, same gap, as the rail's own. */}
|
|
366
|
-
<div className={`kol-shell-rail-list flex min-h-0 w-full flex-1 flex-col items-start gap-2 overflow-y-auto overflow-x-hidden ${fade ? 'kol-shell-rail-list--fade' : ''}`.trim()}>
|
|
379
|
+
<div style={ctx && slotOpen ? { flex: '0 1 auto' } : undefined} className={`kol-shell-rail-list flex min-h-0 w-full flex-1 flex-col items-start gap-2 overflow-y-auto overflow-x-hidden ${fade ? 'kol-shell-rail-list--fade' : ''}`.trim()}>
|
|
367
380
|
{section ? (
|
|
368
381
|
<>
|
|
369
382
|
<RailItem icon="arrow-left" path={section.path} label={section.label} currentPath="" onNavigate={() => setEntered(null)} iconComponent={iconComponent} railOpen={drawer || railOpen} />
|
|
@@ -371,6 +384,13 @@ export default function NavRail({
|
|
|
371
384
|
</>
|
|
372
385
|
) : items.map(row)}
|
|
373
386
|
</div>
|
|
387
|
+
{ctx && (
|
|
388
|
+
<div
|
|
389
|
+
ref={slotRef}
|
|
390
|
+
className="kol-shell-rail-slot w-full min-h-0 overflow-x-hidden"
|
|
391
|
+
style={{ flex: slotOpen ? '1 1 auto' : '0 0 0px', overflowY: slotOpen ? 'auto' : 'hidden' }}
|
|
392
|
+
/>
|
|
393
|
+
)}
|
|
374
394
|
{/* the rule runs the full rail width, out past the px-2 */}
|
|
375
395
|
{bottomItems.length > 0 && <div className="self-stretch -mx-2 border-t border-fg-08" />}
|
|
376
396
|
{bottomItems.map(row)}
|
package/src/index.js
CHANGED
|
@@ -26,7 +26,7 @@ export { default as TabStrip } from './TabStrip.jsx'
|
|
|
26
26
|
export { default as SettingsScaffold } from './SettingsScaffold.jsx'
|
|
27
27
|
export { default as WalkthroughPanel } from './WalkthroughPanel.jsx'
|
|
28
28
|
/* lives in kol-component since 0.228.0 — re-exported so every kol-shell import keeps resolving */
|
|
29
|
-
export { ShortcutsOverlay } from '@kolkrabbi/kol-component'
|
|
29
|
+
export { ShortcutsOverlay, shortcutInScope } from '@kolkrabbi/kol-component'
|
|
30
30
|
export { default as Logomark } from './Logomark.jsx'
|
|
31
31
|
// the app tier, shipped once (ShellHomeSystem, kol-fxr 2026-08-27)
|
|
32
32
|
export { default as CatalogPage } from './CatalogPage.jsx'
|
|
@@ -37,5 +37,8 @@ export { default as TouchDeviceOverlay, useTouchPrimary } from './TouchDeviceOve
|
|
|
37
37
|
export { default as AppHub } from './AppHub.jsx'
|
|
38
38
|
export { default as HubHome } from './HubHome.jsx'
|
|
39
39
|
export { default as HubSettings } from './HubSettings.jsx'
|
|
40
|
+
export { default as HubDev } from './HubDev.jsx'
|
|
40
41
|
// the Studio — the Hub + Home · Library · Create · Use · pages · Settings, the workstation (apps review §6c, 2026-09-29)
|
|
41
42
|
export { default as AppStudio } from './AppStudio.jsx'
|
|
43
|
+
// a consumer slot in the opened rail — the page portals into it (NavRailConsumerSlot, kol-monitor 2026-10-15)
|
|
44
|
+
export { RailSlotProvider, useRailSlot } from './railSlot.jsx'
|
package/src/railSlot.jsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RailSlotProvider / useRailSlot — a consumer slot in NavRail's opened width (NavRailConsumerSlot,
|
|
5
|
+
* kol-monitor 2026-10-15). The rail renders in AppShell's own tree, outside the page's providers,
|
|
6
|
+
* so it cannot read page state and the page cannot render into it. The rail publishes its slot's
|
|
7
|
+
* DOM node here instead, and the page portals into it with its own state:
|
|
8
|
+
*
|
|
9
|
+
* <RailSlotProvider><AppStudio … /></RailSlotProvider>
|
|
10
|
+
* const rail = useRailSlot(); rail?.slot && createPortal(<Panel />, rail.slot)
|
|
11
|
+
*
|
|
12
|
+
* The provider goes ABOVE the shell so the rail and the page both see it. No provider → no slot,
|
|
13
|
+
* and the rail renders as it did. `railOpen` mirrors the rail's open state for the page.
|
|
14
|
+
*
|
|
15
|
+
* Monitor's shape (`src/hooks/useRailSlot.jsx`), shipped so its NavRail fork can retire.
|
|
16
|
+
*/
|
|
17
|
+
const RailSlotContext = createContext(null)
|
|
18
|
+
|
|
19
|
+
export function useRailSlot() {
|
|
20
|
+
return useContext(RailSlotContext)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function RailSlotProvider({ children }) {
|
|
24
|
+
const [slot, setSlot] = useState(null)
|
|
25
|
+
const [railOpen, setRailOpen] = useState(false)
|
|
26
|
+
const value = useMemo(() => ({ slot, setSlot, railOpen, setRailOpen }), [slot, railOpen])
|
|
27
|
+
return <RailSlotContext.Provider value={value}>{children}</RailSlotContext.Provider>
|
|
28
|
+
}
|