@kolkrabbi/kol-component 0.54.0 → 0.56.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.54.0",
3
+ "version": "0.56.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",
@@ -3,6 +3,7 @@ import Tag from '../atoms/Tag.jsx'
3
3
  import Divider from '../atoms/Divider.jsx'
4
4
  import Button from '../atoms/Button.jsx'
5
5
  import { Icon } from '@kolkrabbi/kol-icons'
6
+ import { glyphSize } from '../hooks/glyphLadders.js'
6
7
  import IconFrame from '../atoms/IconFrame.jsx'
7
8
 
8
9
  /**
@@ -171,8 +172,13 @@ const ContentFilters = ({
171
172
  * as the component tier's ONE documented exception to the no-casing law,
172
173
  * because a filter value is data with no authoring site. Consumers must not
173
174
  * uppercase these themselves. */
175
+ /* A group either STACKS its values in a narrow column or WRAPS them across
176
+ * the room it is given (`group.stack`). Both shapes are live on kol-website's
177
+ * /work: a short closed set like Type reads as a column you scan down, while
178
+ * ~45 tags must wrap or they run off the page. One shape for both meant the
179
+ * short group ate a full row it did not need. */
174
180
  const renderFilterGroup = (group) => (
175
- <div key={group.key} className="flex flex-col gap-3">
181
+ <div key={group.key} className={`flex flex-col gap-3 ${group.stack ? 'shrink-0' : 'min-w-0 flex-1'}`}>
176
182
  {/* THE CATEGORY LABEL IS THE ACTIVE INK — `kol-helper-12` at `text-fg-96`,
177
183
  * the same full opacity a SELECTED layout item wears (user ruling
178
184
  * 2026-08-15: "TAGS and other categories are ACTIVE state full opacity").
@@ -186,7 +192,7 @@ const ContentFilters = ({
186
192
  <h4 className={labelClassName} style={labelUppercase ? { textTransform: 'uppercase' } : undefined}>
187
193
  {group.label}
188
194
  </h4>
189
- <div className="flex flex-wrap gap-4">
195
+ <div className={group.stack ? 'flex flex-col items-start gap-2' : 'flex flex-wrap gap-2'}>
190
196
  {group.values.map((value) => (
191
197
  <Tag
192
198
  key={value}
@@ -228,7 +234,11 @@ const ContentFilters = ({
228
234
  aria-label="Toggle filters"
229
235
  style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: 'inherit' }}
230
236
  >
231
- <IconSeam name="filter" size={16} />
237
+ {/* SOLO ladder, md rung — a 32px square takes a 20px glyph.
238
+ * Both header glyphs were 16 (the sm rung) in md boxes, which is
239
+ * the exact hand-transcription hooks/glyphLadders.js exists to
240
+ * stop and the one ActionButton's docstring already names. */}
241
+ <IconSeam name="filter" size={glyphSize('md', true)} />
232
242
  </button>
233
243
  <div
234
244
  className="kol-expand flex items-center justify-center rounded-sm cursor-pointer hover:bg-fg-04"
@@ -254,7 +264,7 @@ const ContentFilters = ({
254
264
  position: searchOpen ? 'absolute' : 'relative',
255
265
  }}
256
266
  >
257
- <IconSeam name="search" size={16} />
267
+ <IconSeam name="search" size={glyphSize('md', true)} />
258
268
  </span>
259
269
  {searchOpen && (
260
270
  <input
@@ -284,6 +294,34 @@ const ContentFilters = ({
284
294
  </div>
285
295
 
286
296
  <div className="flex items-center gap-8">
297
+ {/* The count and LIST/GRID ride the HEADER row, above the divider.
298
+ *
299
+ * "N of N" appears ONLY while the filter panel is open (user ruling
300
+ * 2026-08-15) — with nothing filtered it always reads "12 of 12",
301
+ * which is a number that has never once told anyone anything. It
302
+ * earns its place the moment a filter can change it.
303
+ *
304
+ * Ink stays `text-fg-64`: static information, not a toggle, so it
305
+ * takes neither the 96 active nor the 32 rest. */}
306
+ {showCount && isExpanded && (
307
+ <span className={countClassName} style={{ letterSpacing: 1 }}>
308
+ {filteredItems.length} of {totalCount}
309
+ </span>
310
+ )}
311
+ {layoutOptions && (
312
+ <div className="flex items-center gap-4">
313
+ {layoutOptions.map((opt) => (
314
+ <span
315
+ key={opt.value}
316
+ onClick={() => setLayout(opt.value)}
317
+ className={`${layoutClassName} cursor-pointer select-none ${layout === opt.value ? stripActiveClassName : stripRestClassName}`}
318
+ style={{ letterSpacing: 1 }}
319
+ >
320
+ {opt.label}
321
+ </span>
322
+ ))}
323
+ </div>
324
+ )}
287
325
  {/* RECENT / SAVED is the SAME STRIP as LIST / GRID, not a ViewToggle.
288
326
  * Read off kol-monitor's original (_tmp/2026-08-15-shell-adoption/
289
327
  * ContentFilters.jsx:225-232): inline spans, `kol-helper-14`,
@@ -312,16 +350,15 @@ const ContentFilters = ({
312
350
 
313
351
  <Divider className="mb-4" />
314
352
 
315
- {/* ONE ROW BELOW THE DIVIDER (user ruling 2026-08-15, four rounds of QA
316
- * against a fork of this file). Filter groups are left-aligned COLUMNS
317
- * label above values — and appear only while the filter toggle is open.
318
- * The layout strip is right-aligned and ALWAYS visible.
353
+ {/* BELOW the divider: the filter GROUPS only. Left-aligned columns
354
+ * label above values visible only while the filter toggle is open.
319
355
  *
320
- * `items-start` is load-bearing: it pins the strip to the label row so
321
- * the values hang beneath it. The strip previously rendered in its own
322
- * row AFTER this block, which is why expanding a filter group pushed it
323
- * down the page the defect that started the whole ticket. */}
324
- {(layoutOptions || isExpanded || showCount) && (
356
+ * The count + layout strip moved ABOVE the divider into the header row
357
+ * (user ruling 2026-08-15, later pass), reversing the earlier "one row
358
+ * below the divider" cut. It also settles the defect that ruling was
359
+ * fighting for good: the strip cannot be pushed down the page by an
360
+ * expanding filter group if it is not in the same row as one. */}
361
+ {isExpanded && (
325
362
  <div className="flex items-start justify-between gap-16 pb-4">
326
363
  <div className="flex items-start gap-16">
327
364
  {isExpanded && filterGroups.map((group) => renderFilterGroup(group))}
@@ -336,31 +373,6 @@ const ContentFilters = ({
336
373
  </button>
337
374
  )}
338
375
  </div>
339
- {/* The count rides the LAYOUT row, not the header (user ruling
340
- * 2026-08-15) — same `kol-helper-12` + 1px tracking as LIST/GRID
341
- * beside it. Ink stays `text-fg-64`: it is static information, not
342
- * a toggle, so it takes neither the 96 active nor the 32 rest. */}
343
- <div className="flex items-center gap-4 flex-shrink-0">
344
- {showCount && (
345
- <span className={countClassName} style={{ letterSpacing: 1 }}>
346
- {filteredItems.length} of {totalCount}
347
- </span>
348
- )}
349
- {layoutOptions && (
350
- <div className="flex items-center gap-4">
351
- {layoutOptions.map((opt) => (
352
- <span
353
- key={opt.value}
354
- onClick={() => setLayout(opt.value)}
355
- className={`${layoutClassName} cursor-pointer select-none ${layout === opt.value ? stripActiveClassName : stripRestClassName}`}
356
- style={{ letterSpacing: 1 }}
357
- >
358
- {opt.label}
359
- </span>
360
- ))}
361
- </div>
362
- )}
363
- </div>
364
376
  </div>
365
377
  )}
366
378