@kolkrabbi/kol-framework 0.23.0 → 0.25.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
@@ -150,6 +150,16 @@ body {
150
150
  gap: 0.125rem;
151
151
  margin-bottom: 0.5rem;
152
152
  }
153
+ /* A nested route GROUP's header (sidenav-nested-groups, kol-studio
154
+ * 2026-08-26): a non-routing label sitting where a leaf sits, its rows one
155
+ * step further in. Type and colour ride the element as utilities
156
+ * (kol-helper-10; text-subtle, text-emphasis when a leaf beneath it is the
157
+ * route); the box lives here. No text-transform — the retired fork's
158
+ * `uppercase` does not return (type law: only the display voice carries one). */
159
+ .kol-sidenav-group {
160
+ display: block;
161
+ padding: 0.25rem 1.5rem 0.25rem 3.5rem;
162
+ }
153
163
  .kol-sidenav-footer { padding-inline: 1.5rem 1rem; }
154
164
 
155
165
  /* The aside's OWN box (SideNavMobilePosition, kol-website 2026-08-25) — the
@@ -493,6 +503,15 @@ body {
493
503
  width: clamp(320px, 72vw, 900px);
494
504
  }
495
505
 
506
+ /* fullWidth (FeaturedCarouselFullWidth, kol-website 2026-08-26): the slide
507
+ * fills its container. The prop only ever dropped the section's own padding —
508
+ * the slide stayed the 72vw peek width, so a 1440 viewport showed 270px of
509
+ * the neighbours on both sides and every neighbour's video autoplayed. Peek
510
+ * is still the default; nothing existing moves. */
511
+ .kol-featured-carousel.is-full .kol-embla-slide {
512
+ width: 100%;
513
+ }
514
+
496
515
  .kol-embla-controls {
497
516
  display: flex;
498
517
  gap: 8px;
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-framework",
3
- "version": "0.23.0",
3
+ "version": "0.25.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",
7
7
  "main": "./src/index.js",
8
8
  "module": "./src/index.js",
9
- "sideEffects": [
10
- "*.css"
11
- ],
12
9
  "exports": {
13
10
  ".": "./src/index.js",
14
11
  "./src/*": "./src/*",
15
12
  "./kol-framework.css": "./kol-framework.css",
16
13
  "./kol-brand-color.css": "./kol-brand-color.css"
17
14
  },
15
+ "peerDependencies": {
16
+ "@kolkrabbi/kol-component": ">=0.70.0",
17
+ "@kolkrabbi/kol-icons": ">=0.18.0",
18
+ "react": "^18.3.0 || ^19.0.0",
19
+ "react-dom": "^18.3.0 || ^19.0.0",
20
+ "react-router-dom": "^6.0.0 || ^7.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@kolkrabbi/kol-component": "^0.70.0",
24
+ "@kolkrabbi/kol-icons": "^0.18.0"
25
+ },
18
26
  "files": [
19
27
  "src",
20
28
  "*.css",
@@ -27,15 +35,6 @@
27
35
  "react",
28
36
  "app-shell"
29
37
  ],
30
- "dependencies": {
31
- "@kolkrabbi/kol-icons": "^0.18.0",
32
- "@kolkrabbi/kol-component": "^0.68.1"
33
- },
34
- "peerDependencies": {
35
- "react": "^18.3.0 || ^19.0.0",
36
- "react-dom": "^18.3.0 || ^19.0.0",
37
- "react-router-dom": "^6.0.0 || ^7.0.0"
38
- },
39
38
  "repository": {
40
39
  "type": "git",
41
40
  "url": "git+https://github.com/Tor-Grimsson/kol-ds.git",
@@ -43,5 +42,8 @@
43
42
  },
44
43
  "publishConfig": {
45
44
  "access": "public"
46
- }
45
+ },
46
+ "sideEffects": [
47
+ "*.css"
48
+ ]
47
49
  }
package/src/SideNav.jsx CHANGED
@@ -13,14 +13,26 @@ import useDragResize from './useDragResize'
13
13
  * better version in kol-website apps:brand") — the brand app's evolved fork
14
14
  * folded back into the package, generic seams kept.
15
15
  *
16
- * TWO LEVELS ONLY (elder ruling 2026-08-01). A category is a grouping LABEL —
17
- * a disclosure <button> with a trailing caret, no route. A page is a route.
18
- * The `#anchor` scroll-spied section layer is gone, and with it the
19
- * group/section tree walkers. The ONE exception: a category with `to` and no
20
- * `pages` (Home) renders as a plain link row — it has nothing to disclose.
16
+ * A category is a grouping LABEL — a disclosure <button> with a trailing
17
+ * caret, no route. A page is a route. The ONE exception: a category with `to`
18
+ * and no `pages` (Home) renders as a plain link row — it has nothing to
19
+ * disclose.
21
20
  *
22
- * navTree: [{ id, label, icon, to?, pages?: [{ label, to }] }]
23
- * (legacy `children` route leaves are read as `pages`; section anchors are not.)
21
+ * The 2026-08-01 "TWO LEVELS ONLY" ruling was the BRAND APP's, about its
22
+ * scroll-spied `#anchor` section layer — that layer stays dead (an `{ id }`
23
+ * leaf is dropped). It was inscribed here as a law for every consumer, and
24
+ * kol-studio lost two groups and six routes from its rail on adopting 0.23.0.
25
+ * User, 2026-08-26, verbatim: "ok that is such a problem, sometimes Im
26
+ * talking about a specific thing and it gets applied as a literal fit-all
27
+ * rule, which isnt the case. its very hard to work in such uncertainty."
28
+ * Re-scoped (sidenav-nested-groups, 0.24.0): nested ROUTE groups are
29
+ * supported. A `{ label, children }` node inside a category renders as a
30
+ * non-routing group header with its rows indented one step under it,
31
+ * recursively. The tree shape is the opt-in — there is no prop — and a tree
32
+ * with no group nodes renders byte-for-byte as before.
33
+ *
34
+ * navTree: [{ id, label, icon, to?, pages?: [{ label, to } | { label, children }] }]
35
+ * (legacy `children` on a category is read as `pages`; section anchors are not.)
24
36
  *
25
37
  * Collapse: the pill-marked grab edge is THE single control (user build
26
38
  * order 2026-08-09, completing SideNavGrabResize — the chip Button is gone
@@ -47,9 +59,17 @@ const pageStyle = { '--kol-sidenav-dot-left': '2.625rem' }
47
59
  const pageCls = `${pageBase} text-strong hover:text-emphasis`
48
60
  const pageActiveCls = `${pageBase} is-active`
49
61
 
50
- /* Legacy DS trees carried `children` (groups + `#anchor` leaves). Route
51
- * leaves survive as pages; anchor sections died with the two-level ruling. */
52
- const pagesOf = (cat) => cat.pages ?? cat.children?.filter((c) => c.to) ?? []
62
+ /* A category's rows: route leaves (`to`) and route groups (`children`).
63
+ * `#anchor` leaves (`id` only) are dropped — that layer died with the brand
64
+ * app's 2026-08-01 ruling and is not reopened here. */
65
+ const rowsOf = (nodes) => nodes.filter((c) => c.to || c.children)
66
+ const pagesOf = (cat) => rowsOf(cat.pages ?? cat.children ?? [])
67
+ /* Every route leaf under a set of rows, through any depth of groups. */
68
+ const leavesOf = (rows) => rows.flatMap((r) => (r.children ? leavesOf(rowsOf(r.children)) : [r]))
69
+ /* A nested row sits one --kol-spacing-3 step further in per depth. Inline,
70
+ * not a rule: `pl-14` rides the leaf as a utility and outranks anything in
71
+ * this layer. Depth 0 carries no indent so a flat tree renders unchanged. */
72
+ const indentOf = (depth) => `calc(3.5rem + ${depth} * var(--kol-spacing-3))`
53
73
 
54
74
  export default function SideNav({
55
75
  drawerOpen = false,
@@ -61,7 +81,7 @@ export default function SideNav({
61
81
  }) {
62
82
  const { pathname } = useLocation()
63
83
  const isPageActive = (to) => (isActive ? isActive(to) : pathname === to)
64
- const isActiveCat = (cat) => pagesOf(cat).some((p) => isPageActive(p.to))
84
+ const isActiveCat = (cat) => leavesOf(pagesOf(cat)).some((p) => isPageActive(p.to))
65
85
 
66
86
  /* Every navigation closes the mobile drawer — the prop sat accepted-but-
67
87
  * unread until the kol-website session flagged it (2026-08-09) — and, from
@@ -90,6 +110,40 @@ export default function SideNav({
90
110
  const asideRef = useRef(null)
91
111
  const { collapsed, toggleCollapsed, grabProps } = useDragResize(asideRef)
92
112
 
113
+ /* Rows under a category: a leaf is a NavLink; a group is a non-routing
114
+ * header over its own rows, one step further in (recursive). A group lights
115
+ * up the way a category does — when one of its leaves is the current route. */
116
+ const renderRows = (rows, depth) => rows.map((row, i) => {
117
+ if (row.children) {
118
+ const lit = leavesOf(rowsOf(row.children)).some((l) => isPageActive(l.to))
119
+ return (
120
+ <li key={row.id ?? `${row.label}-${i}`}>
121
+ <div
122
+ className={`kol-sidenav-group kol-helper-10${lit ? ' text-emphasis' : ' text-subtle'}`}
123
+ style={depth ? { paddingLeft: indentOf(depth) } : undefined}
124
+ >
125
+ {row.label}
126
+ </div>
127
+ <ul className="kol-sidenav-list">{renderRows(rowsOf(row.children), depth + 1)}</ul>
128
+ </li>
129
+ )
130
+ }
131
+ return (
132
+ <li key={row.to}>
133
+ <NavLink
134
+ to={row.to}
135
+ end
136
+ /* the dot keeps its 0.875rem lead on the text at every depth */
137
+ style={depth ? { paddingLeft: indentOf(depth), '--kol-sidenav-dot-left': `calc(2.625rem + ${depth} * var(--kol-spacing-3))` } : pageStyle}
138
+ className={({ isActive: navActive }) => ((isActive ? isActive(row.to) : navActive) ? pageActiveCls : pageCls)}
139
+ onClick={handleNavigate}
140
+ >
141
+ {row.label}
142
+ </NavLink>
143
+ </li>
144
+ )
145
+ })
146
+
93
147
  return (
94
148
  <aside
95
149
  ref={asideRef}
@@ -174,21 +228,7 @@ export default function SideNav({
174
228
  ) : head}
175
229
 
176
230
  {!isLink && isOpen && (
177
- <ul className="kol-sidenav-list">
178
- {pages.map((page) => (
179
- <li key={page.to}>
180
- <NavLink
181
- to={page.to}
182
- end
183
- style={pageStyle}
184
- className={({ isActive: navActive }) => ((isActive ? isActive(page.to) : navActive) ? pageActiveCls : pageCls)}
185
- onClick={handleNavigate}
186
- >
187
- {page.label}
188
- </NavLink>
189
- </li>
190
- ))}
191
- </ul>
231
+ <ul className="kol-sidenav-list">{renderRows(pages, 0)}</ul>
192
232
  )}
193
233
  </li>
194
234
  )