@kolkrabbi/kol-component 0.162.0 → 0.164.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/organisms/ContentFilters.jsx +67 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.164.0",
|
|
4
4
|
"description": "KOL design-system components — atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,6 +21,10 @@ import IconFrame from '../atoms/IconFrame.jsx'
|
|
|
21
21
|
* @param {Array} props.filterGroups — [{label, key, values}, ...]
|
|
22
22
|
* @param {Function} props.renderItem — (filteredItems, viewMode, layout) => ReactNode
|
|
23
23
|
* @param {Array} props.viewModeOptions — optional view mode options for the view strip
|
|
24
|
+
* @param {'auto'|'header'|'below'} props.viewPlacement — WHERE the RECENT/SAVED strip sits (ContentFiltersViewStripOverflow,
|
|
25
|
+
* kol-mirror 2026-09-02): `auto` (default) rides the header row from `md` and takes its OWN line under the divider
|
|
26
|
+
* below it, full width and wrapping — five views at 390 were 440px in a 390px page, two cut off and unreachable;
|
|
27
|
+
* `header` always the header row; `below` always its own line. Same family, same rung as LIST/GRID's drop.
|
|
24
28
|
* @param {string} props.defaultViewMode — default view mode (falls back to the FIRST option)
|
|
25
29
|
* @param {string} props.layout — controlled LIST/GRID value (kol-r2b2 2026-08-27: a consumer that persists
|
|
26
30
|
* layout per bucket needs the strip's value back; `defaultLayout` alone kept it internal)
|
|
@@ -30,6 +34,10 @@ import IconFrame from '../atoms/IconFrame.jsx'
|
|
|
30
34
|
* @param {ReactNode} props.belowActions — the RIGHT half of the below-divider row, beside the count
|
|
31
35
|
* (kol-r2b2 2026-08-27: the sort group, once SELECT/FLAT moved up into the header strip)
|
|
32
36
|
* @param {ReactNode} props.trailingActions — the header's RIGHT slot, where the view strip sits
|
|
37
|
+
* @param {'auto'|'header'|'below'} props.trailingPlacement — WHERE `trailingActions` sit (the showcase's icons page,
|
|
38
|
+
* 2026-09-02: brand's ground + guide cluster in this slot measured 223px and scrolled `main` sideways at 390):
|
|
39
|
+
* `auto` (default) the header from `md`, their OWN line under the divider below it — full width, wrapping; `header` / `below` pin it.
|
|
40
|
+
* The same rung `viewPlacement` and LIST/GRID have.
|
|
33
41
|
* (kol-r2b2 2026-08-27: a consumer's own controls — sort, flat, select — belong there;
|
|
34
42
|
* `headerActions` is the left group beside search and was never that)
|
|
35
43
|
* @param {Function} props.onFilterChange — optional callback when filters change
|
|
@@ -90,12 +98,14 @@ const ContentFilters = ({
|
|
|
90
98
|
* "N of N" is below the divider in BOTH — it is a count of what the filters
|
|
91
99
|
* did, so it belongs with them, and it only renders while they are open. */
|
|
92
100
|
layoutPlacement = 'below',
|
|
101
|
+
viewPlacement = 'auto',
|
|
93
102
|
onFilterChange,
|
|
94
103
|
mutuallyExclusiveFilters = [],
|
|
95
104
|
customFilterKeys = [],
|
|
96
105
|
searchKeys = ['label', 'name', 'title', 'type'],
|
|
97
106
|
headerActions,
|
|
98
107
|
trailingActions,
|
|
108
|
+
trailingPlacement = 'auto',
|
|
99
109
|
leadingActions,
|
|
100
110
|
belowActions,
|
|
101
111
|
showCountOnlyWhenFiltering = false,
|
|
@@ -302,6 +312,22 @@ const ContentFilters = ({
|
|
|
302
312
|
)
|
|
303
313
|
}
|
|
304
314
|
|
|
315
|
+
/* RECENT / SAVED — one node, two homes (header from md, its own line below) */
|
|
316
|
+
const viewStrip = (wrapCls) => (
|
|
317
|
+
<div className={`${wrapCls} items-center gap-4`}>
|
|
318
|
+
{viewModeOptions.map((opt) => (
|
|
319
|
+
<span
|
|
320
|
+
key={opt.value}
|
|
321
|
+
onClick={() => handleViewModeChange(opt.value)}
|
|
322
|
+
className={`${viewClassName} cursor-pointer select-none ${viewMode === opt.value ? stripActiveClassName : stripRestClassName}`}
|
|
323
|
+
style={{ textTransform: 'uppercase', letterSpacing: 1 }}
|
|
324
|
+
>
|
|
325
|
+
{opt.label}
|
|
326
|
+
</span>
|
|
327
|
+
))}
|
|
328
|
+
</div>
|
|
329
|
+
)
|
|
330
|
+
|
|
305
331
|
return (
|
|
306
332
|
/* minHeight 0 on both this root and the body below: without it a flex
|
|
307
333
|
* child refuses to shrink past its content, so a scrollable body pushed
|
|
@@ -325,7 +351,7 @@ const ContentFilters = ({
|
|
|
325
351
|
* for exactly this (lobby ruling 2026-07-30 — "icons only, NO
|
|
326
352
|
* states"); the span here was the same defect that promoted it. */}
|
|
327
353
|
{/* the icon gap matches the specimen header — 16 from md (FoundrySpecimenSections, 2026-08-27) */}
|
|
328
|
-
<h2 className=
|
|
354
|
+
<h2 className={`flex items-center gap-2 md:gap-4 ${searchOpen ? 'max-md:hidden' : ''}`}>
|
|
329
355
|
{titleIcon && <IconFrame name={titleIcon} variant="secondary" size="md" />}
|
|
330
356
|
{/* `pr-4` by rule (ContentFiltersTitleGap, kol-website 2026-08-27): the
|
|
331
357
|
* divider sat 24px from the title's text edge but 32 from the
|
|
@@ -338,8 +364,13 @@ const ContentFilters = ({
|
|
|
338
364
|
* spending 40px of a 390px viewport on one divider. */}
|
|
339
365
|
<span className={`${titleClassName} pr-2 md:pr-4`} style={titleUppercase ? { textTransform: 'uppercase', letterSpacing: 1 } : undefined}>{title}</span>
|
|
340
366
|
</h2>
|
|
341
|
-
<Divider variant="vertical" />
|
|
342
|
-
|
|
367
|
+
<Divider variant="vertical" className={searchOpen ? 'max-md:hidden' : ''} />
|
|
368
|
+
{/* THE OPEN SEARCH TAKES THE ROW BELOW `md` (2026-09-02, the showcase
|
|
369
|
+
* held to its own law): the pill is a fixed 200px, and beside the
|
|
370
|
+
* title at 390 it scrolled `main` sideways. Title and divider step
|
|
371
|
+
* aside while searching and return on close; the group grows to the
|
|
372
|
+
* row so the pill fits. */}
|
|
373
|
+
<div className="flex items-center gap-1 min-w-0 max-md:flex-1">
|
|
343
374
|
{/* IconFrame, not a kol-btn with its chrome cancelled inline.
|
|
344
375
|
* 05-control-chrome.md:109 — "any icon-only control in chrome is
|
|
345
376
|
* IconFrame; nothing hand-writes the square". This wore
|
|
@@ -384,6 +415,7 @@ const ContentFilters = ({
|
|
|
384
415
|
value={searchText}
|
|
385
416
|
onChange={(e) => setSearchText(e.target.value)}
|
|
386
417
|
expandedWidth={200}
|
|
418
|
+
className="max-w-full"
|
|
387
419
|
triggerLabel="Search"
|
|
388
420
|
/* NO placeholder. The field opens from a glyph you just clicked —
|
|
389
421
|
* the caret is the affordance, and a greyed "Search…" sitting in
|
|
@@ -412,10 +444,17 @@ const ContentFilters = ({
|
|
|
412
444
|
</div>
|
|
413
445
|
|
|
414
446
|
<div className="flex items-center gap-6">
|
|
415
|
-
{
|
|
416
|
-
|
|
417
|
-
* the
|
|
418
|
-
{trailingActions &&
|
|
447
|
+
{/* the consumer's trailing controls ride the header from `md` under
|
|
448
|
+
* `auto`; below it they move to the below-divider row (same rung as
|
|
449
|
+
* the view strip and LIST/GRID) so the header row never scrolls */}
|
|
450
|
+
{trailingActions && trailingPlacement !== 'below' && (
|
|
451
|
+
<div className={`${trailingPlacement === 'auto' ? 'hidden md:flex' : 'flex'} items-center gap-6`}>
|
|
452
|
+
{trailingActions}
|
|
453
|
+
{/* The divider between a consumer's trailing controls and the strip is
|
|
454
|
+
* the organism's, as it is on the left between title and icons. */}
|
|
455
|
+
{layoutPlacement === 'header' && layoutStrip && <Divider variant="vertical" />}
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
419
458
|
{layoutPlacement === 'header' && layoutStrip}
|
|
420
459
|
{/* RECENT / SAVED is the SAME STRIP as LIST / GRID, not a ViewToggle.
|
|
421
460
|
* Read off kol-monitor's original (_tmp/2026-08-15-shell-adoption/
|
|
@@ -426,20 +465,11 @@ const ContentFilters = ({
|
|
|
426
465
|
* This REPLACES the filled-chip reading of the 2026-07-28 ruling on
|
|
427
466
|
* this surface — user ruling 2026-08-15: the fork's look, everywhere.
|
|
428
467
|
* One family across the whole row. */}
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
onClick={() => handleViewModeChange(opt.value)}
|
|
435
|
-
className={`${viewClassName} cursor-pointer select-none ${viewMode === opt.value ? stripActiveClassName : stripRestClassName}`}
|
|
436
|
-
style={{ textTransform: 'uppercase', letterSpacing: 1 }}
|
|
437
|
-
>
|
|
438
|
-
{opt.label}
|
|
439
|
-
</span>
|
|
440
|
-
))}
|
|
441
|
-
</div>
|
|
442
|
-
)}
|
|
468
|
+
{/* THE STRIP HAS A NARROW RUNG (ContentFiltersViewStripOverflow,
|
|
469
|
+
* kol-mirror 2026-09-02): in the header it is `hidden md:flex` under
|
|
470
|
+
* `auto`, and the same options render on their own line under the
|
|
471
|
+
* divider below `md` — full width, wrapping. */}
|
|
472
|
+
{viewModeOptions && viewPlacement !== 'below' && viewStrip(viewPlacement === 'auto' ? 'hidden md:flex' : 'flex')}
|
|
443
473
|
</div>
|
|
444
474
|
</div>
|
|
445
475
|
|
|
@@ -447,6 +477,22 @@ const ContentFilters = ({
|
|
|
447
477
|
* dropped, which is how this margin nearly went missing. */}
|
|
448
478
|
<Divider className="mb-3" />
|
|
449
479
|
|
|
480
|
+
{/* the view family's own line below md (`auto`) or always (`below`) —
|
|
481
|
+
* full width, wrapping, so no view is ever off the page */}
|
|
482
|
+
{viewModeOptions && viewPlacement !== 'header' && (
|
|
483
|
+
<div className={`${viewPlacement === 'auto' ? 'md:hidden ' : ''}mb-3`}>
|
|
484
|
+
{viewStrip('flex flex-wrap gap-y-2')}
|
|
485
|
+
</div>
|
|
486
|
+
)}
|
|
487
|
+
{/* the consumer's trailing controls: their OWN line below md (`auto`) or
|
|
488
|
+
* always (`below`) — full width, wrapping. In the below row's right
|
|
489
|
+
* group they measured 223px beside LIST/GRID in a 342px track. */}
|
|
490
|
+
{trailingActions && trailingPlacement !== 'header' && (
|
|
491
|
+
<div className={`${trailingPlacement === 'auto' ? 'md:hidden ' : ''}mb-3 flex flex-wrap items-center gap-6 gap-y-2`}>
|
|
492
|
+
{trailingActions}
|
|
493
|
+
</div>
|
|
494
|
+
)}
|
|
495
|
+
|
|
450
496
|
{/* BELOW the divider: the filter GROUPS only. Left-aligned columns —
|
|
451
497
|
* label above values — visible only while the filter toggle is open.
|
|
452
498
|
*
|