@kolkrabbi/kol-shell 0.1.2 → 0.2.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 +1 -1
- package/src/ContentFilters.jsx +61 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
package/src/ContentFilters.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo, useRef, useEffect } from 'react'
|
|
1
|
+
import { Fragment, useState, useMemo, useRef, useEffect } from 'react'
|
|
2
2
|
import { Divider } from '@kolkrabbi/kol-component'
|
|
3
3
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
4
4
|
import TabStrip from './TabStrip.jsx'
|
|
@@ -27,6 +27,22 @@ import TabStrip from './TabStrip.jsx'
|
|
|
27
27
|
* @param {Array} props.mutuallyExclusiveFilters - filter keys that self-clear
|
|
28
28
|
* @param {Array} props.customFilterKeys - keys renderItem handles, not this organism
|
|
29
29
|
* @param {ElementType} props.iconComponent - icon seam (defaults to DS Icon; needs `filter` + `search`)
|
|
30
|
+
* @param {Function} props.renderFilterValue - `(value, isActive, toggle) => node`, one filter value
|
|
31
|
+
* @param {string} props.labelClassName - REPLACES the group label's class (never stacks)
|
|
32
|
+
*
|
|
33
|
+
* **The two rendering seams (0.2.0).** How a filter value and a group label look
|
|
34
|
+
* is the consumer's call, not this organism's. Three publishes in one day went
|
|
35
|
+
* into re-ruling that here — outlined pills vs bare strip items vs chips — none
|
|
36
|
+
* of which is a design-system question: it is what a given app's filter bar
|
|
37
|
+
* should look like. `renderFilterValue` and `labelClassName` end that class of
|
|
38
|
+
* change without a publish.
|
|
39
|
+
*
|
|
40
|
+
* `labelClassName` REPLACES rather than stacks, per the 2026-07-30 law: two
|
|
41
|
+
* equal-specificity type classes on one element are decided by sheet order, so
|
|
42
|
+
* a stacked seam produces different renders in different consumers with no
|
|
43
|
+
* version difference. Same contract as ListingCard's `titleClassName`.
|
|
44
|
+
*
|
|
45
|
+
* Defaults reproduce 0.1.3 exactly — a bump moves nothing.
|
|
30
46
|
*/
|
|
31
47
|
const ContentFilters = ({
|
|
32
48
|
items,
|
|
@@ -47,6 +63,8 @@ const ContentFilters = ({
|
|
|
47
63
|
headerActions,
|
|
48
64
|
showCountOnlyWhenFiltering = false,
|
|
49
65
|
iconComponent,
|
|
66
|
+
renderFilterValue,
|
|
67
|
+
labelClassName,
|
|
50
68
|
}) => {
|
|
51
69
|
const [activeFilters, setActiveFilters] = useState(new Set())
|
|
52
70
|
const [isExpanded, setIsExpanded] = useState(false)
|
|
@@ -132,9 +150,10 @@ const ContentFilters = ({
|
|
|
132
150
|
})
|
|
133
151
|
}, [items, activeFilters, customFilterKeys, searchText, searchKeys])
|
|
134
152
|
|
|
135
|
-
//
|
|
136
|
-
// 2026-08-15
|
|
137
|
-
//
|
|
153
|
+
// A group is a COLUMN: label on top, values beneath it (user ruling
|
|
154
|
+
// 2026-08-15). How a value and a label RENDER is the consumer's — three
|
|
155
|
+
// publishes in one day went into re-deciding that here, which is the defect
|
|
156
|
+
// the seams below end. Defaults are 0.1.3 exactly, so nothing moves on bump.
|
|
138
157
|
const renderFilterGroup = (group) => {
|
|
139
158
|
const prefix = `${group.key}:`
|
|
140
159
|
const selected = new Set(
|
|
@@ -144,16 +163,31 @@ const ContentFilters = ({
|
|
|
144
163
|
)
|
|
145
164
|
|
|
146
165
|
return (
|
|
147
|
-
<div key={group.key} className="flex
|
|
148
|
-
<h4
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
className="gap-4"
|
|
156
|
-
|
|
166
|
+
<div key={group.key} className="flex flex-col gap-3">
|
|
167
|
+
<h4
|
|
168
|
+
className={labelClassName || 'kol-helper-12 text-fg-32'}
|
|
169
|
+
style={labelClassName ? undefined : { letterSpacing: 1 }}
|
|
170
|
+
>
|
|
171
|
+
{group.label}
|
|
172
|
+
</h4>
|
|
173
|
+
{renderFilterValue ? (
|
|
174
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
175
|
+
{group.values.map((v) => (
|
|
176
|
+
<Fragment key={v}>
|
|
177
|
+
{renderFilterValue(v, selected.has(v), () => toggleFilter(group.key, v))}
|
|
178
|
+
</Fragment>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
) : (
|
|
182
|
+
<TabStrip
|
|
183
|
+
options={group.values.map((v) => ({ value: v, label: v }))}
|
|
184
|
+
value={selected}
|
|
185
|
+
onChange={(v) => toggleFilter(group.key, v)}
|
|
186
|
+
size={12}
|
|
187
|
+
tracked
|
|
188
|
+
className="gap-4 flex-wrap"
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
157
191
|
</div>
|
|
158
192
|
)
|
|
159
193
|
}
|
|
@@ -238,19 +272,25 @@ const ContentFilters = ({
|
|
|
238
272
|
|
|
239
273
|
<Divider className="mb-4" />
|
|
240
274
|
|
|
241
|
-
{/*
|
|
242
|
-
filter toggle is open), layout strip
|
|
243
|
-
|
|
244
|
-
|
|
275
|
+
{/* BELOW the divider: filter groups as left-aligned COLUMNS (only while
|
|
276
|
+
the filter toggle is open), layout strip RIGHT and ALWAYS visible.
|
|
277
|
+
`items-start` is load-bearing — it pins the strip to the label row, so
|
|
278
|
+
the strip and the group labels read as one line and the values hang
|
|
279
|
+
beneath. 0.1.1 read "at the divider level" as the header row above the
|
|
280
|
+
divider; the strip never belongs in the header
|
|
245
281
|
(ShellHeaderFilterRefinements, 2026-08-15). */}
|
|
246
282
|
{(layoutOptions || isExpanded) && (
|
|
247
|
-
<div className="flex items-
|
|
248
|
-
<div className="flex items-
|
|
283
|
+
<div className="flex items-start justify-between gap-16 mb-4">
|
|
284
|
+
<div className="flex items-start gap-16">
|
|
249
285
|
{isExpanded && filterGroups.map((group) => renderFilterGroup(group))}
|
|
250
286
|
{isExpanded && activeFilters.size > 0 && (
|
|
287
|
+
// Strip rest recipe, not its own ink — this button shares the line
|
|
288
|
+
// with the labels and values, and that line carries exactly TWO
|
|
289
|
+
// ink states (user ruling 2026-08-15: "we are not maintaining 3
|
|
290
|
+
// opacity states for one line in a component").
|
|
251
291
|
<button
|
|
252
292
|
onClick={clearAllFilters}
|
|
253
|
-
className="kol-helper-12 transition-colors underline text-fg-48"
|
|
293
|
+
className="kol-helper-12 transition-colors underline text-fg-32 hover:text-fg-48"
|
|
254
294
|
>
|
|
255
295
|
Clear all ({activeFilters.size})
|
|
256
296
|
</button>
|