@kolkrabbi/kol-shell 0.16.1 → 0.17.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.16.1",
3
+ "version": "0.17.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",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@kolkrabbi/kol-component": "^0.126.0",
24
- "@kolkrabbi/kol-icons": "^0.24.0",
25
24
  "@kolkrabbi/kol-framework": "^0.35.0",
25
+ "@kolkrabbi/kol-icons": "^0.24.0",
26
26
  "@kolkrabbi/kol-theme": "^0.88.0"
27
27
  },
28
28
  "files": [
package/src/AppShell.jsx CHANGED
@@ -21,6 +21,12 @@ import TouchDeviceOverlay, { useTouchPrimary } from './TouchDeviceOverlay.jsx'
21
21
  * `kol-framework.css` is no longer required by the shell (it was, for one
22
22
  * grid rule, 0.13–0.15).
23
23
  *
24
+ * `railComponent` (RailTwoLevelSections, kol-fxr 2026-08-28) — the rail is
25
+ * rendered directly here, so proving a rail change in a consumer meant forking
26
+ * BOTH this file and `NavRail`. Pass a component with `NavRail`'s props to
27
+ * render your own; default is `NavRail`. A seam for experiments, not an
28
+ * invitation to keep a fork.
29
+ *
24
30
  * @param {Array} props.items nav items `{ icon, path, label }`
25
31
  * @param {Array} props.bottomItems items pinned below the theme toggle
26
32
  * @param {Object} props.logomark `{ svgUrl, title }` — the rail's header mark, at the top in both states, navigates to '/'
@@ -62,6 +68,7 @@ export default function AppShell({
62
68
  currentPath,
63
69
  onNavigate,
64
70
  iconComponent,
71
+ railComponent: Rail = NavRail,
65
72
  railToggleKey,
66
73
  touch = 'shell',
67
74
  appName,
@@ -122,7 +129,7 @@ export default function AppShell({
122
129
  >
123
130
  {touch === 'overlay' && <TouchDeviceOverlay appName={appName} />}
124
131
  {!navHidden && (
125
- <NavRail
132
+ <Rail
126
133
  items={items}
127
134
  bottomItems={bottomItems}
128
135
  logomark={logomark}
package/src/NavRail.jsx CHANGED
@@ -34,7 +34,30 @@ import Logomark from './Logomark.jsx'
34
34
  * has animation and gsap") — `.kol-rail-grab` in kol-theme's kol-animation.css,
35
35
  * its pointer numbers in kol-component's `utilities/motion` (`GRAB`).
36
36
  *
37
- * @param {Array} items `{ icon, path, label, sub?: [{ path, label }] }`
37
+ * TWO LEVELS (RailTwoLevelSections, kol-fxr 2026-08-28 user: "the NAV has 20px
38
+ * icons in 32px containers, maybe level below has 12px icons in 20px container
39
+ * aligned to right? … you expand you see that they have main 56 items each, which
40
+ * you won't be aware of until you actually open labs, which should not happen
41
+ * automatically"). An item with `sub` is a SECTION: the same L1 row, plus a caret.
42
+ * Its rows are L2 — a 12px glyph in a 20px box, indented so its glyph column sits
43
+ * to the RIGHT of L1's (glyph x 14 → 30), resting a rung quieter at `oq-64` and
44
+ * going `oq-96` when it is the route. fxr's labs chrome is ~44 rows across four
45
+ * method sections; on one level they flatten into an icon column built for five
46
+ * destinations and the section names have nowhere to render at all.
47
+ *
48
+ * L2 is NOT a `Button`: the icon-button ladder is sm 28 / md 32 / lg 36 and this
49
+ * rung is 20 — there is nothing below sm, and adding one is a change to that
50
+ * ladder's law, which is the user's call and not this ticket's. The row is
51
+ * written directly and keys `aria-current`, so kol-theme's
52
+ * `.kol-shell-rail .kol-btn-nav` active rules still reach it. A sub row with no
53
+ * `icon` renders label-only, as it did before this level existed.
54
+ *
55
+ * NOTHING AUTO-EXPANDS. An L2 row is behind BOTH the rail's clipped width and its
56
+ * section's own disclosure, and `open` starts `false` with no prop to get it
57
+ * wrong — arriving on a route reveals nothing, by construction rather than by a
58
+ * flag a consumer has to remember.
59
+ *
60
+ * @param {Array} items `{ icon, path, label, sub?: [{ icon?, path, label }] }`
38
61
  * @param {Array} bottomItems pinned below the rule (Settings)
39
62
  * @param {Object} logomark `{ svgUrl, title }` — the mark, and the app name beside it when open
40
63
  * @param {string} currentPath active match: '/' exact, else prefix → aria-current="page"
@@ -138,6 +161,12 @@ function useRailDrag(railRef, grabRef, onSnap) {
138
161
 
139
162
  /* one row: the rung exactly where the closed rail had it, then the label and
140
163
  * (with `sub`) the chevron — the row clips to the rail's width */
164
+ /* the L2 glyph goes through the same injection seam Button gives L1 */
165
+ function IconAt({ name, size, component }) {
166
+ const Cmp = component || Icon
167
+ return <Cmp name={name} size={size} />
168
+ }
169
+
141
170
  function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconComponent, railOpen }) {
142
171
  const [open, setOpen] = useState(false)
143
172
  const active = path === '/' ? currentPath === '/' : currentPath.startsWith(path)
@@ -184,16 +213,37 @@ function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconCompone
184
213
  />
185
214
  )}
186
215
  </div>
187
- {railOpen && open && sub.map((s) => (
188
- <div key={s.path} className="flex items-center w-full overflow-hidden shrink-0 pl-11">
189
- <span
190
- className="kol-helper-12 uppercase text-oq-64 hover:text-oq-96 truncate cursor-pointer"
216
+ {railOpen && open && sub.map((s) => {
217
+ const on = s.path === '/' ? currentPath === '/' : currentPath.startsWith(s.path)
218
+ return (
219
+ <div
220
+ key={s.path}
221
+ /* 18px of gutter puts the 20px box at x 26, so its 12px glyph starts
222
+ * at 30 — one step right of L1's 20-in-32 at 14 (measured in fxr) */
223
+ className="flex items-center gap-3 w-full overflow-hidden shrink-0 cursor-pointer"
224
+ style={{ paddingLeft: 18 }}
191
225
  onClick={() => onNavigate?.(s.path)}
226
+ aria-current={on ? 'page' : undefined}
227
+ title={s.label}
192
228
  >
193
- {s.label}
194
- </span>
195
- </div>
196
- ))}
229
+ {s.icon && (
230
+ <span
231
+ className="w-5 h-5 shrink-0 inline-flex items-center justify-center"
232
+ style={{ color: on ? 'var(--kol-oq-96)' : 'var(--kol-oq-64)' }}
233
+ aria-hidden="true"
234
+ >
235
+ <IconAt name={s.icon} size={12} component={iconComponent} />
236
+ </span>
237
+ )}
238
+ <span
239
+ className="kol-helper-12 uppercase flex-1 min-w-0 truncate"
240
+ style={{ color: on ? 'var(--kol-oq-96)' : 'var(--kol-oq-64)' }}
241
+ >
242
+ {s.label}
243
+ </span>
244
+ </div>
245
+ )
246
+ })}
197
247
  </>
198
248
  )
199
249
  }