@kolkrabbi/kol-framework 0.10.3 → 0.13.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
@@ -31,8 +31,21 @@ body {
31
31
 
32
32
  :root {
33
33
  --kol-topnav-h: 0px;
34
- --kol-sidenav-w: 260px;
34
+ /* 16rem — the rail width, one number (2026-07-31). It read 260 while every
35
+ grid track that had to line up with it read 256, so the shell header's
36
+ brand block sat 4 off the sidebar column it exists to align to. */
37
+ --kol-sidenav-w: 16rem;
35
38
  --kol-sidenav-w-collapsed: 56px;
39
+ /* The workshop shell's RIGHT rail. EQUAL TO THE LEFT (user ruling
40
+ 2026-08-01) — the two rails frame the same content and a lopsided frame is
41
+ a frame nobody drew on purpose; it read 14rem against the left's 16rem.
42
+ Kept as its own token rather than aliased: the right rail can be ruled
43
+ different later without touching the sidenav.
44
+ The track is RESERVED whether or not the rail has anything in it — a rail
45
+ that vanishes on a heading-less page re-flows main, so the same shell
46
+ renders at two widths. Distinct from --kol-toc-w further down, which is
47
+ the BRAND layout's rail: same concept, two shells, two numbers. */
48
+ --kol-shell-toc-w: 16rem;
36
49
  --kol-container-max: 100%;
37
50
 
38
51
  /* Opacity tokens (--kol-fg-*) + text-role tokens (--kol-text-*) live in
@@ -44,6 +57,15 @@ body {
44
57
  Intentional: site chrome uses browser-ease for a softer feel. */
45
58
  --kol-t-btn: background 200ms ease, color 200ms ease, border-color 200ms ease;
46
59
 
60
+ /* Shell CHROME inset (2026-07-31, user ruling: "24, it's what lives in
61
+ kolkrabbi") — a separate concern from the page ladder below, and
62
+ deliberately tighter: chrome is not page content. The shell header and the
63
+ shell content frame both read this, so they cannot disagree; before it,
64
+ the header carried bare Tailwind steps and the frame carried the page ramp.
65
+ FLAT, not stepped — one value at every width, on the user's word.
66
+ Do NOT reach for this on page containers — that is --kol-pad-section-x. */
67
+ --kol-pad-chrome-x: var(--kol-spacing-6);
68
+
47
69
  /* Stepped page / section / band padding tokens.
48
70
  Used by chrome (topnav, sidenav) and landing bands. Scaled at 768 / 1024
49
71
  breakpoints so one set of rules covers mobile → desktop without clamp(). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-framework",
3
- "version": "0.10.3",
3
+ "version": "0.13.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,8 +27,8 @@
27
27
  "app-shell"
28
28
  ],
29
29
  "dependencies": {
30
- "@kolkrabbi/kol-component": "0.15.1",
31
- "@kolkrabbi/kol-icons": "0.8.11"
30
+ "@kolkrabbi/kol-component": "0.24.0",
31
+ "@kolkrabbi/kol-icons": "0.10.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": "^18.3.0 || ^19.0.0",
package/src/AppShell.jsx CHANGED
@@ -53,7 +53,7 @@ export default function AppShell({ navTree = [], getActivePage, header, footer,
53
53
  </button>
54
54
 
55
55
  <div
56
- className="kol-sidenav-backdrop fixed inset-0 z-20 bg-black/50 opacity-0 pointer-events-none transition-opacity duration-200 md:hidden"
56
+ className="kol-sidenav-backdrop fixed inset-0 z-20 kol-overlay-scrim opacity-0 pointer-events-none transition-opacity duration-200 md:hidden"
57
57
  onClick={() => setDrawerOpen(false)}
58
58
  aria-hidden="true"
59
59
  />
@@ -1,5 +1,5 @@
1
1
  import { Icon } from '@kolkrabbi/kol-icons'
2
- import { SearchInput, Tooltip } from '@kolkrabbi/kol-component'
2
+ import { IconFrame, SearchInput, Tooltip, glyphSize } from '@kolkrabbi/kol-component'
3
3
  import ThemeToggle from './ThemeToggle.jsx'
4
4
 
5
5
  /**
@@ -42,8 +42,35 @@ import ThemeToggle from './ThemeToggle.jsx'
42
42
  * @param {string} className extra classes on the outer bar
43
43
  */
44
44
 
45
- const iconBtnCls =
46
- 'flex h-9 w-9 items-center justify-center rounded p-0 bg-transparent border-0 cursor-pointer transition-colors hover:bg-fg-08 hover:text-emphasis'
45
+ /* ONE header-action glyph size (user 2026-08-01: "icon in navbar is small and
46
+ * wrong"). The row mixed sizes: the ThemeToggle draws its solo glyph at 24 on
47
+ * the `lg` rung while search, GitHub and the hamburger sat at 18 — four
48
+ * controls, two scales, in a row eight inches wide.
49
+ *
50
+ * It is NOT an independent number. Every header control is a `lg` rung, and
51
+ * `SOLO.lg` (hooks/glyphLadders.js) is 24 — this constant is that ladder rung
52
+ * under a name the chrome can import. Naming it once was not enough on its own:
53
+ * it shipped exported and the two rail toggles still hardcoded 18 three rows
54
+ * below it, which is the whole reason the row looked wrong after the fix. The
55
+ * durable answer is that no header control writes a glyph size at all — it
56
+ * writes `size="lg"` and the ladder resolves it.
57
+ *
58
+ * @deprecated 2026-08-01 — **zero consumers as of this change.** Every header
59
+ * control now names its rung and takes the glyph from `SOLO`. Kept exported
60
+ * because it is public API of a published package (breaking an export needs a
61
+ * changeset + major bump); a consumer still importing it gets the right number,
62
+ * it is just no longer how the chrome decides. Passing it as `iconSize` is the
63
+ * anti-pattern this whole change removes: it pins the glyph while leaving the
64
+ * SQUARE on whatever rung the call site forgot to set, which is precisely how
65
+ * the search trigger ended up drawing an lg glyph in an md box. */
66
+ export const HEADER_ICON = 24
67
+
68
+ /* `iconBtnCls` lived here — a private class string re-implementing the icon
69
+ * button box (h-9 w-9, rounded, transparent, hover wash) on three call sites,
70
+ * with a near-duplicate of it hand-written in the showcase's GitHub link. Four
71
+ * containers, one job. Deleted 2026-08-01: the box has an owner, and it is
72
+ * `Button variant="ghost" quiet iconOnly` — the shape the search trigger in
73
+ * ShellLayout had been calling correctly the entire time. */
47
74
 
48
75
  export default function ShellHeader({
49
76
  brand,
@@ -66,7 +93,11 @@ export default function ShellHeader({
66
93
  <header className={`kol-shell-header sticky top-0 z-50 shrink-0 bg-surface-primary ${className}`.trim()}>
67
94
  {/* Row 1: brand block + controls */}
68
95
  <div className="border-b border-fg-08">
69
- <div className="w-full px-4 py-4 md:px-5 lg:px-6">
96
+ {/* Chrome inset from --kol-pad-chrome-x, not Tailwind steps
97
+ * (2026-07-31): the header sat on its own improvised steps while the
98
+ * shell content frame sat on the page ramp, so the sidebar hung right
99
+ * of the wordmark above it. One token, both surfaces. */}
100
+ <div className="w-full py-4" style={{ paddingInline: 'var(--kol-pad-chrome-x)' }}>
70
101
  <div className="flex items-center justify-between">
71
102
  {/* kol-shell-header-brand reserves the nav-column width at lg+ so
72
103
  * the Row-2 tabs align to the shell grid. */}
@@ -82,19 +113,24 @@ export default function ShellHeader({
82
113
  * quiet square = button geometry + fill none + no label
83
114
  * (was the old `icon` variant default). */}
84
115
  <span className="hidden lg:inline-flex"><ThemeToggle fill="none" label={false} size="lg" /></span>
85
- <span className="lg:hidden"><ThemeToggle fill="none" label={false} /></span>
116
+ <span className="lg:hidden"><ThemeToggle fill="none" label={false} size="lg" /></span>
86
117
  </Tooltip>
87
118
  )}
88
119
  {onMenuClick && (
89
120
  <Tooltip label="Open navigation menu">
90
- <button
91
- type="button"
92
- className={`${iconBtnCls} text-fg-64`}
121
+ {/* IconFrame, not Button (user ruling 2026-08-01). Header
122
+ * chrome takes a click but must not light up; the frame has
123
+ * no state rules at all, which is the property Button can
124
+ * only approximate by dropping its own. `nav` rests at
125
+ * oq-64 — the `text-fg-64` this control hand-wrote for
126
+ * months. */}
127
+ <IconFrame
128
+ name="hamburger"
129
+ variant="nav"
130
+ size="lg"
93
131
  onClick={onMenuClick}
94
132
  aria-label="Open navigation menu"
95
- >
96
- <Icon name="hamburger" size={18} />
97
- </button>
133
+ />
98
134
  </Tooltip>
99
135
  )}
100
136
  </div>
@@ -105,7 +141,7 @@ export default function ShellHeader({
105
141
  {/* Row 2: section tabs + inline search + rail toggles */}
106
142
  {hasTabRow && (
107
143
  <div className="border-b border-fg-08">
108
- <div className="w-full px-4 md:px-5 lg:px-6">
144
+ <div className="w-full" style={{ paddingInline: 'var(--kol-pad-chrome-x)' }}>
109
145
  <div className="kol-shell-header-tabs">
110
146
  <nav className="flex flex-1 gap-6" aria-label="Sections">
111
147
  {nav.map((item) => {
@@ -118,7 +154,12 @@ export default function ShellHeader({
118
154
  aria-current={active ? 'page' : undefined}
119
155
  onClick={onNavigate ? (event) => onNavigate(event, item) : undefined}
120
156
  >
121
- {item.icon && <Icon name={item.icon} size={14} />}
157
+ {/* The ladder, not a literal (2026-08-01). This hardcoded
158
+ * 14 — the same value ADJACENT.sm carries, but written
159
+ * out, so the tab could not follow the rule it was
160
+ * meant to. The tab's own type is 14/18, which is that
161
+ * rung. */}
162
+ {item.icon && <Icon name={item.icon} size={glyphSize('sm')} />}
122
163
  {item.label}
123
164
  </a>
124
165
  )
@@ -135,28 +176,32 @@ export default function ShellHeader({
135
176
  <div className="hidden lg:flex items-center gap-1 pb-2">
136
177
  {onNavToggle && (
137
178
  <Tooltip label="Toggle navigation sidebar">
138
- <button
139
- type="button"
140
- className={`${iconBtnCls} ${navCollapsed ? 'text-fg-32' : 'text-fg-64'}`}
179
+ {/* The collapsed tint is a VARIANT SWAP, not a state:
180
+ * `ghost` rests at oq-48, `nav` at oq-64, and both are
181
+ * static classes with no rules to fire. That is the only
182
+ * honest way to express it on a frame — Button's `quiet`
183
+ * is an opacity transition, i.e. exactly the kind of
184
+ * behaviour this control was ruled out of. */}
185
+ <IconFrame
186
+ name="panel-left"
187
+ variant={navCollapsed ? 'ghost' : 'nav'}
188
+ size="sm"
141
189
  onClick={onNavToggle}
142
190
  aria-label="Toggle navigation sidebar"
143
191
  aria-pressed={!navCollapsed}
144
- >
145
- <Icon name="panel-left" size={18} />
146
- </button>
192
+ />
147
193
  </Tooltip>
148
194
  )}
149
195
  {onTocToggle && (
150
196
  <Tooltip label="Toggle table of contents sidebar">
151
- <button
152
- type="button"
153
- className={`${iconBtnCls} ${tocCollapsed ? 'text-fg-32' : 'text-fg-64'}`}
197
+ <IconFrame
198
+ name="panel-right"
199
+ variant={tocCollapsed ? 'ghost' : 'nav'}
200
+ size="sm"
154
201
  onClick={onTocToggle}
155
202
  aria-label="Toggle table of contents sidebar"
156
203
  aria-pressed={!tocCollapsed}
157
- >
158
- <Icon name="panel-right" size={18} />
159
- </button>
204
+ />
160
205
  </Tooltip>
161
206
  )}
162
207
  </div>
package/src/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
  export { default as AppShell } from './AppShell.jsx'
11
11
  export { ShellTocContext, ShellTocCollapsedContext } from './AppShell.jsx'
12
12
  export { default as SideNav } from './SideNav.jsx'
13
- export { default as ShellHeader } from './ShellHeader.jsx'
13
+ export { default as ShellHeader, HEADER_ICON } from './ShellHeader.jsx'
14
14
  export { default as ThemeToggle } from './ThemeToggle.jsx'
15
15
  export { useTheme, applyTheme, getInitialTheme, THEME_STORAGE_KEY } from './theme.js'
16
16
  export { default as Layout } from './Layout.jsx'