@kolkrabbi/kol-component 0.162.0 → 0.163.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-component",
3
- "version": "0.162.0",
3
+ "version": "0.163.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)
@@ -90,6 +94,7 @@ const ContentFilters = ({
90
94
  * "N of N" is below the divider in BOTH — it is a count of what the filters
91
95
  * did, so it belongs with them, and it only renders while they are open. */
92
96
  layoutPlacement = 'below',
97
+ viewPlacement = 'auto',
93
98
  onFilterChange,
94
99
  mutuallyExclusiveFilters = [],
95
100
  customFilterKeys = [],
@@ -302,6 +307,22 @@ const ContentFilters = ({
302
307
  )
303
308
  }
304
309
 
310
+ /* RECENT / SAVED — one node, two homes (header from md, its own line below) */
311
+ const viewStrip = (wrapCls) => (
312
+ <div className={`${wrapCls} items-center gap-4`}>
313
+ {viewModeOptions.map((opt) => (
314
+ <span
315
+ key={opt.value}
316
+ onClick={() => handleViewModeChange(opt.value)}
317
+ className={`${viewClassName} cursor-pointer select-none ${viewMode === opt.value ? stripActiveClassName : stripRestClassName}`}
318
+ style={{ textTransform: 'uppercase', letterSpacing: 1 }}
319
+ >
320
+ {opt.label}
321
+ </span>
322
+ ))}
323
+ </div>
324
+ )
325
+
305
326
  return (
306
327
  /* minHeight 0 on both this root and the body below: without it a flex
307
328
  * child refuses to shrink past its content, so a scrollable body pushed
@@ -426,20 +447,11 @@ const ContentFilters = ({
426
447
  * This REPLACES the filled-chip reading of the 2026-07-28 ruling on
427
448
  * this surface — user ruling 2026-08-15: the fork's look, everywhere.
428
449
  * One family across the whole row. */}
429
- {viewModeOptions && (
430
- <div className="flex items-center gap-4">
431
- {viewModeOptions.map((opt) => (
432
- <span
433
- key={opt.value}
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
- )}
450
+ {/* THE STRIP HAS A NARROW RUNG (ContentFiltersViewStripOverflow,
451
+ * kol-mirror 2026-09-02): in the header it is `hidden md:flex` under
452
+ * `auto`, and the same options render on their own line under the
453
+ * divider below `md` — full width, wrapping. */}
454
+ {viewModeOptions && viewPlacement !== 'below' && viewStrip(viewPlacement === 'auto' ? 'hidden md:flex' : 'flex')}
443
455
  </div>
444
456
  </div>
445
457
 
@@ -447,6 +459,14 @@ const ContentFilters = ({
447
459
  * dropped, which is how this margin nearly went missing. */}
448
460
  <Divider className="mb-3" />
449
461
 
462
+ {/* the view family's own line below md (`auto`) or always (`below`) —
463
+ * full width, wrapping, so no view is ever off the page */}
464
+ {viewModeOptions && viewPlacement !== 'header' && (
465
+ <div className={`${viewPlacement === 'auto' ? 'md:hidden ' : ''}mb-3`}>
466
+ {viewStrip('flex flex-wrap gap-y-2')}
467
+ </div>
468
+ )}
469
+
450
470
  {/* BELOW the divider: the filter GROUPS only. Left-aligned columns —
451
471
  * label above values — visible only while the filter toggle is open.
452
472
  *