@kolkrabbi/kol-shell 0.21.0 → 0.23.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 +2 -2
- package/src/SettingsScaffold.jsx +98 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@kolkrabbi/kol-component": "^0.133.0",
|
|
24
|
-
"@kolkrabbi/kol-framework": "^0.35.0",
|
|
25
24
|
"@kolkrabbi/kol-icons": "^0.25.0",
|
|
25
|
+
"@kolkrabbi/kol-framework": "^0.35.0",
|
|
26
26
|
"@kolkrabbi/kol-theme": "^0.97.0"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
package/src/SettingsScaffold.jsx
CHANGED
|
@@ -1,52 +1,122 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { ContentFilters } from '@kolkrabbi/kol-component'
|
|
3
3
|
import PageShell from './PageShell.jsx'
|
|
4
4
|
import PageHeader from './PageHeader.jsx'
|
|
5
|
-
import TabStrip from './TabStrip.jsx'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
* SettingsScaffold — the settings-page idiom both shells re-implemented
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* SettingsScaffold — the settings-page idiom both shells re-implemented inline:
|
|
8
|
+
* fixed PageShell, a PageHeader masthead, one header row, then a scrolling body.
|
|
9
|
+
* Content is consumer-authored via `renderContent` — sections/rows are content,
|
|
10
|
+
* not markup.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* THE HEADER ROW IS `ContentFilters`, THE ACTUAL ORGANISM (user ruling
|
|
13
|
+
* 2026-08-30, taken from kol-fxr's approved `/settings`). It was a local
|
|
14
|
+
* TabStrip + Divider, which made this a THIRD header shape in the estate beside
|
|
15
|
+
* PageHeader and ContentFilters — on the one page type every app has. fxr hand-
|
|
16
|
+
* wrote a lookalike to the organism's grammar and it drifted inside the hour:
|
|
17
|
+
* the organism's search is `size="md" iconSize={16} fieldHeight={28}`, and a
|
|
18
|
+
* copy passing none of those renders a different pill in the same row one page
|
|
19
|
+
* over. The user caught it as *"2 different content filters in settings and
|
|
20
|
+
* effexor home why"*.
|
|
21
|
+
*
|
|
22
|
+
* A row that looks like ContentFilters must BE ContentFilters.
|
|
23
|
+
*
|
|
24
|
+
* What that buys over the tab strip it replaces: search across the settings rows
|
|
25
|
+
* (`searchKeys` — a row's own label matches, so `loop`, `aspect`, `undo` all
|
|
26
|
+
* land), filter groups, the view strip, and `trailingActions` for a page's own
|
|
27
|
+
* controls. A TabStrip could carry none of it.
|
|
28
|
+
*
|
|
29
|
+
* Body building blocks live in **kol-component**, not here:
|
|
30
|
+
* `LabeledControlSection` (the eyebrow-headed section) and `SettingsRow` (the
|
|
31
|
+
* 160px label column). This file used to export its own `SettingsSection` +
|
|
32
|
+
* `LabelRow` — a second implementation of that pair, and the DS does not ship
|
|
33
|
+
* two of one thing (user ruling 2026-08-30: "dont ship duplicate components").
|
|
34
|
+
* Both retired to `_tmp/2026-08-30-shell-settings-duplicates/`.
|
|
35
|
+
*
|
|
36
|
+
* The DRAWER is the page's other half and is also kol-component's:
|
|
37
|
+
* `SettingsPanel` (ShellDrawer underneath — scrim, Escape, focus trap, scroll
|
|
38
|
+
* lock). Render page and drawer from ONE definition; that is what stops the two
|
|
39
|
+
* surfaces drifting. Put its opener in `header.actions`.
|
|
19
40
|
*
|
|
20
41
|
* Shortcuts single-source: both repos hand-maintained the shortcut list twice
|
|
21
42
|
* (settings + overlay) and both pairs drifted. Feed ONE array to both your
|
|
22
43
|
* `ShortcutsOverlay` and `SettingsShortcuts`.
|
|
23
44
|
*
|
|
24
|
-
* @param {Array}
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {
|
|
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
|
+
* @param {string} props.defaultTab
|
|
47
|
+
* @param {Function} props.renderContent `(tabValue, filteredItems) => node` — the scrolling body
|
|
48
|
+
* @param {Object} props.header PageHeader props spread onto the masthead (`voice`, `size`, `titleClass`, `eyebrow`, `actions`)
|
|
49
|
+
* @param {string} props.title the filter row's own title (default `Preferences`)
|
|
50
|
+
* @param {Array} props.items rows to search/filter over; omit and the row is chrome only
|
|
51
|
+
* @param {Array} props.filterGroups ContentFilters groups
|
|
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
|
|
54
|
+
* @param {string} props.tone forwarded to ContentFilters (`sunken` is fxr's approved page)
|
|
55
|
+
* @param {Object} props.filtersProps escape hatch — anything else the organism takes
|
|
27
56
|
*/
|
|
28
|
-
export default function SettingsScaffold({
|
|
57
|
+
export default function SettingsScaffold({
|
|
58
|
+
tabs = [],
|
|
59
|
+
defaultTab,
|
|
60
|
+
renderContent,
|
|
61
|
+
header,
|
|
62
|
+
title = 'Preferences',
|
|
63
|
+
items,
|
|
64
|
+
filterGroups,
|
|
65
|
+
searchKeys,
|
|
66
|
+
trailingActions,
|
|
67
|
+
tone = 'sunken',
|
|
68
|
+
filtersProps,
|
|
69
|
+
}) {
|
|
29
70
|
const [tab, setTab] = useState(defaultTab ?? tabs[0]?.value)
|
|
30
71
|
const active = tabs.find((t) => t.value === tab)
|
|
31
72
|
|
|
73
|
+
/* ONE PAGE SELECTOR DRAWN ACROSS TWO ROWS (SettingsScaffoldTabRows, kol-fxr
|
|
74
|
+
* 2026-08-30). fxr's SETTINGS above the rule and ABOUT / REPO below it are not
|
|
75
|
+
* two selectors — they are one set of destinations, split because the user
|
|
76
|
+
* ruled the side pages down to the smaller row. `row: 'layout'` says which.
|
|
77
|
+
*
|
|
78
|
+
* Both strips read and write the SAME `tab`, which is the whole point: before
|
|
79
|
+
* this, a page wanting both rows had to hand ContentFilters its own state
|
|
80
|
+
* through `filtersProps` and the scaffold's `tab` went dead — so
|
|
81
|
+
* `renderContent`'s first argument was a lie, and the masthead only got the
|
|
82
|
+
* right title because `header` happens to spread after it. */
|
|
83
|
+
const viewTabs = tabs.filter((t) => t.row !== 'layout')
|
|
84
|
+
const layoutTabs = tabs.filter((t) => t.row === 'layout')
|
|
85
|
+
|
|
32
86
|
return (
|
|
33
87
|
<PageShell mode="fixed">
|
|
34
88
|
{/* `header` is spread onto the PageHeader (PageHeaderMonoTitle addendum,
|
|
35
89
|
* kol-fxr 2026-08-27): a consumer could not reach this title at all —
|
|
36
|
-
* `voice="mono"`, `size`, `titleClass`, `eyebrow` all pass through
|
|
90
|
+
* `voice="mono"`, `size`, `titleClass`, `eyebrow` all pass through.
|
|
91
|
+
* `actions` is where the drawer opener goes. */}
|
|
37
92
|
<PageHeader title={active?.title} subtitle={active?.subtitle} {...header} />
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
93
|
+
<ContentFilters
|
|
94
|
+
tone={tone}
|
|
95
|
+
title={title}
|
|
96
|
+
items={items ?? []}
|
|
97
|
+
totalCount={items?.length ?? 0}
|
|
98
|
+
filterGroups={filterGroups}
|
|
99
|
+
searchKeys={searchKeys}
|
|
100
|
+
trailingActions={trailingActions}
|
|
101
|
+
/* THE TABS ARE THE VIEW STRIP — the same row fxr's SETTINGS and the home
|
|
102
|
+
* page's RECENT / SAVED run on, not a strip of our own. That is the
|
|
103
|
+
* whole point: one row grammar, not a settings dialect of it. */
|
|
104
|
+
viewModeOptions={viewTabs.length ? viewTabs : undefined}
|
|
105
|
+
viewMode={tab}
|
|
106
|
+
onViewModeChange={setTab}
|
|
107
|
+
/* a strip whose options do not contain the active tab simply shows
|
|
108
|
+
* nothing lit — which is correct: on ABOUT, the SETTINGS strip has no
|
|
109
|
+
* selection to draw. */
|
|
110
|
+
layoutOptions={layoutTabs.length ? layoutTabs : undefined}
|
|
111
|
+
layout={tab}
|
|
112
|
+
onLayoutChange={setTab}
|
|
113
|
+
renderItem={(filtered) => (
|
|
114
|
+
<div style={{ flex: 1, overflow: 'auto', paddingTop: 4, paddingBottom: 4 }}>
|
|
115
|
+
{renderContent?.(tab, filtered)}
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
{...filtersProps}
|
|
44
119
|
/>
|
|
45
|
-
<Divider className="mb-6" />
|
|
46
|
-
<div style={{ flex: 1, overflow: 'auto', paddingTop: 4, paddingBottom: 4 }}>
|
|
47
|
-
{renderContent?.(tab)}
|
|
48
|
-
</div>
|
|
49
120
|
</PageShell>
|
|
50
121
|
)
|
|
51
122
|
}
|
|
52
|
-
|