@kolkrabbi/kol-shell 0.22.0 → 0.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-shell",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "private": false,
5
5
  "description": "KOL application shell — fixed 48px NavRail + AppShell layout root, PageShell/PageHeader scaffolds, ContentFilters catalog organism, GridCard, SettingsScaffold, WalkthroughPanel, ShortcutsOverlay. App chrome (kol-framework owns site chrome). Nav items, content, shortcuts and settings are consumer-injected. Sits above @kolkrabbi/kol-{theme,component,framework}.",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@ import PageHeader from './PageHeader.jsx'
42
42
  * (settings + overlay) and both pairs drifted. Feed ONE array to both your
43
43
  * `ShortcutsOverlay` and `SettingsShortcuts`.
44
44
  *
45
- * @param {Array} props.tabs `[{ value, label, title, subtitle }]` — title/subtitle feed the masthead, label the strip
45
+ * @param {Array} props.tabs `[{ value, label, title, subtitle, row }]` — title/subtitle feed the masthead, label the strip. `row: 'layout'` puts an entry on the SECOND row (fxr's ABOUT / REPO); default is the view strip
46
46
  * @param {string} props.defaultTab
47
47
  * @param {Function} props.renderContent `(tabValue, filteredItems) => node` — the scrolling body
48
48
  * @param {Object} props.header PageHeader props spread onto the masthead (`voice`, `size`, `titleClass`, `eyebrow`, `actions`)
@@ -50,7 +50,9 @@ import PageHeader from './PageHeader.jsx'
50
50
  * @param {Array} props.items rows to search/filter over; omit and the row is chrome only
51
51
  * @param {Array} props.filterGroups ContentFilters groups
52
52
  * @param {Array} props.searchKeys which keys `items` are searched on
53
- * @param {ReactNode} props.trailingActions the page's own controls, right of the row
53
+ * @param {ReactNode|Function} props.trailingActions the page's own controls, right of the row.
54
+ * A FUNCTION receives `(tab, setTab)` — for a control that has to
55
+ * move the page, e.g. an icon pair that jumps back to Settings
54
56
  * @param {string} props.tone forwarded to ContentFilters (`sunken` is fxr's approved page)
55
57
  * @param {Object} props.filtersProps escape hatch — anything else the organism takes
56
58
  */
@@ -70,6 +72,19 @@ export default function SettingsScaffold({
70
72
  const [tab, setTab] = useState(defaultTab ?? tabs[0]?.value)
71
73
  const active = tabs.find((t) => t.value === tab)
72
74
 
75
+ /* ONE PAGE SELECTOR DRAWN ACROSS TWO ROWS (SettingsScaffoldTabRows, kol-fxr
76
+ * 2026-08-30). fxr's SETTINGS above the rule and ABOUT / REPO below it are not
77
+ * two selectors — they are one set of destinations, split because the user
78
+ * ruled the side pages down to the smaller row. `row: 'layout'` says which.
79
+ *
80
+ * Both strips read and write the SAME `tab`, which is the whole point: before
81
+ * this, a page wanting both rows had to hand ContentFilters its own state
82
+ * through `filtersProps` and the scaffold's `tab` went dead — so
83
+ * `renderContent`'s first argument was a lie, and the masthead only got the
84
+ * right title because `header` happens to spread after it. */
85
+ const viewTabs = tabs.filter((t) => t.row !== 'layout')
86
+ const layoutTabs = tabs.filter((t) => t.row === 'layout')
87
+
73
88
  return (
74
89
  <PageShell mode="fixed">
75
90
  {/* `header` is spread onto the PageHeader (PageHeaderMonoTitle addendum,
@@ -84,13 +99,31 @@ export default function SettingsScaffold({
84
99
  totalCount={items?.length ?? 0}
85
100
  filterGroups={filterGroups}
86
101
  searchKeys={searchKeys}
87
- trailingActions={trailingActions}
102
+ /* A NODE cannot reach the tab it sits beside. fxr's OPTIONS /
103
+ * SHORTCUTS pair stays visible on About and Repo (user 2026-08-28 — the
104
+ * row is the page's furniture, and hiding it made the header jump a
105
+ * line on every leave), and picking either one must return you to
106
+ * Settings. Owning `tab` here took that away: the pair changed the
107
+ * settings view while you sat on About and you had to click SETTINGS
108
+ * yourself (SettingsScaffoldTabFromTrailing, 2026-08-30).
109
+ *
110
+ * A render prop, not a controlled `tab`/`onTabChange` pair: the
111
+ * controlled shape hands page state back to the consumer, which is
112
+ * precisely what 0.23.0 removed. This hands out a setter and leaves the
113
+ * state where the last ticket put it. A plain node still works. */
114
+ trailingActions={typeof trailingActions === 'function' ? trailingActions(tab, setTab) : trailingActions}
88
115
  /* THE TABS ARE THE VIEW STRIP — the same row fxr's SETTINGS and the home
89
116
  * page's RECENT / SAVED run on, not a strip of our own. That is the
90
117
  * whole point: one row grammar, not a settings dialect of it. */
91
- viewModeOptions={tabs.length ? tabs : undefined}
118
+ viewModeOptions={viewTabs.length ? viewTabs : undefined}
92
119
  viewMode={tab}
93
120
  onViewModeChange={setTab}
121
+ /* a strip whose options do not contain the active tab simply shows
122
+ * nothing lit — which is correct: on ABOUT, the SETTINGS strip has no
123
+ * selection to draw. */
124
+ layoutOptions={layoutTabs.length ? layoutTabs : undefined}
125
+ layout={tab}
126
+ onLayoutChange={setTab}
94
127
  renderItem={(filtered) => (
95
128
  <div style={{ flex: 1, overflow: 'auto', paddingTop: 4, paddingBottom: 4 }}>
96
129
  {renderContent?.(tab, filtered)}