@kolkrabbi/kol-component 0.99.0 → 0.100.1

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.99.0",
3
+ "version": "0.100.1",
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",
package/src/atoms/Tag.jsx CHANGED
@@ -75,13 +75,12 @@ export default function Tag({
75
75
  * SINGLE-LINE chrome — that is exactly the kol-helper-* ramp, whose defining
76
76
  * property is `line-height: 1`. The sizes match the scale 1:1 (10/12/14), so
77
77
  * this is adopting the existing system, not restating it. */
78
- const TYPE = { sm: 'kol-helper-10', md: 'kol-helper-12', lg: 'kol-helper-14' }
78
+ /* the type rides `.kol-tag--{size}` in kol-theme since 0.69.0 (WorkListingRowsAndFilters) */
79
79
 
80
80
  const classes = [
81
81
  'kol-tag',
82
82
  VARIANTS[variant] ?? VARIANTS.primary,
83
83
  `kol-tag--${size}`,
84
- TYPE[size] ?? TYPE.sm,
85
84
  active ? 'is-active' : '',
86
85
  className
87
86
  ].filter(Boolean).join(' ')
@@ -12,7 +12,8 @@ import ContentText from './ContentText.jsx'
12
12
  *
13
13
  * @param {string} variant default | catalog | print | article | work | typeface
14
14
  * @param {ReactNode} media thumb content (omit → placeholder)
15
- * @param {number} thumb thumb edge px — overrides the ruled default; 0 hides
15
+ * @param {number|'fill'} thumb thumb edge px — overrides the ruled default; 0 hides; `'fill'` = a square
16
+ * the height of the row's content, whatever the rung (WorkListingRowsAndFilters)
16
17
  * @param {string} ratio thumb aspect-ratio — overrides the ruled default
17
18
  * @param {number} paddingY vertical padding px — overrides the ruled default
18
19
  * @param {boolean} selected
@@ -38,7 +39,12 @@ const BOX = {
38
39
  article: { thumb: 120, ratio: '1 / 1', pad: '0', gap: S6, align: 'items-start', thumbBg: 'var(--kol-fg-12)', hover: 'var(--kol-oq-02)' },
39
40
  /* work and typeface step UP at md — the shipped rows both do, and a work row
40
41
  * at a fixed 96 cannot hold the display-03 line it was ruled to carry. */
41
- work: { thumb: 64, thumbMd: 112, ratio: '1 / 1', pad: S4, padMd: S6, gap: S4, gapMd: S6, frame: 'transparent', frameHover: 'var(--kol-fg-16)', bg: 'var(--kol-surface-secondary)', minH: 96, minHMd: 160, align: 'items-stretch', thumbRadius: 'var(--kol-radius-xs)', thumbBorder: true },
42
+ /* RULED ON /work (WorkListingRowsAndFilters, 2026-08-27): min-height 168 ("div
43
+ * 8"), 16 padding at every width ("16px padding, that's fine"), the thumb FILLS
44
+ * the content height (136 in a 168 row) with no hairline, the frame steps
45
+ * transparent → fg-08 on hover ("0 → 16 is a big jump"). No md step: the rows
46
+ * were approved at their base values. */
47
+ work: { thumb: 'fill', ratio: '1 / 1', pad: S4, gap: S4, frame: 'transparent', frameHover: 'var(--kol-fg-08)', bg: 'var(--kol-surface-secondary)', minH: 168, align: 'items-stretch', thumbRadius: 'var(--kol-radius-xs)', thumbBorder: false },
42
48
  /* typeface's row is a COLUMN, not a line: a header (name/styles left,
43
49
  * classification/year right) with a full-width specimen band under it. The
44
50
  * shipped item is `flex-col gap-6`, and forcing it into the horizontal
@@ -63,6 +69,7 @@ export default function ContentRow({
63
69
  }) {
64
70
  const box = BOX[variant] ?? BOX.default
65
71
  const thumbPx = thumb ?? box.thumb
72
+ const padY = (paddingY != null ? `${paddingY}px` : String(box.pad)).trim().split(/\s+/)[0]
66
73
 
67
74
  /* The md: STEP is a custom property, not a Tailwind variant. Tailwind cannot
68
75
  * generate `md:min-h-40` from package source (the SegmentedToggle rule), and
@@ -76,7 +83,10 @@ export default function ContentRow({
76
83
  '--kol-row-gap-md': box.gapMd,
77
84
  '--kol-row-min-h': box.minH != null ? `${box.minH}px` : undefined,
78
85
  '--kol-row-min-h-md': box.minHMd != null ? `${box.minHMd}px` : undefined,
79
- '--kol-row-thumb': `${thumbPx}px`,
86
+ /* `fill` = the rung minus the vertical padding — a definite number, not a
87
+ * stretch (a min-height row has no definite cross size, so a stretched
88
+ * aspect-ratio square resolved to the image's intrinsic width) */
89
+ '--kol-row-thumb': thumbPx === 'fill' ? `calc(${box.minH ?? 96}px - 2 * ${padY})` : `${thumbPx}px`,
80
90
  '--kol-row-thumb-md': box.thumbMd != null ? `${box.thumbMd}px` : undefined,
81
91
  '--kol-content-hover-bg': box.hover,
82
92
  '--kol-content-hover-border': box.frameHover,
@@ -103,7 +113,7 @@ export default function ContentRow({
103
113
  </>
104
114
  ) : (
105
115
  <>
106
- {thumbPx > 0 && (
116
+ {(thumbPx === 'fill' || thumbPx > 0) && (
107
117
  /* THE THUMB IS A FIXED SQUARE BOX (ContentRowsAndPrintCard, user
108
118
  * 2026-08-27: "the image should not control height, image should fit
109
119
  * the row image placeholder") — `--kol-row-thumb` wide, square, at the
@@ -111,7 +121,7 @@ export default function ContentRow({
111
121
  * media object-covers into it; row height = max(thumb, text). This
112
122
  * retires the 2026-08-15 fill-the-height ruling and the `thumbSquare`
113
123
  * exception it needed. Rows never zoom their thumb — cards keep theirs. */
114
- <div className="kol-row-thumb shrink-0 self-start">
124
+ <div className={`kol-row-thumb shrink-0 self-start ${thumbPx === 'fill' ? 'is-fill' : ''}`.trim()}>
115
125
  <ContentMedia
116
126
  ratio={null}
117
127
  border={box.thumbBorder ?? false}
@@ -87,7 +87,10 @@ const RAMP = {
87
87
  * `kol-mono-12 text-fg-64` · description `kol-sans-heading-03 text-auto`.
88
88
  * Title and type are the pair — same rung, same full ink; only the year
89
89
  * steps down. */
90
- row: { title: 'kol-mono-12 text-body uppercase truncate', body: 'kol-sans-heading-03 leading-tight text-emphasis truncate', meta: 'kol-mono-12 text-body', date: 'kol-mono-12 text-meta', tags: 'flex flex-wrap items-center gap-1.5' },
90
+ /* RULED ON /work beside the local WorkListItem (WorkListingRowsAndFilters,
91
+ * 2026-08-27): the typeface row's voices — title + type kol-mono-14 full
92
+ * ink, the year kol-mono-12 fg-64. Body unchanged (the site passes its face). */
93
+ row: { title: 'kol-mono-14 uppercase text-emphasis truncate', body: 'kol-sans-heading-03 leading-tight text-emphasis truncate', meta: 'kol-mono-14 text-emphasis', date: 'kol-mono-12 text-fg-64', tags: 'flex flex-wrap items-center gap-1.5' },
91
94
  },
92
95
  typeface: {
93
96
  /* RULED ON SCREEN (TypefaceCardAndRow, kol-website 2026-08-27): name and
@@ -85,7 +85,7 @@ export default function SectionText({
85
85
  {/* `kol-section-text-eyebrow` = uppercase by ROLE (kol-theme ≥0.55.0);
86
86
  * `labelClass` is the voice riding beside it */}
87
87
  {eb && <span className={cls(`kol-section-text-eyebrow ${ebClass}`, 'eyebrow')} style={slotStyle.eyebrow ?? slotStyle.label}>{eb}</span>}
88
- {headline && <Headline className={cls(`${headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}${headlineCase === 'upper' ? ' kol-section-text-caps' : ''}`, 'headline')} style={slotStyle.headline}>{headline}</Headline>}
88
+ {headline && <Headline className={cls(`kol-section-text-headline ${headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}${headlineCase === 'upper' ? ' kol-section-text-caps' : ''}`, 'headline')} style={slotStyle.headline}>{headline}</Headline>}
89
89
  {body && (typeof body === 'string' ? <p className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</p> : <div className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</div>)}
90
90
  {children}
91
91
  {actions && <div className={cls(actionsClass, 'actions')} style={slotStyle.actions}>{actions}</div>}
@@ -98,6 +98,10 @@ export default function ContentCollection({
98
98
  key={form}
99
99
  className={`m-0 list-none p-0 ${colsCls} ${className}`.replace(/\s+/g, ' ').trim()}
100
100
  style={{
101
+ /* the ROW's md step is a container query (kol-theme .kol-row) and nothing
102
+ * declared a container until 2026-08-27 (WorkListingRowsAndFilters) — the
103
+ * wall is one, so a row inside it steps on the wall's own width */
104
+ containerType: 'inline-size',
101
105
  display: 'grid',
102
106
  /* with `cols` the classes carry the tracks — an inline template would outrank them */
103
107
  gridTemplateColumns: colsCls
@@ -42,6 +42,8 @@ import IconFrame from '../atoms/IconFrame.jsx'
42
42
  * @param {boolean} props.titleUppercase — cases the title (default false)
43
43
  * @param {string} props.labelClassName — filter-group label type/ink
44
44
  * @param {boolean} props.labelUppercase — cases the group label (default true)
45
+ * groups: `{ label, key, values, stack?, className?, wrapClassName? }` — a group with ≤ 6 values stacks by
46
+ * default (`stack: false` opts out); `className` / `wrapClassName` are per-group seams
45
47
  * @param {string} props.tagVariant — filter chip variant ('primary' grey fill)
46
48
  * @param {string} props.tagSize — filter chip size
47
49
  * @param {string} props.tagActiveClassName — chip ink, selected
@@ -240,8 +242,13 @@ const ContentFilters = ({
240
242
  </div>
241
243
  ) : null
242
244
 
243
- const renderFilterGroup = (group) => (
244
- <div key={group.key} className={`flex flex-col gap-3 ${group.stack ? 'shrink-0' : 'min-w-0 flex-1'}`}>
245
+ const renderFilterGroup = (group) => {
246
+ /* a SHORT group (≤ 6 values) stacks by default (WorkListingRowsAndFilters, 2026-08-27
247
+ * user: "why do I have to say it every time? why do you default to 50/50?");
248
+ * `stack: false` opts out, `stack: true` forces */
249
+ const stacked = group.stack ?? (group.values?.length ?? 0) <= 6
250
+ return (
251
+ <div key={group.key} className={`flex flex-col gap-3 ${stacked ? 'shrink-0' : 'min-w-0 flex-1'} ${group.className ?? ''}`.trim()}>
245
252
  {/* THE CATEGORY LABEL IS THE ACTIVE INK — `kol-helper-12` at `text-fg-96`,
246
253
  * the same full opacity a SELECTED layout item wears (user ruling
247
254
  * 2026-08-15: "TAGS and other categories are ACTIVE state full opacity").
@@ -255,7 +262,9 @@ const ContentFilters = ({
255
262
  <h4 className={labelClassName} style={labelUppercase ? { textTransform: 'uppercase' } : undefined}>
256
263
  {group.label}
257
264
  </h4>
258
- <div className={group.stack ? 'flex flex-col items-start gap-2' : 'flex flex-wrap gap-2'}>
265
+ {/* the fluid group keeps 48px of room on its right (it ran flush to the
266
+ * panel's edge); `group.wrapClassName` is the per-group seam */}
267
+ <div className={`${stacked ? 'flex flex-col items-start gap-2' : 'flex flex-wrap gap-2 pr-12'} ${group.wrapClassName ?? ''}`.trim()}>
259
268
  {group.values.map((value) => (
260
269
  <Tag
261
270
  key={value}
@@ -272,6 +281,7 @@ const ContentFilters = ({
272
281
  </div>
273
282
  </div>
274
283
  )
284
+ }
275
285
 
276
286
  return (
277
287
  /* minHeight 0 on both this root and the body below: without it a flex
@@ -466,7 +476,8 @@ const ContentFilters = ({
466
476
  )}
467
477
 
468
478
  <div style={{ marginTop: 'var(--kol-spacing-6)', display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
469
- {renderItem(filteredItems, viewMode, layout)}
479
+ {/* the items panel is a CONTAINER (WorkListingRowsAndFilters, 2026-08-27) — rows step on its width */}
480
+ <div style={{ containerType: 'inline-size' }}>{renderItem(filteredItems, viewMode, layout)}</div>
470
481
  </div>
471
482
  </div>
472
483
  )
@@ -1,5 +1,4 @@
1
1
  import Button from '../atoms/Button.jsx'
2
- import Tag from '../atoms/Tag.jsx'
3
2
  import SegmentedToggle from '../atoms/SegmentedToggle.jsx'
4
3
  import ToggleSwitch from '../atoms/ToggleSwitch.jsx'
5
4
  import ShellDrawer from '../molecules/ShellDrawer.jsx'
@@ -137,10 +136,11 @@ export function SettingsChoice({ options = [], value, onChange, ariaLabel }) {
137
136
  return <SegmentedToggle size="sm" value={value} onChange={onChange} options={opts} ariaLabel={ariaLabel} />
138
137
  }
139
138
 
140
- /* The chips are the DS `Tag` ("the tag ticket" SettingsPanelChromeAndColumnPreview,
141
- * user 2026-08-27: same chrome as Tag everywhere else, not a third look): `md`
142
- * (helper-12), `inverse` when on, `secondary` when off, as a button. The old
143
- * `.kol-control` string stays exported for consumers that typed it. */
139
+ /* THE CONTROL CHIP (SettingsPanelCompliance, user 2026-08-27: "WROOOONG" on the
140
+ * 0.98.0 Tag pills): `.kol-control` sm on kol-mono-12, filled when on, meta ink
141
+ * when off, the count in meta after the label the SAME string ContentFilters'
142
+ * kind chips, ViewToggle's text variant and the SELECT / FLAT strip wear. No
143
+ * Tag, no uppercase, no inverse. */
144
144
  export const CHIP_CLS = 'kol-control kol-control-sm kol-mono-12'
145
145
  export const chipCls = (on) => `${CHIP_CLS} ${on ? 'kol-control--filled' : 'text-meta hover:text-emphasis'}`
146
146
 
@@ -156,18 +156,16 @@ export function SettingsChipRow({ options = [], selected = [], onToggle }) {
156
156
  return (
157
157
  <div className="flex flex-wrap gap-1">
158
158
  {options.map((o) => (
159
- <Tag
159
+ <button
160
160
  key={String(o.value)}
161
- size="md"
162
- hash={false}
163
- variant={on.has(o.value) ? 'inverse' : 'secondary'}
164
- active={on.has(o.value)}
161
+ type="button"
165
162
  aria-pressed={on.has(o.value)}
166
163
  onClick={() => onToggle?.(o.value)}
164
+ className={chipCls(on.has(o.value))}
167
165
  >
168
166
  {o.label ?? String(o.value)}
169
167
  {o.count > 0 && <span className="text-meta"> {o.count}</span>}
170
- </Tag>
168
+ </button>
171
169
  ))}
172
170
  </div>
173
171
  )