@kolkrabbi/kol-shell 0.16.1 → 0.17.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/package.json +4 -4
- package/src/AppShell.jsx +8 -1
- package/src/NavRail.jsx +64 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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,7 +12,7 @@
|
|
|
12
12
|
"./src/*": "./src/*"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@kolkrabbi/kol-component": ">=0.
|
|
15
|
+
"@kolkrabbi/kol-component": ">=0.127.0",
|
|
16
16
|
"@kolkrabbi/kol-icons": ">=0.16.0",
|
|
17
17
|
"@kolkrabbi/kol-theme": ">=0.84.0",
|
|
18
18
|
"gsap": "^3.13.0",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@kolkrabbi/kol-component": "^0.126.0",
|
|
24
|
-
"@kolkrabbi/kol-icons": "^0.24.0",
|
|
25
23
|
"@kolkrabbi/kol-framework": "^0.35.0",
|
|
24
|
+
"@kolkrabbi/kol-component": "^0.127.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
|
-
<
|
|
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
|
-
*
|
|
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"
|
|
@@ -58,7 +81,8 @@ const openWidth = () => {
|
|
|
58
81
|
* it lands, so it's not constantly jerking"): it targets the pointer's own
|
|
59
82
|
* position and re-targets only once the pointer is `GRAB.stick` px away, so it
|
|
60
83
|
* lands on you, holds while you move inside the radius, then travels to where you
|
|
61
|
-
* are now.
|
|
84
|
+
* are now — inside `GRAB.range`, the middle band of the edge, so it never rides
|
|
85
|
+
* up beside the logomark. One window listener, rAF-throttled. */
|
|
62
86
|
function useGrabEdge(ref) {
|
|
63
87
|
useEffect(() => {
|
|
64
88
|
let raf = 0
|
|
@@ -73,7 +97,9 @@ function useGrabEdge(ref) {
|
|
|
73
97
|
const near = dist <= GRAB.near || (h.classList.contains('is-near') && dist <= GRAB.sleep)
|
|
74
98
|
h.classList.toggle('is-near', near)
|
|
75
99
|
if (!near) return
|
|
76
|
-
|
|
100
|
+
/* the travel band: bipolar from the middle, GRAB.range of the height */
|
|
101
|
+
const edge = (r.height * (1 - GRAB.range)) / 2
|
|
102
|
+
const along = Math.min(Math.max(clientY - r.top, edge), r.height - edge)
|
|
77
103
|
if (h.dataset.grabSeeded && Math.abs(along - Number(h.dataset.grabTarget)) < GRAB.stick) return
|
|
78
104
|
h.dataset.grabTarget = String(along)
|
|
79
105
|
const vars = { '--kol-rail-grab-y': `${along}px` }
|
|
@@ -138,6 +164,12 @@ function useRailDrag(railRef, grabRef, onSnap) {
|
|
|
138
164
|
|
|
139
165
|
/* one row: the rung exactly where the closed rail had it, then the label and
|
|
140
166
|
* (with `sub`) the chevron — the row clips to the rail's width */
|
|
167
|
+
/* the L2 glyph goes through the same injection seam Button gives L1 */
|
|
168
|
+
function IconAt({ name, size, component }) {
|
|
169
|
+
const Cmp = component || Icon
|
|
170
|
+
return <Cmp name={name} size={size} />
|
|
171
|
+
}
|
|
172
|
+
|
|
141
173
|
function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconComponent, railOpen }) {
|
|
142
174
|
const [open, setOpen] = useState(false)
|
|
143
175
|
const active = path === '/' ? currentPath === '/' : currentPath.startsWith(path)
|
|
@@ -184,16 +216,37 @@ function RailItem({ icon, path, label, sub, currentPath, onNavigate, iconCompone
|
|
|
184
216
|
/>
|
|
185
217
|
)}
|
|
186
218
|
</div>
|
|
187
|
-
{railOpen && open && sub.map((s) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
219
|
+
{railOpen && open && sub.map((s) => {
|
|
220
|
+
const on = s.path === '/' ? currentPath === '/' : currentPath.startsWith(s.path)
|
|
221
|
+
return (
|
|
222
|
+
<div
|
|
223
|
+
key={s.path}
|
|
224
|
+
/* 18px of gutter puts the 20px box at x 26, so its 12px glyph starts
|
|
225
|
+
* at 30 — one step right of L1's 20-in-32 at 14 (measured in fxr) */
|
|
226
|
+
className="flex items-center gap-3 w-full overflow-hidden shrink-0 cursor-pointer"
|
|
227
|
+
style={{ paddingLeft: 18 }}
|
|
191
228
|
onClick={() => onNavigate?.(s.path)}
|
|
229
|
+
aria-current={on ? 'page' : undefined}
|
|
230
|
+
title={s.label}
|
|
192
231
|
>
|
|
193
|
-
{s.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
232
|
+
{s.icon && (
|
|
233
|
+
<span
|
|
234
|
+
className="w-5 h-5 shrink-0 inline-flex items-center justify-center"
|
|
235
|
+
style={{ color: on ? 'var(--kol-oq-96)' : 'var(--kol-oq-64)' }}
|
|
236
|
+
aria-hidden="true"
|
|
237
|
+
>
|
|
238
|
+
<IconAt name={s.icon} size={12} component={iconComponent} />
|
|
239
|
+
</span>
|
|
240
|
+
)}
|
|
241
|
+
<span
|
|
242
|
+
className="kol-helper-12 uppercase flex-1 min-w-0 truncate"
|
|
243
|
+
style={{ color: on ? 'var(--kol-oq-96)' : 'var(--kol-oq-64)' }}
|
|
244
|
+
>
|
|
245
|
+
{s.label}
|
|
246
|
+
</span>
|
|
247
|
+
</div>
|
|
248
|
+
)
|
|
249
|
+
})}
|
|
197
250
|
</>
|
|
198
251
|
)
|
|
199
252
|
}
|