@kolkrabbi/kol-component 0.130.0 → 0.132.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.130.0",
3
+ "version": "0.132.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",
package/src/index.js CHANGED
@@ -82,8 +82,6 @@ export { default as Dropdown } from './molecules/Dropdown.jsx'
82
82
  export { default as FieldRow, StatusChip } from './molecules/FieldRow.jsx'
83
83
  export { default as FramedMediaBand } from './organisms/FramedMediaBand.jsx'
84
84
  export { default as Image } from './atoms/Image.jsx'
85
- export { default as MediaCard } from './molecules/MediaCard.jsx'
86
- export { default as MediaRow } from './molecules/MediaRow.jsx'
87
85
 
88
86
  /* content-card system (2026-08-15) — the ruled card/row family:
89
87
  * docs/documentation/03-components/06-content-card-system.md */
@@ -29,12 +29,11 @@ import ContentText from './ContentText.jsx'
29
29
  /* ruled defaults — defaults, not hardcodes; `ratio` stays overridable while
30
30
  * the A4 question is open (06-content-card-system.md §4). */
31
31
  const RATIOS = {
32
- default: '1 / 1',
32
+ file: '1 / 1',
33
33
  catalog: '1 / 1.41421',
34
- print: '1 / 1.41421',
35
34
  article: '16 / 9',
36
- work: '3 / 4',
37
- typeface: '1 / 1.41421',
35
+ showcase: '3 / 4',
36
+ showcaseCanvas: '1 / 1.41421',
38
37
  }
39
38
 
40
39
  /* ruled box values per variant, verbatim from the §3 reference cards —
@@ -43,24 +42,23 @@ const BOX = {
43
42
  /* no border (ColumnBrowser round, user 2026-08-27: "I don't like the border, I feel
44
43
  * like I've said that before" — the ListingCardThumbBorder ruling): the selected
45
44
  * state reads from the checked ToggleCheckbox, not a fg-64 border */
46
- default: { layout: 'stack', border: null, bg: 'var(--kol-fg-02)', pad: 'var(--kol-pad-card-sm)' },
45
+ file: { layout: 'stack', border: null, bg: 'var(--kol-fg-02)', pad: 'var(--kol-pad-card-sm)' },
47
46
  /* THE FRAME READS BACKWARDS (CatalogCardFrameAndZoom, kol-website 2026-08-28 — user, on a 212-tile
48
47
  * grid: no frame at rest; the old rest value is the hover): a wall of fg-04 frames is a grid of boxes,
49
48
  * louder than what they hold. Rest `transparent` (the 1px stays, so the hover step never relayouts),
50
49
  * hover fg-04. Was fg-04 → fg-16 (ShellHomeSystem, 2026-08-27). */
51
50
  catalog: { layout: 'fill-card', border: 'transparent', bg: 'var(--kol-fg-04)', pad: 'var(--kol-pad-card-sm) var(--kol-pad-card-md)', plateTop: true, plateBg: 'var(--kol-surface-primary)', frameHover: 'var(--kol-fg-04)' },
52
51
  /* flip: PrintGridCard's 3D turn on `isFlipped` → `selected` (ContentRowsAndPrintCard, 2026-08-27) */
53
- print: { layout: 'fill-card', border: null, bg: 'var(--kol-surface-secondary)', pad: 'var(--kol-pad-card-sm) var(--kol-pad-card-md)', plateTop: true, flip: true },
54
52
  article: { layout: 'stack', border: null, bg: null, pad: '0', mediaGap: 'var(--kol-spacing-4)' },
55
53
  /* work is a DRAWER: image-only at rest, and on hover a light plate rises
56
54
  * over the bottom of the artwork carrying the title + meta. This is the
57
55
  * shipped WorkCard and it was wrong to reject it as "hiding the title" — a
58
56
  * work shelf is a wall of images by design, and the caption is the reveal. */
59
- work: { layout: 'drawer', border: 'var(--kol-fg-04)', bg: null, pad: 'var(--kol-pad-card-md)', padMd: 'var(--kol-pad-card-lg)' },
57
+ showcase: { layout: 'drawer', border: 'var(--kol-fg-04)', bg: null, pad: 'var(--kol-pad-card-md)', padMd: 'var(--kol-pad-card-lg)' },
60
58
  /* typeface's card is a FIXED 500px tall specimen board, not a ratio — the
61
59
  * shipped item is `h-[500px]`, and a ratio re-crops the glyph at every
62
60
  * column width, which is the one thing a specimen must not do. */
63
- typeface: { layout: 'canvas', border: 'var(--kol-fg-08)', bg: 'var(--kol-surface-primary)', pad: 'var(--kol-pad-card-lg)', height: 500 },
61
+ showcaseCanvas: { layout: 'canvas', border: 'var(--kol-fg-08)', bg: 'var(--kol-surface-primary)', pad: 'var(--kol-pad-card-lg)', height: 500 },
64
62
  }
65
63
 
66
64
  /* HOVER is a bg STEP on the opaque tier, per 05-control-chrome.md's state model
@@ -69,16 +67,15 @@ const BOX = {
69
67
  * control vanishing. article is the exception the shipped card already set: it
70
68
  * has no surface of its own to step, so it dims its title instead. */
71
69
  const HOVER = {
72
- default: 'var(--kol-oq-04)',
70
+ file: 'var(--kol-oq-04)',
73
71
  catalog: 'var(--kol-surface-tertiary)',
74
- print: 'var(--kol-oq-04)',
75
72
  /* article and work take NO surface hover, and that is a decision not a gap:
76
73
  * article has no surface of its own (its media frame, when on, steps its
77
74
  * border instead), and work's whole hover IS the drawer rising. A second wash under
78
75
  * either would be two answers to one question. */
79
76
  article: null,
80
- work: null,
81
- typeface: 'var(--kol-surface-inverse)',
77
+ showcase: null,
78
+ showcaseCanvas: 'var(--kol-surface-inverse)',
82
79
  }
83
80
 
84
81
  /* per-variant media treatment. `ring` sits OVER the artwork, `frame` UNDER it —
@@ -89,17 +86,39 @@ const MEDIA = {
89
86
  /* zoom is for IMAGE-LED cards — where the artwork is the content and the
90
87
  * card is a frame around it. A catalog tile whose preview is a diagram, or
91
88
  * a default file card whose thumb is a 48px chip, gets nothing from it. */
92
- /* fade: PrintGridCard's image fade-in + loading="lazy", carried (2026-08-27) */
93
- print: { ring: true, zoom: true, fade: true },
94
- work: { zoom: true },
89
+ /* `print` FOLDED INTO `catalog` 2026-08-29 (user: "print doesnt need to exist
90
+ * all together, it can just use catalog"). Its two behaviours became PROPS,
91
+ * because a 3D turn and an image fade-in are things a card DOES, not kinds of
92
+ * content it holds: `flip` and `fade`. Its zoom is catalog's already (below).
93
+ * `ring` was removed the same day — it stays an opt-in on ContentMedia. */
94
+ showcase: { zoom: true },
95
95
  /* frame OFF by default (ListingCardThumbBorder, user 2026-08-27: "I hate
96
96
  * border — remove border"): the article thumb's hairline is opt-in —
97
97
  * `frame` turns it on, and with it the fg-08 → fg-16 hover step */
98
98
  article: { frame: false, borderHover: true, zoom: true },
99
99
  }
100
100
 
101
+ /* VARIANTS ARE CONTENT KINDS, NEVER PAGE NAMES (user ruling 2026-08-29:
102
+ * "naming the variants by their use location doesnt make sense, because print
103
+ * doesnt use print variant"). Six page-named variants became four kinds —
104
+ * file · catalog · article · showcase — and the old names alias onto them:
105
+ *
106
+ * default → file the fallback's name described its position
107
+ * print → catalog /prints renders `work` rows and a plateless
108
+ * catalog card; `flip` + `fade` became props
109
+ * work → showcase /work is a location; a work is a shown piece
110
+ * typeface → showcase + same card, full overlay instead of a drawer
111
+ * layout="canvas"
112
+ *
113
+ * `layout` is the discriminator inside `showcase` because `reveal` was already
114
+ * taken by the consumer's overlay NODE. Its values are the box vocabulary the
115
+ * component already spoke: drawer (default) · canvas. */
116
+ const ALIAS = { default: 'file', print: 'catalog', work: 'showcase', typeface: 'showcaseCanvas' }
117
+ const LAYOUT_KEY = { canvas: 'showcaseCanvas', drawer: 'showcase' }
118
+
101
119
  export default function ContentCard({
102
- variant = 'default',
120
+ variant: variantProp = 'file',
121
+ layout,
103
122
  hero = false,
104
123
  label,
105
124
  pad,
@@ -109,6 +128,9 @@ export default function ContentCard({
109
128
  frame,
110
129
  ring,
111
130
  zoom,
131
+ /* `flip` + `fade` — behaviours, not variants (the print fold, 2026-08-29) */
132
+ flip,
133
+ fade,
112
134
  /* plateRule (CatalogCardFrameAndZoom): the plate's top hairline — default the variant's (catalog and
113
135
  * print draw it); `false` turns it off without an `!important` in a consumer sheet */
114
136
  plateRule,
@@ -125,7 +147,12 @@ export default function ContentCard({
125
147
  className = '',
126
148
  ...text
127
149
  }) {
128
- const box = BOX[variant] ?? BOX.default
150
+ const aliased = ALIAS[variantProp] ?? variantProp
151
+ /* `layout` only re-keys inside the showcase family — it is not a general
152
+ * escape hatch that would let any variant borrow another's box. */
153
+ const variant = aliased.startsWith('showcase') && layout ? LAYOUT_KEY[layout] ?? aliased : aliased
154
+ const box = BOX[variant] ?? BOX.file
155
+ const doFlip = flip ?? box.flip
129
156
  /* HERO (2026-08-27 — the featured card riding a page's fold; ListingCard
130
157
  * size="hero" had no ContentCard equivalent, which is why Stack still
131
158
  * imported it): a header row above the media — `label` left, `meta` chips
@@ -205,7 +232,7 @@ export default function ContentCard({
205
232
  borderHover: MEDIA[variant]?.borderHover ?? false,
206
233
  /* a catalog card with REAL media is image-led: it zooms (ShellHomeSystem) */
207
234
  zoom: zoom ?? (isHero ? 'hero' : variant === 'catalog' ? media != null : MEDIA[variant]?.zoom ?? false),
208
- fade: MEDIA[variant]?.fade ?? false,
235
+ fade: fade ?? MEDIA[variant]?.fade ?? false,
209
236
  }
210
237
 
211
238
  /* `control` — the in-frame control slot (user ruling 2026-08-15). One node,
@@ -236,12 +263,12 @@ export default function ContentCard({
236
263
  </>
237
264
  ) : box.layout === 'fill-card' ? (
238
265
  <>
239
- <div className="flex-1 min-w-0 min-h-0 relative overflow-hidden" style={{ ...(expanded ? { flex: '0 0 50%' } : null), ...(box.flip ? { perspective: '1000px' } : null) }}>
266
+ <div className="flex-1 min-w-0 min-h-0 relative overflow-hidden" style={{ ...(expanded ? { flex: '0 0 50%' } : null), ...(doFlip ? { perspective: '1000px' } : null) }}>
240
267
  {/* the FLIP (print): PrintGridCard's turn, verbatim — preserve-3d,
241
268
  * 0.4s ease-out, rotateY(180deg) while `selected`; the consumer's
242
269
  * `onClick` reads the rect off `event.currentTarget` for its
243
270
  * FLIP-transition (the seam was always reachable) */}
244
- {box.flip ? (
271
+ {doFlip ? (
245
272
  <div className="h-full w-full" style={{ transformStyle: 'preserve-3d', backfaceVisibility: 'hidden', transition: 'transform 0.4s ease-out', transform: selected ? 'rotateY(180deg)' : 'rotateY(0deg)' }}>
246
273
  <ContentMedia ratio={null} {...mediaProps}>{media}</ContentMedia>
247
274
  </div>
@@ -10,12 +10,17 @@ import ContentText from './ContentText.jsx'
10
10
  * thumb; article rides a 120px 16:9 thumb; work a framed 64px row; typeface a
11
11
  * framed no-thumb block.
12
12
  *
13
- * @param {string} variant default | catalog | print | article | work | typeface
13
+ * @param {string} variant file | catalog | print | article | work | typeface
14
+ * (`default` is an alias of `file`)
14
15
  * @param {ReactNode} media thumb content (omit → placeholder)
15
16
  * @param {number|'fill'} thumb thumb edge px — overrides the ruled default; 0 hides; `'fill'` = a square
16
17
  * the height of the row's content, whatever the rung (WorkListingRowsAndFilters)
17
18
  * @param {string} ratio thumb aspect-ratio — overrides the ruled default
18
19
  * @param {number} paddingY vertical padding px — overrides the ruled default
20
+ * @param {{label: ReactNode, value: ReactNode}[]} specs label/value pairs on the
21
+ * row's trailing edge, independent of variant.
22
+ * Horizontal variants only — `typeface` is a column
23
+ * and takes a band through `footer` instead.
19
24
  * @param {boolean} selected
20
25
  * @param {Function} onClick
21
26
  * text slots + *Class seams forwarded to ContentText.
@@ -25,12 +30,15 @@ import ContentText from './ContentText.jsx'
25
30
  * --kol-spacing-* tokens (2=8 · 3=12 · 4=16 · 6=24), never a literal */
26
31
  const S2 = 'var(--kol-spacing-2)', S3 = 'var(--kol-spacing-3)', S4 = 'var(--kol-spacing-4)', S6 = 'var(--kol-spacing-6)'
27
32
  const BOX = {
28
- default: { thumb: 48, ratio: '1 / 1', pad: `${S2} 0`, gap: S3, align: 'items-center', divider: true, hover: 'var(--kol-oq-04)' },
33
+ /* NO hover wash (user 2026-08-29, on the variant-reference page: "makes a
34
+ * grey background, remove it"). The default row is a bare ruled line — a
35
+ * full-width grey band under it is heavier than the line itself. Scoped to
36
+ * `default`: catalog and print still carry the same `oq-04`, unruled. */
37
+ file: { thumb: 48, ratio: '1 / 1', pad: `${S2} 0`, gap: S3, align: 'items-center', divider: true },
29
38
  /* catalog/print rows render AT 36px — the shipped GridCard list row is a
30
39
  * fixed 36 and the Y padding was what pushed it past that. X padding stays;
31
40
  * `minH` is now the whole height budget and the row centres inside it. */
32
41
  catalog: { thumb: 0, ratio: '1 / 1', pad: `0 ${S3}`, gap: S3, frame: 'var(--kol-fg-04)', bg: 'var(--kol-surface-tertiary)', minH: 36, align: 'items-center', hover: 'var(--kol-oq-04)' },
33
- print: { thumb: 0, ratio: '1 / 1', pad: `0 ${S3}`, gap: S3, frame: 'var(--kol-fg-04)', bg: 'var(--kol-surface-tertiary)', minH: 36, align: 'items-center', hover: 'var(--kol-oq-04)' },
34
42
  /* ListingCard's row thumb is `bg-fg-12` bare — a tint, no border. */
35
43
  /* article had NO hover of any kind — the only row in the family you could
36
44
  * point at and get nothing back. It has no surface of its own, so it takes
@@ -44,22 +52,32 @@ const BOX = {
44
52
  * the content height (136 in a 168 row) with no hairline, the frame steps
45
53
  * transparent → fg-08 on hover ("0 → 16 is a big jump"). No md step: the rows
46
54
  * 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 },
55
+ showcase: { 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 },
48
56
  /* typeface's row is a COLUMN, not a line: a header (name/styles left,
49
57
  * classification/year right) with a full-width specimen band under it. The
50
58
  * shipped item is `flex-col gap-6`, and forcing it into the horizontal
51
59
  * thumb-beside-text shape is what turned its alphabet into a 160px thumb. */
52
- typeface: { thumb: 0, ratio: '1 / 1', pad: S6, gap: S6, column: true, frame: 'var(--kol-fg-08)', bg: 'transparent', minH: 160, align: 'items-start', hover: 'color-mix(in srgb, var(--kol-surface-on-primary) 1%, transparent)', frameHover: 'color-mix(in srgb, var(--kol-surface-on-primary) 24%, transparent)' },
60
+ showcaseCanvas: { thumb: 0, ratio: '1 / 1', pad: S6, gap: S6, column: true, frame: 'var(--kol-fg-08)', bg: 'transparent', minH: 160, align: 'items-start', hover: 'color-mix(in srgb, var(--kol-surface-on-primary) 1%, transparent)', frameHover: 'color-mix(in srgb, var(--kol-surface-on-primary) 24%, transparent)' },
53
61
  }
54
62
 
63
+ /* `default` → `file` (user 2026-08-29). The variant was named after being the
64
+ * fallback, which described its position in the map rather than its content;
65
+ * every consumer of it is a file browser (r2b2 via MediaLibrary, the brand
66
+ * app's LibraryLocal + SlideDeckManager) and its slots are filename · date ·
67
+ * size. `default` keeps working as an alias — see 04-retirements.md. */
68
+ const ALIAS = { default: 'file', print: 'catalog', work: 'showcase', typeface: 'showcaseCanvas' }
69
+ const LAYOUT_KEY = { column: 'showcaseCanvas', line: 'showcase' }
70
+
55
71
  export default function ContentRow({
56
- variant = 'default',
72
+ variant: variantProp = 'file',
73
+ layout,
57
74
  media,
58
75
  thumb,
59
76
  ratio,
60
77
  paddingY,
61
78
  actions,
62
79
  footer,
80
+ specs,
63
81
  selected = false,
64
82
  onClick,
65
83
  href,
@@ -67,7 +85,9 @@ export default function ContentRow({
67
85
  className = '',
68
86
  ...text
69
87
  }) {
70
- const box = BOX[variant] ?? BOX.default
88
+ const aliased = ALIAS[variantProp] ?? variantProp
89
+ const variant = aliased.startsWith('showcase') && layout ? LAYOUT_KEY[layout] ?? aliased : aliased
90
+ const box = BOX[variant] ?? BOX.file
71
91
  const thumbPx = thumb ?? box.thumb
72
92
  const padY = (paddingY != null ? `${paddingY}px` : String(box.pad)).trim().split(/\s+/)[0]
73
93
 
@@ -133,6 +153,22 @@ export default function ContentRow({
133
153
  </div>
134
154
  )}
135
155
  <ContentText variant={variant} form="row" className="flex-1" {...text} />
156
+ {/* `specs` rides the trailing edge on EVERY variant — year · material ·
157
+ * edition is a content difference, not a geometry one, and minting a
158
+ * seventh page-named box for it is the exact mistake §1 of the ticket is
159
+ * about (RowVariantNamesAndSpecs, 2026-08-29).
160
+ * ponytail: horizontal rows only — a column variant (`typeface`) has no
161
+ * right column to ride. Give it a band via `footer` instead. */}
162
+ {specs?.length > 0 && (
163
+ <dl className="kol-row-specs shrink-0">
164
+ {specs.map(({ label, value }, i) => (
165
+ <div key={label ?? i}>
166
+ <dt>{label}</dt>
167
+ <dd>{value}</dd>
168
+ </div>
169
+ ))}
170
+ </dl>
171
+ )}
136
172
  {/* trailing edge, never in the text flow — MediaRow's placement */}
137
173
  {actions && <div className="shrink-0">{actions}</div>}
138
174
  </>
@@ -47,7 +47,7 @@
47
47
 
48
48
  /* [variant][form][slot] → 'type-class ink-role', verbatim from the ruled table */
49
49
  const RAMP = {
50
- default: {
50
+ file: {
51
51
  card: { title: 'kol-sans-heading-04 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta' },
52
52
  row: { title: 'kol-sans-heading-05 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta' },
53
53
  },
@@ -57,12 +57,8 @@ const RAMP = {
57
57
  card: { title: 'kol-mono-14 text-emphasis', detail: 'kol-mono-10 text-meta truncate' },
58
58
  row: { title: 'kol-mono-12 text-emphasis', detail: 'kol-mono-10 text-meta' },
59
59
  },
60
- print: {
61
- card: { title: 'kol-mono-14 text-emphasis', detail: 'kol-mono-10 text-meta' },
62
- /* mono-12 + emphasis, matching catalog — print's row IS catalog's row, so
63
- * the two must not disagree about their own type. */
64
- row: { title: 'kol-mono-12 text-emphasis', detail: 'kol-mono-10 text-meta' },
65
- },
60
+ /* `print` folded into `catalog` 2026-08-29 — its row was already catalog's,
61
+ * and its card differed only by `detail` not truncating. It aliases now. */
66
62
  article: {
67
63
  /* kol-content-title-dim: the title dims to 70% on card hover (StackCardHover, 2026-08-27 — ListingCard's move, kol-theme) */
68
64
  card: { eyebrow: 'kol-mono-12 text-meta', title: 'kol-sans-heading-03 text-emphasis kol-content-title-dim', body: 'kol-mono-14 text-body', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta', tags: 'flex flex-wrap gap-2' },
@@ -75,7 +71,7 @@ const RAMP = {
75
71
  * display-03 uppercase, clamp 2, dim on hover; body mono-14 fg-48 clamp 2. */
76
72
  hero: { eyebrow: 'kol-card-kicker tracking-wide text-fg-64', title: 'kol-sans-display-03 uppercase line-clamp-2 kol-content-title-dim', body: 'kol-mono-14 text-fg-48 line-clamp-2', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta', tags: 'flex flex-wrap gap-2' },
77
73
  },
78
- work: {
74
+ showcase: {
79
75
  /* INVERSE ink — the card's plate is the drawer, `surface-inverse`. Leaving
80
76
  * these on `text-emphasis` painted light type on a light plate and the
81
77
  * caption disappeared. */
@@ -95,7 +91,7 @@ const RAMP = {
95
91
  * ink, the year kol-mono-12 fg-64. Body unchanged (the site passes its face). */
96
92
  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' },
97
93
  },
98
- typeface: {
94
+ showcaseCanvas: {
99
95
  /* RULED ON SCREEN (TypefaceCardAndRow, kol-website 2026-08-27): name and
100
96
  * classification are FULL ink, the year steps to 64; the card's title is the
101
97
  * row's title string — one title voice for the typeface family. Ink only. */
@@ -122,9 +118,8 @@ const RAMP = {
122
118
  * Directions are the RULED structures (06-content-card-system.md §2 boxes),
123
119
  * read off the shipped components and the live pages they render on. */
124
120
  const ORDER = {
125
- default: { card: ['title', ['group', 'date', 'size']], row: ['title', ['group', 'date', 'size']] },
121
+ file: { card: ['title', ['group', 'date', 'size']], row: ['title', ['group', 'date', 'size']] },
126
122
  catalog: { card: ['title', 'detail'], row: [['between', 'title', 'detail']] },
127
- print: { card: ['title', 'detail'], row: [['between', 'title', 'detail']] },
128
123
  /* title + body are ONE block in BOTH forms — a `stack`, so they sit on the
129
124
  * tight 4px internal gap while tags, kicker and the meta group keep the
130
125
  * form's own outer gap. A flat column gave every line the same gap, which
@@ -142,12 +137,12 @@ const ORDER = {
142
137
  /* WorkListItem's inner column is `justify-between` with the header row on
143
138
  * top and the description BELOW IT, spanning the full width — not tucked
144
139
  * inside the left column, which is what squeezed the big line. */
145
- work: { card: ['title', 'meta'], row: [['between', ['stack', 'title', 'tags'], ['stack', 'meta', 'date']], 'body'] },
140
+ showcase: { card: ['title', 'meta'], row: [['between', ['stack', 'title', 'tags'], ['stack', 'meta', 'date']], 'body'] },
146
141
  /* typeface row = ONE header line, both sides stacked: name over styles on the
147
142
  * left, classification over year on the right. Verbatim from the shipped
148
143
  * item, which had been flattened into title-left / date-right and lost the
149
144
  * styles line into a body slot below. */
150
- typeface: { card: ['title', 'body'], row: [['between', ['stack', 'title', 'body'], ['stack', 'detail', 'date']]] },
145
+ showcaseCanvas: { card: ['title', 'body'], row: [['between', ['stack', 'title', 'body'], ['stack', 'detail', 'date']]] },
151
146
  }
152
147
 
153
148
  /* inner line gap per variant/form, read from the shipped boxes and spelled in
@@ -163,26 +158,29 @@ const ORDER = {
163
158
  * `self-stretch`, NOT `h-full` — the row carries `min-height`, never `height`,
164
159
  * so `height: 100%` resolves against an indefinite parent, computes to auto,
165
160
  * and shrink-wraps the column. Which is exactly what it did. */
166
- const FILL = { work: true }
161
+ const FILL = { showcase: true }
167
162
 
168
163
  /* gap INSIDE a ['stack', …] block. Defaults to the tight 4px pair article
169
164
  * wants; work's header stack is the shipped gap-1 md:gap-2. */
170
165
  const STACK = {
171
- work: 'var(--kol-spacing-2)',
172
- typeface: 'var(--kol-spacing-2)',
166
+ showcase: 'var(--kol-spacing-2)',
167
+ showcaseCanvas: 'var(--kol-spacing-2)',
173
168
  }
174
169
 
175
170
  const GAPS = {
176
- default: { card: 'var(--kol-spacing-3)', row: 'var(--kol-spacing-2)' },
171
+ file: { card: 'var(--kol-spacing-3)', row: 'var(--kol-spacing-2)' },
177
172
  catalog: { card: 'var(--kol-spacing-1)', row: 'var(--kol-spacing-2)' },
178
- print: { card: 'var(--kol-spacing-2)', row: 'var(--kol-spacing-2)' },
179
173
  article: { card: 'var(--kol-spacing-3)', row: 'var(--kol-spacing-3)', hero: 'var(--kol-spacing-3)' },
180
- work: { card: 'var(--kol-spacing-2)', row: 'var(--kol-spacing-4)' },
181
- typeface: { card: 'var(--kol-spacing-2)', row: 'var(--kol-spacing-6)' },
174
+ showcase: { card: 'var(--kol-spacing-2)', row: 'var(--kol-spacing-4)' },
175
+ showcaseCanvas: { card: 'var(--kol-spacing-2)', row: 'var(--kol-spacing-6)' },
182
176
  }
183
177
 
178
+ /* `default` → `file` (user 2026-08-29) — kept working as an alias here too,
179
+ * because ContentText is exported and a consumer can drive it directly. */
180
+ const ALIAS = { default: 'file', print: 'catalog', work: 'showcase', typeface: 'showcaseCanvas' }
181
+
184
182
  export default function ContentText({
185
- variant = 'default',
183
+ variant: variantProp = 'file',
186
184
  form = 'card',
187
185
  title, body, eyebrow, kicker, detail, date, size, meta, tags,
188
186
  gap, clamp,
@@ -192,8 +190,9 @@ export default function ContentText({
192
190
  /* `kicker` / `kickerClass` = aliases of `eyebrow` / `eyebrowClass` (2026-08-27) */
193
191
  eyebrow = eyebrow ?? kicker
194
192
  eyebrowClass = eyebrowClass ?? kickerClass
195
- const ramp = RAMP[variant]?.[form] ?? RAMP.default[form] ?? RAMP.default.card
196
- const order = (form === 'hero' && ORDER[`${variant}Hero`]) || ORDER[variant]?.[form] || ORDER.default.card
193
+ const variant = ALIAS[variantProp] ?? variantProp
194
+ const ramp = RAMP[variant]?.[form] ?? RAMP.file[form] ?? RAMP.file.card
195
+ const order = (form === 'hero' && ORDER[`${variant}Hero`]) || ORDER[variant]?.[form] || ORDER.file.card
197
196
  const values = { title, body, eyebrow, detail, date, size, meta, tags }
198
197
  const overrides = { title: titleClass, body: bodyClass, eyebrow: eyebrowClass, detail: detailClass, date: dateClass, size: sizeClass, meta: metaClass, tags: tagsClass }
199
198
 
@@ -1,103 +0,0 @@
1
- import { Icon } from '@kolkrabbi/kol-icons'
2
-
3
- /* taxonomy-ok: nests kol-icons's Icon (a package import the relative-import
4
- * check can't see) plus the same-file SelectIndicator. */
5
-
6
- /**
7
- * @deprecated 2026-08-26 — absorbed by `ContentCard variant="default"` in @kolkrabbi/kol-component
8
- * (the Content Set, 2026-08-15). Step 1 of the retirement wave: this export
9
- * stays and renders unchanged until the next major, then it is removed.
10
- * Consumers: swap on your next bump. Map + row-by-row diff:
11
- * docs/documentation/03-components/06-content-card-system.md.
12
- *
13
- * SelectIndicator — passive square check indicator for multi-select rows and
14
- * cards. `on` = checked. Deliberately NOT ToggleCheckbox: that is a labeled
15
- * form control with a real <input>, which double-fires inside a click-target
16
- * card and misleads assistive tech — here the CARD is the toggle, this is
17
- * only its visual state. Inline CSS vars because `bg-fg-default` isn't a
18
- * generated Tailwind utility; the checked fill reads from
19
- * `--kol-surface-on-primary` (solid fg, theme-correct).
20
- *
21
- * Shared by MediaCard and MediaRow; not exported from the package barrel.
22
- */
23
- export function SelectIndicator({ on = false }) {
24
- return (
25
- <span
26
- className="w-4 h-4 shrink-0 rounded-sm border flex items-center justify-center transition-colors"
27
- style={
28
- on
29
- ? { background: 'var(--kol-surface-on-primary)', borderColor: 'var(--kol-surface-on-primary)' }
30
- : { background: 'var(--kol-fg-ab-16, rgba(0,0,0,0.15))', borderColor: 'var(--kol-fg-ab-48, rgba(0,0,0,0.4))' }
31
- }
32
- aria-hidden="true"
33
- >
34
- {on && <Icon name="check" size={11} style={{ color: 'var(--kol-surface-primary)' }} />}
35
- </span>
36
- )
37
- }
38
-
39
- /**
40
- * MediaCard — grid tile for one media object: square thumbnail with a
41
- * top-right download overlay (or a top-left select checkbox in select mode),
42
- * then name / meta / actions stacked below. The grid-view counterpart to
43
- * MediaRow (same slot contract).
44
- *
45
- * Presentational — the parent supplies rendered slots; no media loading,
46
- * fetch, or rename logic lives here. Grid track sizing belongs to the parent
47
- * list; the card just fills its cell.
48
- *
49
- * @param {ReactNode} thumb square thumbnail (image/video/placeholder)
50
- * @param {ReactNode} name name cell (plain text or an inline editor)
51
- * @param {string} meta one-line secondary text (e.g. "1.2 MB · 2026-06-19")
52
- * @param {ReactNode} actions row of action buttons (hidden in select mode)
53
- * @param {string} downloadHref href for the overlay download button (omit to hide)
54
- * @param {boolean} selectMode selection mode on → checkbox replaces download, whole card toggles
55
- * @param {boolean} selected this card is selected (stronger border + checked box)
56
- * @param {Function} onSelect (event) => void — card click in select mode; gets shiftKey for range
57
- */
58
- export default function MediaCard({
59
- thumb,
60
- name,
61
- meta,
62
- actions,
63
- downloadHref,
64
- selectMode = false,
65
- selected = false,
66
- onSelect,
67
- }) {
68
- return (
69
- <li
70
- onClick={selectMode ? onSelect : undefined}
71
- className={`flex flex-col rounded overflow-hidden border bg-fg-02 ${selectMode ? 'cursor-pointer select-none' : ''}`}
72
- style={{ borderColor: selected ? 'var(--kol-fg-64)' : 'var(--kol-fg-12)' }}
73
- >
74
- <div className="aspect-square relative">
75
- {thumb}
76
- {selectMode ? (
77
- <span
78
- className="kol-frame-control kol-frame-control--top-left rounded p-1"
79
- style={{ background: 'var(--kol-fg-ab-12, rgba(0,0,0,0.4))', backdropFilter: 'blur(4px)' }}
80
- >
81
- <SelectIndicator on={selected} />
82
- </span>
83
- ) : downloadHref ? (
84
- <a
85
- href={downloadHref}
86
- aria-label="Download"
87
- title="Download"
88
- className="kol-frame-control inline-flex items-center justify-center w-8 h-8 rounded text-emphasis hover:bg-fg-ab-24 transition-colors"
89
- style={{ background: 'var(--kol-fg-ab-12, rgba(0,0,0,0.4))', backdropFilter: 'blur(4px)' }}
90
- onClick={(e) => e.stopPropagation()}
91
- >
92
- <Icon name="download" size={16} />
93
- </a>
94
- ) : null}
95
- </div>
96
- <div className="p-3 flex flex-col gap-2">
97
- {name}
98
- <p className="kol-mono-12 text-fg-48">{meta}</p>
99
- {!selectMode && actions}
100
- </div>
101
- </li>
102
- )
103
- }
@@ -1,55 +0,0 @@
1
- import { SelectIndicator } from './MediaCard.jsx'
2
-
3
- /**
4
- * @deprecated 2026-08-26 — absorbed by `ContentRow variant="default"` in @kolkrabbi/kol-component
5
- * (the Content Set, 2026-08-15). Step 1 of the retirement wave: this export
6
- * stays and renders unchanged until the next major, then it is removed.
7
- * Consumers: swap on your next bump. Map + row-by-row diff:
8
- * docs/documentation/03-components/06-content-card-system.md.
9
- *
10
- * MediaRow — list row for one media object: optional select checkbox, small
11
- * thumbnail, name (flex), fixed-width date + size columns, then actions.
12
- * The list-view counterpart to MediaCard (same slot contract).
13
- *
14
- * Presentational — the parent supplies rendered slots; interaction outside
15
- * select mode lives in the thumb / name / actions slots. Column widths are
16
- * consumer-tunable via `dateWidth` / `sizeWidth`.
17
- *
18
- * @param {ReactNode} thumb small thumbnail (48px square)
19
- * @param {ReactNode} name name cell (plain text or an inline editor)
20
- * @param {string} date right-aligned date column
21
- * @param {string} size right-aligned size column
22
- * @param {ReactNode} actions row of action buttons (hidden in select mode)
23
- * @param {string} dateWidth Tailwind width class for the date column
24
- * @param {string} sizeWidth Tailwind width class for the size column
25
- * @param {boolean} selectMode selection mode on → checkbox shown, whole row toggles, actions hidden
26
- * @param {boolean} selected this row is selected (highlight + checked box)
27
- * @param {Function} onSelect (event) => void — row click in select mode; gets shiftKey for range
28
- */
29
- export default function MediaRow({
30
- thumb,
31
- name,
32
- date,
33
- size,
34
- actions,
35
- dateWidth = 'w-24',
36
- sizeWidth = 'w-20',
37
- selectMode = false,
38
- selected = false,
39
- onSelect,
40
- }) {
41
- return (
42
- <li
43
- onClick={selectMode ? onSelect : undefined}
44
- className={`flex items-center gap-3 py-2 border-b ${selectMode ? 'cursor-pointer select-none' : ''} ${selected ? 'bg-fg-08' : ''}`}
45
- style={{ borderColor: 'var(--kol-fg-08)' }}
46
- >
47
- {selectMode && <SelectIndicator on={selected} />}
48
- <div className="w-12 h-12 shrink-0 rounded overflow-hidden">{thumb}</div>
49
- <div className="flex-1 min-w-0">{name}</div>
50
- <p className={`kol-mono-12 text-fg-32 shrink-0 text-right ${dateWidth}`}>{date}</p>
51
- <p className={`kol-mono-12 text-fg-48 shrink-0 text-right ${sizeWidth}`}>{size}</p>
52
- {!selectMode && <div className="shrink-0">{actions}</div>}
53
- </li>
54
- )
55
- }