@kolkrabbi/kol-shell 0.60.0 → 0.62.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 +6 -6
- package/src/AppShell.jsx +8 -0
- package/src/NavRail.jsx +38 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The application shell",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"peerDependencies": {
|
|
15
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.165.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-component": "^0.
|
|
24
|
-
"@kolkrabbi/kol-icons": "^0.33.
|
|
25
|
-
"@kolkrabbi/kol-
|
|
26
|
-
"@kolkrabbi/kol-
|
|
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"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src",
|
package/src/AppShell.jsx
CHANGED
|
@@ -42,6 +42,10 @@ import { TABBAR_H, MastheadContext } from '@kolkrabbi/kol-component'
|
|
|
42
42
|
* open/close is the consumer's `onNavigate` (kol-mirror's model: navigating to
|
|
43
43
|
* `/settings` while already there returns to the last page), and the theme
|
|
44
44
|
* toggle lives on the settings page, not in the rail (user, 2026-08-28).
|
|
45
|
+
* @param {boolean} [props.railFade=true] the rail's list fades out at its foot (`NavRail fade`); `false` opts out
|
|
46
|
+
* @param {'fold'|'enter'} [props.railSections='fold'] how a section row opens: `fold` unfolds it in place (the
|
|
47
|
+
* accordion), `enter` replaces the list with its rows under a Back row
|
|
48
|
+
* (`NavRail sections`, 2026-10-06 — labs' catalog)
|
|
45
49
|
* @param {string} props.railToggleKey a key that toggles the rail (e.g. '\\') — ignored while typing in a field;
|
|
46
50
|
* the rail comes back on every `currentPath` change (ShellHomeSystem, 2026-08-27)
|
|
47
51
|
* @param {'shell'|'bare'|'overlay'|'drawer'|'bar'} props.touch the touch-primary policy (default 'shell' = the rail regardless):
|
|
@@ -130,6 +134,8 @@ export default function AppShell({
|
|
|
130
134
|
onNavigate,
|
|
131
135
|
iconComponent,
|
|
132
136
|
railComponent: Rail = NavRail,
|
|
137
|
+
railFade = true,
|
|
138
|
+
railSections = 'fold',
|
|
133
139
|
railToggleKey,
|
|
134
140
|
touch = 'shell',
|
|
135
141
|
appName,
|
|
@@ -370,6 +376,8 @@ export default function AppShell({
|
|
|
370
376
|
onNavigate={navigate}
|
|
371
377
|
iconComponent={iconComponent}
|
|
372
378
|
drawer={drawer}
|
|
379
|
+
fade={railFade}
|
|
380
|
+
sections={railSections}
|
|
373
381
|
/>
|
|
374
382
|
)}
|
|
375
383
|
{/* THE BACK OF THE BACK — surface-primary, always, in every app; the
|
package/src/NavRail.jsx
CHANGED
|
@@ -70,6 +70,10 @@ import Logomark from './Logomark.jsx'
|
|
|
70
70
|
* @param {boolean} drawer off-canvas mode: no grab, no drag, no token writes, rows always
|
|
71
71
|
* labelled, and the rail sizes itself from `--kol-shell-drawer-width` (default 240px).
|
|
72
72
|
* AppShell sets this from `touch="drawer"` — see ShellRailNoDrawerOnMobile, 2026-08-31.
|
|
73
|
+
* @param {boolean} [fade=true] the list fades out at its foot — the workshop rail's fade
|
|
74
|
+
* (2026-10-01), here too on the user's word (2026-10-02). The rows sit in their own scroll
|
|
75
|
+
* region between the mark and the pinned bottom rows, so a list longer than the window scrolls
|
|
76
|
+
* and runs out under the fade instead of being cut by the edge. `false` draws the list plain.
|
|
73
77
|
*/
|
|
74
78
|
const RAIL_W = '--kol-shell-rail-width'
|
|
75
79
|
const CLOSED = 48
|
|
@@ -163,7 +167,7 @@ function IconAt({ name, size, component }) {
|
|
|
163
167
|
return <Cmp name={name} size={size} />
|
|
164
168
|
}
|
|
165
169
|
|
|
166
|
-
function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconComponent, railOpen, onOpenRail }) {
|
|
170
|
+
function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconComponent, railOpen, onOpenRail, onEnter }) {
|
|
167
171
|
const [open, setOpen] = useState(false)
|
|
168
172
|
/* A SECTION ROW IS NOT A DESTINATION (RailSectionPressOpensRail, kol-fxr
|
|
169
173
|
* 2026-08-30 — user: *"if I press effects from collapsed nav it should maybe
|
|
@@ -179,8 +183,13 @@ function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconCompone
|
|
|
179
183
|
* "nothing auto-expands" — that rule is about ARRIVING on a route; this is an
|
|
180
184
|
* explicit press. */
|
|
181
185
|
const isSection = sub?.length > 0
|
|
186
|
+
/* …AND IN AN OPEN RAIL THE SAME PRESS FOLDS AND UNFOLDS IT (2026-10-05, labs on a phone). Open,
|
|
187
|
+
* it still fell through to `onNavigate` with that path no consumer dispatches: dead at a desk,
|
|
188
|
+
* and worse on a touch drawer, where AppShell closes the drawer on every navigate — pressing a
|
|
189
|
+
* section's name shut the nav and loaded nothing. */
|
|
182
190
|
const press = () => {
|
|
183
|
-
if (isSection && !railOpen)
|
|
191
|
+
if (isSection && onEnter) { if (!railOpen) onOpenRail?.(); onEnter(); return }
|
|
192
|
+
if (isSection) { if (!railOpen) onOpenRail?.(); setOpen((v) => !railOpen || !v); return }
|
|
184
193
|
onNavigate?.(path)
|
|
185
194
|
}
|
|
186
195
|
const active = path === '/' ? currentPath === '/' : currentPath.startsWith(path)
|
|
@@ -217,7 +226,7 @@ function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconCompone
|
|
|
217
226
|
{sub?.length > 0 && (
|
|
218
227
|
<Tooltip label={label} placement="right" triggerClassName="inline-flex shrink-0">
|
|
219
228
|
<Button
|
|
220
|
-
iconOnly={open ? 'chevron-down' : 'chevron-right'}
|
|
229
|
+
iconOnly={onEnter ? 'chevron-right' : open ? 'chevron-down' : 'chevron-right'}
|
|
221
230
|
iconSize={16}
|
|
222
231
|
iconComponent={iconComponent}
|
|
223
232
|
variant="nav"
|
|
@@ -226,14 +235,14 @@ function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconCompone
|
|
|
226
235
|
size="md"
|
|
227
236
|
className="shrink-0"
|
|
228
237
|
style={{ color: 'var(--kol-oq-96)' }}
|
|
229
|
-
aria-expanded={open}
|
|
230
|
-
onClick={() => setOpen((v) => !v)}
|
|
238
|
+
aria-expanded={onEnter ? undefined : open}
|
|
239
|
+
onClick={onEnter ? press : () => setOpen((v) => !v)}
|
|
231
240
|
aria-label={`${label} sub categories`}
|
|
232
241
|
/>
|
|
233
242
|
</Tooltip>
|
|
234
243
|
)}
|
|
235
244
|
</div>
|
|
236
|
-
{railOpen && open && sub.map((s) => {
|
|
245
|
+
{railOpen && open && !onEnter && sub.map((s) => {
|
|
237
246
|
const on = s.path === '/' ? currentPath === '/' : currentPath.startsWith(s.path)
|
|
238
247
|
return (
|
|
239
248
|
<Tooltip key={s.path} label={s.label} placement="right" asChild>
|
|
@@ -277,18 +286,29 @@ export default function NavRail({
|
|
|
277
286
|
iconComponent,
|
|
278
287
|
hidden = false,
|
|
279
288
|
drawer = false,
|
|
289
|
+
fade = true,
|
|
290
|
+
sections = 'fold',
|
|
280
291
|
}) {
|
|
281
292
|
const railRef = useRef(null)
|
|
282
293
|
const grabRef = useRef(null)
|
|
283
294
|
const [railOpen, setRailOpen] = useState(false)
|
|
295
|
+
/* ENTER MODE (`sections="enter"`, 2026-10-06 — the user, on labs: "having a 'enter' mode in the
|
|
296
|
+
* left sidebar instead of collapse folder?"): pressing a section replaces the list with that
|
|
297
|
+
* section's rows under a Back row, instead of unfolding it in place. `fold` is the accordion
|
|
298
|
+
* every rail had and keeps. Per consumer, not a law: labs' catalog is five sections of up to
|
|
299
|
+
* ten rows each, which is where an accordion stops reading. */
|
|
300
|
+
const [entered, setEntered] = useState(null)
|
|
284
301
|
const snapOpenRef = useRef(null)
|
|
285
302
|
/* the grab strip is not rendered in drawer mode, so the hook's ref stays null
|
|
286
303
|
* and it no-ops on its own — no second argument needed */
|
|
287
304
|
useGrabEdge(grabRef)
|
|
288
305
|
useRailDrag(railRef, grabRef, setRailOpen, snapOpenRef, !drawer)
|
|
289
306
|
if (hidden) return null
|
|
307
|
+
const enterMode = sections === 'enter'
|
|
308
|
+
const section = enterMode && entered ? items.find((i) => i.path === entered) : null
|
|
290
309
|
const row = (item) => (
|
|
291
|
-
<RailItem key={item.path} {...item} currentPath={currentPath} onNavigate={onNavigate} iconComponent={iconComponent} railOpen={drawer || railOpen} onOpenRail={() => snapOpenRef.current?.()}
|
|
310
|
+
<RailItem key={item.path} {...item} currentPath={currentPath} onNavigate={onNavigate} iconComponent={iconComponent} railOpen={drawer || railOpen} onOpenRail={() => snapOpenRef.current?.()}
|
|
311
|
+
onEnter={enterMode ? () => setEntered(item.path) : undefined} />
|
|
292
312
|
)
|
|
293
313
|
return (
|
|
294
314
|
<div
|
|
@@ -340,9 +360,17 @@ export default function NavRail({
|
|
|
340
360
|
</div>
|
|
341
361
|
</Tooltip>
|
|
342
362
|
)}
|
|
343
|
-
{
|
|
344
|
-
|
|
345
|
-
|
|
363
|
+
{/* THE LIST IS ITS OWN REGION (2026-10-02): it takes the room between the mark and the pinned
|
|
364
|
+
* bottom rows — what the bare spacer used to hold — scrolls when the rows outrun the window,
|
|
365
|
+
* 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()}>
|
|
367
|
+
{section ? (
|
|
368
|
+
<>
|
|
369
|
+
<RailItem icon="arrow-left" path={section.path} label={section.label} currentPath="" onNavigate={() => setEntered(null)} iconComponent={iconComponent} railOpen={drawer || railOpen} />
|
|
370
|
+
{section.sub.map((s) => <RailItem key={s.path} {...s} currentPath={currentPath} onNavigate={onNavigate} iconComponent={iconComponent} railOpen={drawer || railOpen} />)}
|
|
371
|
+
</>
|
|
372
|
+
) : items.map(row)}
|
|
373
|
+
</div>
|
|
346
374
|
{/* the rule runs the full rail width, out past the px-2 */}
|
|
347
375
|
{bottomItems.length > 0 && <div className="self-stretch -mx-2 border-t border-fg-08" />}
|
|
348
376
|
{bottomItems.map(row)}
|