@kolkrabbi/kol-framework 0.22.0 → 0.23.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/kol-framework.css +30 -1
- package/package.json +3 -3
- package/src/ShellHeader.jsx +22 -1
- package/src/SideNav.jsx +6 -1
package/kol-framework.css
CHANGED
|
@@ -152,6 +152,25 @@ body {
|
|
|
152
152
|
}
|
|
153
153
|
.kol-sidenav-footer { padding-inline: 1.5rem 1rem; }
|
|
154
154
|
|
|
155
|
+
/* The aside's OWN box (SideNavMobilePosition, kol-website 2026-08-25) — the
|
|
156
|
+
* hop's law above, one element up. `sticky top-0 self-start h-dvh flex
|
|
157
|
+
* flex-col z-20` rode the className, and a utility outranks every rule in
|
|
158
|
+
* this layer — so the 767px drawer block below (`position: fixed`) never won:
|
|
159
|
+
* the aside stayed IN FLOW, the one-column grid handed it an 852px row, and
|
|
160
|
+
* every consumer page opened one viewport down on phones (probe: aside
|
|
161
|
+
* `sticky`, content top=852 at 393; the consumer's stopgap was one media rule
|
|
162
|
+
* re-stating `fixed`). Declared here, the drawer rule beats it by source
|
|
163
|
+
* order — which is how it was written to work. */
|
|
164
|
+
.kol-sidenav {
|
|
165
|
+
position: sticky;
|
|
166
|
+
top: 0;
|
|
167
|
+
align-self: start;
|
|
168
|
+
height: 100dvh;
|
|
169
|
+
display: flex;
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
z-index: var(--kol-z-sticky);
|
|
172
|
+
}
|
|
173
|
+
|
|
155
174
|
/* :root[data-sidenav="collapsed"] — the manual collapse, rebuilt DELIBERATELY
|
|
156
175
|
* 2026-08-09 (lobby: SideNavGrabResize) after the 2026-07-29 removal of the
|
|
157
176
|
* renderer-less stamp. SideNav's grab edge (useDragResize.js) owns the state
|
|
@@ -894,7 +913,15 @@ body {
|
|
|
894
913
|
}
|
|
895
914
|
}
|
|
896
915
|
|
|
897
|
-
/* Tab strip — horizontally scrollable, scrollbar hidden.
|
|
916
|
+
/* Tab strip — horizontally scrollable, scrollbar hidden.
|
|
917
|
+
* Snaps to a tab's start on a flick (WorkshopShellMobile, kol-website
|
|
918
|
+
* 2026-08-25): the strip measured 613px of tabs in a 345px viewport and did
|
|
919
|
+
* scroll, but a swipe could settle on half a label. `proximity`, not
|
|
920
|
+
* `mandatory` — the last tabs can never reach a start position, and mandatory
|
|
921
|
+
* would refuse to show them. ShellHeader.jsx scrolls the active tab into view
|
|
922
|
+
* on load, to the same start edge, so the two never fight. NO edge fade: the
|
|
923
|
+
* 2026-07-28 ruling bans scroll-edge paint (the table wrapper's cover
|
|
924
|
+
* gradients died for it 2026-08-09). */
|
|
898
925
|
.kol-shell-header-tabs {
|
|
899
926
|
display: flex;
|
|
900
927
|
flex-wrap: nowrap;
|
|
@@ -903,6 +930,7 @@ body {
|
|
|
903
930
|
padding-top: 8px;
|
|
904
931
|
scrollbar-width: none;
|
|
905
932
|
-ms-overflow-style: none;
|
|
933
|
+
scroll-snap-type: x proximity;
|
|
906
934
|
}
|
|
907
935
|
|
|
908
936
|
.kol-shell-header-tabs::-webkit-scrollbar {
|
|
@@ -931,6 +959,7 @@ body {
|
|
|
931
959
|
border-bottom: 2px solid transparent;
|
|
932
960
|
transition: color 0.2s ease, border-color 0.2s ease;
|
|
933
961
|
text-decoration: none;
|
|
962
|
+
scroll-snap-align: start;
|
|
934
963
|
}
|
|
935
964
|
|
|
936
965
|
.kol-shell-header-tab:hover {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "KOL app shell — sidenav, layout, theme toggle, footer, heroes + brand color layer. SideNav takes nav data as props so the tree stays app-local.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"app-shell"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@kolkrabbi/kol-
|
|
32
|
-
"@kolkrabbi/kol-
|
|
31
|
+
"@kolkrabbi/kol-icons": "^0.18.0",
|
|
32
|
+
"@kolkrabbi/kol-component": "^0.68.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.3.0 || ^19.0.0",
|
package/src/ShellHeader.jsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'react'
|
|
1
2
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
3
|
import { IconFrame, SearchInput, Tooltip, glyphSize } from '@kolkrabbi/kol-component'
|
|
3
4
|
import ThemeToggle from './ThemeToggle.jsx'
|
|
@@ -89,6 +90,26 @@ export default function ShellHeader({
|
|
|
89
90
|
}) {
|
|
90
91
|
const hasTabRow = nav.length > 0 || Boolean(search) || Boolean(onNavToggle) || Boolean(onTocToggle)
|
|
91
92
|
|
|
93
|
+
/* The current tab must be ON SCREEN (WorkshopShellMobile, kol-website
|
|
94
|
+
* 2026-08-25). The strip scrolls (.kol-shell-header-tabs) — but on a phone
|
|
95
|
+
* the active tab measured x=347–445 in a 345px strip: "Dashboard" was the
|
|
96
|
+
* page you were on and the one tab you could not see. Scrolled to the
|
|
97
|
+
* strip's start edge (the snap position), and ONLY when it is out of view,
|
|
98
|
+
* so a wide viewport never moves. Keyed on the active href, not `nav` —
|
|
99
|
+
* consumers rebuild that array every render. Layout effect: before paint,
|
|
100
|
+
* so a route change never flashes the wrong tab. */
|
|
101
|
+
const tabsRef = useRef(null)
|
|
102
|
+
const activeHref = nav.find((item) => (isActive ? isActive(item.href) : false))?.href
|
|
103
|
+
useLayoutEffect(() => {
|
|
104
|
+
const strip = tabsRef.current
|
|
105
|
+
const tab = strip?.querySelector('[aria-current="page"]')
|
|
106
|
+
if (!strip || !tab) return
|
|
107
|
+
const s = strip.getBoundingClientRect()
|
|
108
|
+
const t = tab.getBoundingClientRect()
|
|
109
|
+
if (t.left >= s.left && t.right <= s.right) return
|
|
110
|
+
strip.scrollLeft += t.left - s.left
|
|
111
|
+
}, [activeHref])
|
|
112
|
+
|
|
92
113
|
return (
|
|
93
114
|
<header className={`kol-shell-header sticky top-0 z-50 shrink-0 bg-surface-primary ${className}`.trim()}>
|
|
94
115
|
{/* Row 1: brand block + controls */}
|
|
@@ -139,7 +160,7 @@ export default function ShellHeader({
|
|
|
139
160
|
{hasTabRow && (
|
|
140
161
|
<div className="border-b border-fg-08">
|
|
141
162
|
<div className="w-full" style={{ paddingInline: 'var(--kol-pad-chrome-x)' }}>
|
|
142
|
-
<div className="kol-shell-header-tabs">
|
|
163
|
+
<div className="kol-shell-header-tabs" ref={tabsRef}>
|
|
143
164
|
<nav className="flex flex-1 gap-6" aria-label="Sections">
|
|
144
165
|
{nav.map((item) => {
|
|
145
166
|
const active = isActive ? isActive(item.href) : false
|
package/src/SideNav.jsx
CHANGED
|
@@ -93,7 +93,12 @@ export default function SideNav({
|
|
|
93
93
|
return (
|
|
94
94
|
<aside
|
|
95
95
|
ref={asideRef}
|
|
96
|
-
|
|
96
|
+
/* Box utilities deliberately absent (SideNavMobilePosition, 2026-08-25):
|
|
97
|
+
* the aside's position/height/stack live in .kol-sidenav
|
|
98
|
+
* (kol-framework.css). `sticky … h-dvh z-20` here outranked the 767px
|
|
99
|
+
* drawer rule's `position: fixed`, so every consumer page opened one
|
|
100
|
+
* viewport down on phones. Only colour utilities ride the element. */
|
|
101
|
+
className={`kol-sidenav${background ? ' bg-surface-primary border-r border-fg-08' : ''}${collapsed ? ' is-collapsed' : ''}${drawerOpen ? ' is-drawer-open' : ''}`}
|
|
97
102
|
>
|
|
98
103
|
{/* NO chip Button in either state (user build order 2026-08-09 — "MAKE
|
|
99
104
|
* IT"; supersedes the elder A/B): the pill-marked grab edge below is
|