@kolkrabbi/kol-framework 0.15.1 → 0.16.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 CHANGED
@@ -112,6 +112,36 @@ body {
112
112
  * said 1800 — same element, two answers (lobby/WidthSystemContradictions). */
113
113
  @media (min-width: 1920px) { :root { --kol-container-max: var(--kol-content-shell, 1800px); } }
114
114
 
115
+ /* SideNav's own box — IN RULES, not utilities (2026-08-09, defect found by
116
+ * the kol-website session during 0.15.1 verify): this whole file lives in
117
+ * @layer components, so a Tailwind utility the JSX stamps on the same element
118
+ * outranks every rule here — the collapsed overrides below silently lost to
119
+ * py-2 / pl-6 / Icon's own inline-flex. Same law as type: a component's box
120
+ * lives in its own rule, never a utility racing it. The JSX keeps only
121
+ * type/color utilities on these elements. */
122
+ .kol-sidenav-hop {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: 0.75rem;
126
+ width: 100%;
127
+ position: relative;
128
+ padding: 0.5rem 2.5rem 0.5rem 1.5rem;
129
+ }
130
+ /* the caret rides a PLAIN span (Icon stamps inline-flex on its own element,
131
+ * which would outrank the collapsed display:none) */
132
+ .kol-sidenav-hop-caret {
133
+ position: absolute;
134
+ top: 0.75rem;
135
+ right: 0.75rem;
136
+ }
137
+ .kol-sidenav-list {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 0.125rem;
141
+ margin-bottom: 0.5rem;
142
+ }
143
+ .kol-sidenav-footer { padding-inline: 1.5rem 1rem; }
144
+
115
145
  /* :root[data-sidenav="collapsed"] — the manual collapse, rebuilt DELIBERATELY
116
146
  * 2026-08-09 (lobby: SideNavGrabResize) after the 2026-07-29 removal of the
117
147
  * renderer-less stamp. SideNav's grab edge (useDragResize.js) owns the state
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-framework",
3
- "version": "0.15.1",
3
+ "version": "0.16.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",
@@ -27,7 +27,7 @@
27
27
  "app-shell"
28
28
  ],
29
29
  "dependencies": {
30
- "@kolkrabbi/kol-component": "0.27.0",
30
+ "@kolkrabbi/kol-component": "0.28.0",
31
31
  "@kolkrabbi/kol-icons": "0.10.0"
32
32
  },
33
33
  "peerDependencies": {
package/src/SideNav.jsx CHANGED
@@ -62,6 +62,16 @@ export default function SideNav({
62
62
  const isPageActive = (to) => (isActive ? isActive(to) : pathname === to)
63
63
  const isActiveCat = (cat) => pagesOf(cat).some((p) => isPageActive(p.to))
64
64
 
65
+ /* Every navigation closes the mobile drawer — the prop sat accepted-but-
66
+ * unread until the kol-website session flagged it (2026-08-09) — and, from
67
+ * the collapsed rail, expands the sidebar ("pressing any icon would expand
68
+ * the sidebar", same ruling as the chip removal). */
69
+ const handleNavigate = (e) => {
70
+ onNavigate?.(e)
71
+ onCloseDrawer?.()
72
+ if (collapsed) toggleCollapsed()
73
+ }
74
+
65
75
  /* Which categories are disclosed. INDEPENDENT per category (elder ruling
66
76
  * 2026-08-01) — arriving somewhere must not silently close wherever you
67
77
  * were. Loads closed, in-memory only (elder ruling 2026-08-06). */
@@ -71,6 +81,7 @@ export default function SideNav({
71
81
  next.has(id) ? next.delete(id) : next.add(id)
72
82
  return next
73
83
  })
84
+ const discloseCat = (id) => setOpenCats((prev) => new Set(prev).add(id))
74
85
 
75
86
  /* One collapse contract, two gestures — the hook owns the stamp, the
76
87
  * storage schema and the state; the chip Button and the grab edge both
@@ -84,19 +95,24 @@ export default function SideNav({
84
95
  className={`kol-sidenav sticky top-0 self-start h-dvh flex flex-col z-20${background ? ' bg-surface-primary border-r border-fg-08' : ''}${collapsed ? ' is-collapsed' : ''}${drawerOpen ? ' is-drawer-open' : ''}`}
85
96
  >
86
97
  {/* The elder A/B (closed 2026-08-01): the DS Button IS the collapse
87
- * control. Offset is half the pinned sm square, centring it on the
88
- * rail's edge — the DS hardcodes that square and exposes no token. */}
89
- <Button
90
- variant="primary"
91
- size="sm"
92
- radius="full"
93
- iconOnly={collapsed ? 'chevron-right' : 'chevron-left'}
94
- aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
95
- aria-expanded={!collapsed}
96
- title={collapsed ? 'Expand' : 'Collapse'}
97
- onClick={toggleCollapsed}
98
- className="kol-sidenav-toggle absolute top-5 right-[-0.875rem] z-[2]"
99
- />
98
+ * control — EXPANDED ONLY (user ruling 2026-08-09, verbatim: "REMOVE
99
+ * chevron from collapsed view … from collapsed view pressing any icon
100
+ * would expand the sidebar!"). The collapsed rail's expand affordance
101
+ * is the rows themselves. Offset is half the pinned sm square,
102
+ * centring it on the rail's edge. */}
103
+ {!collapsed && (
104
+ <Button
105
+ variant="primary"
106
+ size="sm"
107
+ radius="full"
108
+ iconOnly="chevron-left"
109
+ aria-label="Collapse sidebar"
110
+ aria-expanded
111
+ title="Collapse"
112
+ onClick={toggleCollapsed}
113
+ className="kol-sidenav-toggle absolute top-5 right-[-0.875rem] z-[2]"
114
+ />
115
+ )}
100
116
 
101
117
  <div className="kol-sidenav-scroll flex-1 flex flex-col justify-between overflow-y-auto pt-4 pb-4 [scrollbar-width:thin]">
102
118
  <nav aria-label="Sections">
@@ -105,7 +121,10 @@ export default function SideNav({
105
121
  const pages = pagesOf(cat)
106
122
  const isOpen = openCats.has(cat.id)
107
123
  const isLink = !pages.length && cat.to
108
- const rowCls = 'kol-sidenav-hop kol-helper-12 text-emphasis w-full relative flex items-center gap-3 py-2 pr-10 pl-6 no-underline'
124
+ /* Box utilities deliberately absent — the hop's layout lives in
125
+ * .kol-sidenav-hop (kol-framework.css): a utility here would
126
+ * outrank the collapsed-state overrides (2026-08-09 defect). */
127
+ const rowCls = 'kol-sidenav-hop kol-helper-12 text-emphasis no-underline'
109
128
  const glyph = (
110
129
  <span className="kol-sidenav-hop-icon inline-flex items-center justify-center w-5 h-5 shrink-0" aria-hidden="true">
111
130
  <Icon name={cat.icon} size={16} />
@@ -119,7 +138,7 @@ export default function SideNav({
119
138
  to={cat.to}
120
139
  end
121
140
  className={({ isActive: navActive }) => `${rowCls}${(isActive ? isActive(cat.to) : navActive) ? ' is-active' : ''}`}
122
- onClick={onNavigate}
141
+ onClick={handleNavigate}
123
142
  >
124
143
  {glyph}
125
144
  <span className="kol-sidenav-hop-label flex-1 min-w-0">{cat.label}</span>
@@ -129,15 +148,28 @@ export default function SideNav({
129
148
  type="button"
130
149
  className={`${rowCls} bg-transparent border-0 cursor-pointer text-left${isActiveCat(cat) ? ' is-active' : ''}`}
131
150
  aria-expanded={isOpen}
132
- onClick={() => toggleCat(cat.id)}
151
+ /* Collapsed rail: the click meant "open this" — expand the
152
+ * rail AND disclose the category, or the tap is wasted on
153
+ * an expand that lands on a closed tree. */
154
+ onClick={() => {
155
+ if (collapsed) {
156
+ toggleCollapsed()
157
+ discloseCat(cat.id)
158
+ } else {
159
+ toggleCat(cat.id)
160
+ }
161
+ }}
133
162
  >
134
163
  {glyph}
135
164
  <span className="kol-sidenav-hop-label flex-1 min-w-0">{cat.label}</span>
136
- <Icon
137
- name="chevron-down"
138
- size={12}
139
- className={`kol-sidenav-hop-caret absolute top-3 right-3 transition-transform duration-150${isOpen ? '' : ' -rotate-90'}`}
140
- />
165
+ {/* plain wrapper span — Icon stamps inline-flex on its own
166
+ * element, which would outrank the rail's display:none */}
167
+ <span
168
+ className={`kol-sidenav-hop-caret transition-transform duration-150${isOpen ? '' : ' -rotate-90'}`}
169
+ aria-hidden="true"
170
+ >
171
+ <Icon name="chevron-down" size={12} />
172
+ </span>
141
173
  </button>
142
174
  )
143
175
  return (
@@ -151,7 +183,7 @@ export default function SideNav({
151
183
  ) : head}
152
184
 
153
185
  {!isLink && isOpen && (
154
- <ul className="kol-sidenav-list mb-2 flex flex-col gap-[2px]">
186
+ <ul className="kol-sidenav-list">
155
187
  {pages.map((page) => (
156
188
  <li key={page.to}>
157
189
  <NavLink
@@ -159,7 +191,7 @@ export default function SideNav({
159
191
  end
160
192
  style={pageStyle}
161
193
  className={({ isActive: navActive }) => ((isActive ? isActive(page.to) : navActive) ? pageActiveCls : pageCls)}
162
- onClick={onNavigate}
194
+ onClick={handleNavigate}
163
195
  >
164
196
  {page.label}
165
197
  </NavLink>
@@ -180,7 +212,7 @@ export default function SideNav({
180
212
  </div>
181
213
  </div>
182
214
 
183
- <div className="kol-sidenav-footer flex items-center pl-6 pr-4 h-14 border-t border-fg-08 min-w-0">
215
+ <div className="kol-sidenav-footer flex items-center h-14 border-t border-fg-08 min-w-0">
184
216
  <a
185
217
  href="https://kolkrabbi.io"
186
218
  target="_blank"