@kolkrabbi/kol-shell 0.62.0 → 0.64.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-shell",
3
- "version": "0.62.0",
3
+ "version": "0.64.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.229.0",
15
+ "@kolkrabbi/kol-component": ">=0.246.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.240.0",
24
- "@kolkrabbi/kol-icons": "^0.33.1",
25
- "@kolkrabbi/kol-theme": "^0.166.0",
26
- "@kolkrabbi/kol-framework": "^0.49.0"
23
+ "@kolkrabbi/kol-framework": "^0.49.0",
24
+ "@kolkrabbi/kol-theme": "^0.174.0",
25
+ "@kolkrabbi/kol-icons": "^0.38.0",
26
+ "@kolkrabbi/kol-component": "^0.261.0"
27
27
  },
28
28
  "files": [
29
29
  "src",
package/src/AppStudio.jsx CHANGED
@@ -28,8 +28,12 @@ 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, plus `{ path, label, icon }`
32
- * @param {Object} [create] `{ header (PageHeader props), children, path, label, icon }`
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
@@ -63,10 +67,10 @@ export default function AppStudio({
63
67
 
64
68
  let page = null
65
69
  if (lib && at(lib.path)) {
66
- const { path: _p, label: _l, icon: _i, ...props } = lib
67
- page = <CatalogPage {...props} />
70
+ const { path: _p, label: _l, icon: _i, children, ...props } = lib
71
+ page = children ?? <CatalogPage {...props} />
68
72
  } else if (make && at(make.path)) {
69
- page = (
73
+ page = make.shell === false ? make.children : (
70
74
  <PageShell>
71
75
  {make.header && <PageHeader size="sm" {...make.header} />}
72
76
  {make.children}
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)}
@@ -1,5 +1,5 @@
1
- import { useState } from 'react'
2
- import { Button } from '@kolkrabbi/kol-component'
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { Button, pushLayer, popLayer, isTopLayer } from '@kolkrabbi/kol-component'
3
3
 
4
4
  /**
5
5
  * WalkthroughPanel — A stepped intro card. the absolutely-centred stepped intro card (both repos'
@@ -13,10 +13,29 @@ import { Button } from '@kolkrabbi/kol-component'
13
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
+ *
17
+ * KEYS (kol-fxr 2026-10-09, HubWalkthroughEscape): Escape calls `onClose`, ← → page. The panel
18
+ * joins the DS overlay layer stack, so a sheet opened above it takes the Escape first, and keys
19
+ * typed into a field are left alone.
16
20
  */
17
21
  export default function WalkthroughPanel({ steps = [], iconComponent, onClose }) {
18
22
  const [step, setStep] = useState(0)
19
23
  const current = steps[step]
24
+ const live = useRef()
25
+ live.current = { onClose, last: steps.length - 1 }
26
+ useEffect(() => {
27
+ const layer = pushLayer()
28
+ const onKey = (e) => {
29
+ if (!isTopLayer(layer) || e.metaKey || e.ctrlKey || e.altKey) return
30
+ const el = e.target
31
+ if (el?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el?.tagName ?? '')) return
32
+ if (e.key === 'Escape' && live.current.onClose) { e.preventDefault(); live.current.onClose() }
33
+ else if (e.key === 'ArrowLeft') { e.preventDefault(); setStep((s) => Math.max(0, s - 1)) }
34
+ else if (e.key === 'ArrowRight') { e.preventDefault(); setStep((s) => Math.min(live.current.last, s + 1)) }
35
+ }
36
+ document.addEventListener('keydown', onKey)
37
+ return () => { document.removeEventListener('keydown', onKey); popLayer(layer) }
38
+ }, [])
20
39
  if (!current) return null
21
40
 
22
41
  return (
package/src/index.js CHANGED
@@ -39,3 +39,5 @@ export { default as HubHome } from './HubHome.jsx'
39
39
  export { default as HubSettings } from './HubSettings.jsx'
40
40
  // the Studio — the Hub + Home · Library · Create · Use · pages · Settings, the workstation (apps review §6c, 2026-09-29)
41
41
  export { default as AppStudio } from './AppStudio.jsx'
42
+ // a consumer slot in the opened rail — the page portals into it (NavRailConsumerSlot, kol-monitor 2026-10-15)
43
+ export { RailSlotProvider, useRailSlot } from './railSlot.jsx'
@@ -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
+ }