@kolkrabbi/kol-framework 0.38.0 → 0.40.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/README.md CHANGED
@@ -26,17 +26,17 @@ chrome above the theme's type layer and the same package renders differently
26
26
  per consumer.
27
27
 
28
28
  ```jsx
29
- import { AppShell } from '@kolkrabbi/kol-framework'
30
- import { NAV_TREE, getActivePage } from './sidebars.config'
29
+ import { PageLayout } from '@kolkrabbi/kol-framework'
30
+ import { NAV_TREE } from './sidebars.config'
31
31
 
32
- <AppShell navTree={NAV_TREE} getActivePage={getActivePage} />
32
+ <PageLayout navTree={NAV_TREE} pageWash="var(--kol-fg-02)" />
33
33
  ```
34
34
 
35
- Exports: `AppShell`, `SideNav`, `ThemeToggle`, `Layout`, `PageSection`, `PortalFooter`, `ScrollToTop`, `BrandHero`, `SubPageHero`.
35
+ Exports: `PageLayout` (alias `AppShell`), `SideNav`, `ThemeToggle`, `Layout`, `PageHero` (aliases `BrandHero`, `SubPageHero`), `PageSection`, `PortalFooter`, `ScrollToTop`.
36
36
 
37
37
  ## Tailwind v4 consumers
38
38
 
39
- Tailwind skips `node_modules` when scanning, so add this next to the imports above — otherwise the shell's layout utilities never generate and the AppShell grid collapses:
39
+ Tailwind skips `node_modules` when scanning, so add this next to the imports above — otherwise the shell's layout utilities never generate and the PageLayout grid collapses:
40
40
 
41
41
  ```css
42
42
  @source "../node_modules/@kolkrabbi/kol-framework/src";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-framework",
3
- "version": "0.38.0",
3
+ "version": "0.40.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",
@@ -13,15 +13,15 @@
13
13
  "./kol-brand-color.css": "./kol-brand-color.css"
14
14
  },
15
15
  "peerDependencies": {
16
- "@kolkrabbi/kol-component": ">=0.70.0",
16
+ "@kolkrabbi/kol-component": ">=0.176.0",
17
17
  "@kolkrabbi/kol-icons": ">=0.18.0",
18
- "@kolkrabbi/kol-theme": ">=0.133.0",
18
+ "@kolkrabbi/kol-theme": ">=0.134.0",
19
19
  "react": "^18.3.0 || ^19.0.0",
20
20
  "react-dom": "^18.3.0 || ^19.0.0",
21
21
  "react-router-dom": "^6.0.0 || ^7.0.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@kolkrabbi/kol-component": "^0.175.0",
24
+ "@kolkrabbi/kol-component": "^0.176.0",
25
25
  "@kolkrabbi/kol-icons": "^0.25.0"
26
26
  },
27
27
  "files": [
@@ -0,0 +1,59 @@
1
+ import { Link } from 'react-router-dom'
2
+ import { SectionText } from '@kolkrabbi/kol-component'
3
+
4
+ /**
5
+ * PageHero — the page's opening band: label · title · lede, with an optional
6
+ * mark beside the text and an optional back link above it.
7
+ *
8
+ * ONE HERO (page-family-is-not-a-set, kol-client-olina 2026-09-03; user, on
9
+ * the brand kit: *"why do 3 of 4 prefix PAGE if they are a set?"*). `BrandHero`
10
+ * and `SubPageHero` were the same core with one optional slot each — a `mark`
11
+ * on one, a back link on the other — under two prefixes, and each hand-stacked
12
+ * its own label / title / lede. Both names alias this; the retirements ledger
13
+ * carries them.
14
+ *
15
+ * COMPOSES `SectionText`, the page tier's base, the way `PageHeader` does. The
16
+ * voices are `BrandHero`'s verbatim — `.kol-prose-label` · `.kol-prose-display`
17
+ * · `.kol-prose-lede` (kol-theme's section-head trio, each carrying its own
18
+ * bottom margin, so the block's `gap` is 0 and the rhythm is the classes').
19
+ * `SubPageHero` had no package consumer, so its private voices did not survive
20
+ * the merge; its back link did, class-for-class.
21
+ *
22
+ * @param {string} id section id (default `hero`) — the sidenav's scroll-spy anchor
23
+ * @param {string} backTo route for the back link; unset renders no link
24
+ * @param {ReactNode} backLabel the back link's text
25
+ * @param {ReactNode} label eyebrow above the title
26
+ * @param {ReactNode} title the h1
27
+ * @param {ReactNode} lede standfirst under the title, measured at 60ch
28
+ * @param {ReactNode} mark a node beside the text column (a logo); the text sits right of it
29
+ * @param {string} className extra classes on the section
30
+ */
31
+ export default function PageHero({ id = 'hero', backTo, backLabel, label, title, lede, mark, className = '' }) {
32
+ return (
33
+ <section id={id} className={`kol-page-hero ${className}`.trim()}>
34
+ {backTo && (
35
+ <Link
36
+ to={backTo}
37
+ className="kol-back-link kol-helper-12 tracking-widest text-body hover:text-emphasis no-underline"
38
+ >
39
+ {backLabel}
40
+ </Link>
41
+ )}
42
+ <div className="flex items-center gap-12 flex-wrap">
43
+ {mark}
44
+ <div className="flex-1 min-w-[280px]">
45
+ <SectionText
46
+ eyebrow={label}
47
+ eyebrowClass="kol-prose-label"
48
+ headline={title}
49
+ headlineAs="h1"
50
+ headlineClass="kol-prose-display"
51
+ body={lede}
52
+ bodyClass="kol-prose-lede max-w-[60ch]"
53
+ gap="gap-0"
54
+ />
55
+ </div>
56
+ </div>
57
+ </section>
58
+ )
59
+ }
@@ -21,7 +21,43 @@ export const ShellTocCollapsedContext = createContext(null)
21
21
  /* `sideNav` — every SideNav prop AppShell does not own, spread onto the rail
22
22
  * (sidenav-rail-hairline, 2026-08-26): `background`, `hairline`, `isActive`,
23
23
  * `onNavigate` … the next rail option needs no AppShell release. */
24
- export default function AppShell({ navTree = [], getActivePage: _getActivePage, header, footer, defaultTocContent, sideNav }) {
24
+ /**
25
+ * PageLayout — the brand-book page layout: the `.kol-brand-layout` grid with
26
+ * the sidenav in its first track, the page plane in the second, the mobile
27
+ * drawer + hamburger, the modal provider and the optional TOC rail.
28
+ *
29
+ * WAS `AppShell` (page-family-is-not-a-set, kol-client-olina 2026-09-03). The
30
+ * ticket asked for a `PageLayout` to ship beside the fifteen `.kol-brand-layout`
31
+ * rules kol-framework already owned — every consumer `BrandLayout.jsx` in the
32
+ * estate is a copy of this file's markup. The component existed; the name did
33
+ * not say so, and it collided with kol-shell's `AppShell` (the 48px app rail).
34
+ * Renamed, `AppShell` aliased on the retirements ledger, and the two things the
35
+ * forks carried that it lacked folded in:
36
+ *
37
+ * pageWash the plane's wash over the primary back — the SAME prop and
38
+ * variable as kol-shell's `AppShell` (`--kol-shell-page-wash`; there
39
+ * `PageShell` paints it, here the plane does), so a brand page and a
40
+ * monitor / mirror / fxr page step their lightness with one name
41
+ * (user 2026-08-27). A translucent `fg-*` over `surface-primary`,
42
+ * never an opaque swap. Unset = the plane IS the back.
43
+ * bare the plane and the outlet only — no grid, no rail, no drawer. The
44
+ * forks' `?embed=1` branch, for iframing a page into another site.
45
+ *
46
+ * THE BACK IS `surface-primary` (BrandLayout's line, user rulings 2026-08-24 →
47
+ * 08-27): the rail sits on it, the plane wears the wash. It was
48
+ * `surface-tertiary` here (appshell-content-surface, kol-studio 2026-08-26) —
49
+ * one component takes one model, and kol-studio passes its wash.
50
+ *
51
+ * @param {Array} navTree the sidenav's tree (see SideNav)
52
+ * @param {Function} getActivePage accepted, inert since 2026-08-09
53
+ * @param {ReactNode} header above the outlet on the plane
54
+ * @param {ReactNode} footer below the outlet on the plane
55
+ * @param {ReactNode} defaultTocContent the TOC rail's content when no page registers one
56
+ * @param {object} sideNav every SideNav prop this layout does not own, spread onto the rail
57
+ * @param {string} pageWash the plane's wash, a CSS colour (e.g. `'var(--kol-fg-02)'`)
58
+ * @param {boolean} bare plane + outlet only (default false)
59
+ */
60
+ export default function PageLayout({ navTree = [], getActivePage: _getActivePage, header, footer, defaultTocContent, sideNav, pageWash, bare = false }) {
25
61
  const [drawerOpen, setDrawerOpen] = useState(false)
26
62
  const [tocContent, setTocContent] = useState(null)
27
63
  const [tocCollapsed, setTocCollapsed] = useState(false)
@@ -39,17 +75,36 @@ export default function AppShell({ navTree = [], getActivePage: _getActivePage,
39
75
  const effectiveTocContent = tocContent ?? defaultTocContent
40
76
  const showToc = Boolean(effectiveTocContent) && !tocCollapsed
41
77
 
78
+ /* the plane paints the wash; the var lives on the plane so any child that
79
+ reads it resolves the same value (BrandLayout, verbatim) */
80
+ const plane = {
81
+ '--kol-shell-page-wash': pageWash,
82
+ background: 'var(--kol-shell-page-wash, var(--kol-surface-primary))',
83
+ }
84
+
85
+ if (bare) {
86
+ return (
87
+ <ModalProvider>
88
+ <ShellTocContext.Provider value={setTocContent}>
89
+ <ShellTocCollapsedContext.Provider value={setTocCollapsed}>
90
+ <div className="min-h-dvh min-w-0" style={plane}>
91
+ <Outlet />
92
+ </div>
93
+ </ShellTocCollapsedContext.Provider>
94
+ </ShellTocContext.Provider>
95
+ </ModalProvider>
96
+ )
97
+ }
98
+
42
99
  return (
43
100
  <ModalProvider>
44
101
  <ShellTocContext.Provider value={setTocContent}>
45
102
  <ShellTocCollapsedContext.Provider value={setTocCollapsed}>
46
103
  <div
47
- /* the PAGE is surface-tertiary, the rail surface-primary — the brand
48
- * split (appshell-content-surface, kol-studio 2026-08-26, user:
49
- * "sidenav primary, site tertiary, like brand"). BrandLayout's own
50
- * line, verbatim; the fork dropped it on 2026-08-09 and every
51
- * AppShell app rendered one flat colour. */
52
- className="kol-brand-layout bg-surface-tertiary min-h-dvh"
104
+ /* TWO surfaces: the BACK is surface-primary, the rail sits on it,
105
+ * and the PLANE wears `pageWash` over it (BrandLayout's line,
106
+ * 2026-08-24 → 08-27). */
107
+ className="kol-brand-layout bg-surface-primary min-h-dvh"
53
108
  data-drawer-open={drawerOpen ? 'true' : undefined}
54
109
  data-toc={showToc ? 'true' : undefined}
55
110
  >
@@ -70,7 +125,7 @@ export default function AppShell({ navTree = [], getActivePage: _getActivePage,
70
125
  />
71
126
 
72
127
  <SideNav navTree={navTree} drawerOpen={drawerOpen} onCloseDrawer={() => setDrawerOpen(false)} {...sideNav} />
73
- <div className="min-w-0">
128
+ <div className="min-w-0" style={plane}>
74
129
  {header}
75
130
  <Outlet />
76
131
  {footer}
@@ -1,5 +1,10 @@
1
- import { Divider } from '@kolkrabbi/kol-component'
1
+ import { Divider, SectionText } from '@kolkrabbi/kol-component'
2
2
 
3
+ /* The chapter section of the brand-book page kit — `PageHero` · `PageSection`
4
+ * · `PageLayout`. Its head COMPOSES `SectionText` (page-family-is-not-a-set,
5
+ * 2026-09-03) instead of hand-stacking label / title / lede: the voices are the
6
+ * same `.kol-prose-*` trio as before, passed as the base's seams, and each class
7
+ * carries its own bottom margin, so the block's gap is 0 and nothing moved. */
3
8
  export default function PageSection({ id, label, title, body, children, className = '', fullbleed = false, divider = false }) {
4
9
  const hasHead = label || title || body
5
10
  const cls = [
@@ -16,9 +21,16 @@ export default function PageSection({ id, label, title, body, children, classNam
16
21
  * exactly the panel token; `720px` was an improvised near-miss and
17
22
  * becomes the column token — 768px, so section heads widen by 48px. */
18
23
  <header className={fullbleed ? 'max-w-[var(--kol-content-panel)]' : 'max-w-[var(--kol-content-column)]'}>
19
- {label && <p className="kol-prose-label">{label}</p>}
20
- {title && <h2 className="kol-prose-title">{title}</h2>}
21
- {body && <p className="kol-prose-lede">{body}</p>}
24
+ <SectionText
25
+ eyebrow={label}
26
+ eyebrowClass="kol-prose-label"
27
+ headline={title}
28
+ headlineAs="h2"
29
+ headlineClass="kol-prose-title"
30
+ body={body}
31
+ bodyClass="kol-prose-lede"
32
+ gap="gap-0"
33
+ />
22
34
  </header>
23
35
  )}
24
36
  {children}
@@ -90,7 +90,9 @@ export default function ThemeToggle({
90
90
  className = '',
91
91
  }) {
92
92
  const { theme, mode, cycle, clear } = useTheme()
93
- const extra = `${tone === 'sunken' || tone === 'inverse' ? 'kol-tone-sunken' : ''} ${className}`.trim()
93
+ /* six tones (tone-is-the-ground-axis, 2026-09-03); unset inherits the wrapper's */
94
+ const TONE_CLASS = { primary: 'kol-tone-primary', secondary: 'kol-tone-secondary', outline: 'kol-tone-outline', ghost: 'kol-tone-ghost', grey: 'kol-tone-grey', sunken: 'kol-tone-sunken', inverse: 'kol-tone-sunken' }
95
+ const extra = `${TONE_CLASS[tone] ?? ''} ${className}`.trim()
94
96
 
95
97
  /* Label and slot follow the RESOLVED theme, not the stored choice: when the
96
98
  * page is unset, what the user is looking at is the OS's answer, and the
package/src/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * @kol/framework — shared KOL app shell.
3
3
  *
4
- * Site chrome (sidenav, layout, theme toggle, footer, heroes) consumed across
4
+ * Site chrome (sidenav, page layout, theme toggle, footer, the page kit) consumed across
5
5
  * apps. SideNav takes its nav data as props (navTree) so the tree stays
6
6
  * app-local. CSS lives in src/styles: kol-framework.css
7
7
  * and kol-brand-color.css.
8
8
  */
9
9
 
10
- export { default as AppShell } from './AppShell.jsx'
11
- export { ShellTocContext, ShellTocCollapsedContext } from './AppShell.jsx'
10
+ export { default as PageLayout, default as AppShell } from './PageLayout.jsx'
11
+ export { ShellTocContext, ShellTocCollapsedContext } from './PageLayout.jsx'
12
12
  export { default as SideNav } from './SideNav.jsx'
13
13
  export { default as useDragResize } from './useDragResize.js'
14
14
  export { default as ShellHeader, HEADER_ICON } from './ShellHeader.jsx'
@@ -18,5 +18,4 @@ export { default as Layout } from './Layout.jsx'
18
18
  export { default as PageSection } from './PageSection.jsx'
19
19
  export { default as PortalFooter } from './PortalFooter.jsx'
20
20
  export { default as ScrollToTop } from './ScrollToTop.jsx'
21
- export { default as BrandHero } from './BrandHero.jsx'
22
- export { default as SubPageHero } from './SubPageHero.jsx'
21
+ export { default as PageHero, default as BrandHero, default as SubPageHero } from './PageHero.jsx'
package/src/BrandHero.jsx DELETED
@@ -1,14 +0,0 @@
1
- export default function BrandHero({ id = 'hero', label, title, lede, mark }) {
2
- return (
3
- <section id={id} className="kol-page-hero">
4
- {label && <p className="kol-prose-label">{label}</p>}
5
- <div className="flex items-center gap-12 flex-wrap">
6
- {mark}
7
- <div className="flex-1 min-w-[280px]">
8
- <h1 className="kol-prose-display">{title}</h1>
9
- {lede && <p className="kol-prose-lede max-w-[60ch]">{lede}</p>}
10
- </div>
11
- </div>
12
- </section>
13
- )
14
- }
@@ -1,27 +0,0 @@
1
- import { Link } from 'react-router-dom'
2
-
3
- export default function SubPageHero({ backTo, backLabel, label, title, lede }) {
4
- return (
5
- <section className="kol-page-hero" id="hero">
6
- {backTo && (
7
- <Link
8
- to={backTo}
9
- className="kol-back-link kol-helper-12 tracking-widest text-body hover:text-emphasis no-underline"
10
- >
11
- {backLabel}
12
- </Link>
13
- )}
14
- {label && (
15
- <p className="kol-helper-12 tracking-widest text-meta m-0 mb-4">
16
- {label}
17
- </p>
18
- )}
19
- <div className="flex-1 min-w-[280px]">
20
- <h1 className="kol-sans-display-01 text-auto m-0 mb-6">{title}</h1>
21
- {lede && (
22
- <p className="kol-sans-body-01 text-body m-0 max-w-[60ch]">{lede}</p>
23
- )}
24
- </div>
25
- </section>
26
- )
27
- }