@kolkrabbi/kol-component 0.67.0 → 0.68.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.67.0",
3
+ "version": "0.68.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",
@@ -51,6 +51,15 @@ const syntaxTheme = (foregroundToken = 80) => ({
51
51
  display: 'block',
52
52
  borderRadius: 0,
53
53
  border: 'none',
54
+ /* THE BLOCK WRAPS (06-code-surface.md: Block · pre-wrap + overflow-x auto)
55
+ * and this is the one place that decides it. oneDark's code style carries
56
+ * `whiteSpace: 'pre'`, and react-syntax-highlighter spreads the theme's
57
+ * code style AFTER its own `wrapLongLines` pre-wrap — so the prop, the
58
+ * .kol-codeblock rule and customStyle all said wrap while the <code>
59
+ * computed `pre`, and every long line scrolled instead (measured on
60
+ * /stack/vcap at 393: block 359 wide, content 465–759, CodeBlockMobile-
61
+ * Overflow, kol-website 2026-08-25). */
62
+ whiteSpace: 'pre-wrap',
54
63
  color: `color-mix(in srgb, var(--kol-surface-on-primary) ${foregroundToken}%, transparent)`
55
64
  },
56
65
  comment: {
@@ -95,7 +104,11 @@ export default function CodeBlock({ children, code: codeProp, language: language
95
104
  wrapLines={true}
96
105
  wrapLongLines={true}
97
106
  PreTag="div"
107
+ /* `kol-codeblock-line` names each wrapped line so the theme can
108
+ * reserve the copy control's lane on the FIRST one when no chip row
109
+ * exists (kol-components-molecules.css). */
98
110
  lineProps={{
111
+ className: 'kol-codeblock-line',
99
112
  style: {
100
113
  border: 'none',
101
114
  background: 'transparent',
@@ -57,6 +57,10 @@ const HOVER = {
57
57
  default: 'var(--kol-oq-04)',
58
58
  catalog: 'var(--kol-surface-tertiary)',
59
59
  print: 'var(--kol-oq-04)',
60
+ /* article and work take NO surface hover, and that is a decision not a gap:
61
+ * article has no surface of its own (its media frame steps its border
62
+ * instead), and work's whole hover IS the drawer rising. A second wash under
63
+ * either would be two answers to one question. */
60
64
  article: null,
61
65
  work: null,
62
66
  typeface: 'var(--kol-surface-inverse)',
@@ -31,10 +31,14 @@ const BOX = {
31
31
  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)' },
32
32
  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)' },
33
33
  /* ListingCard's row thumb is `bg-fg-12` bare — a tint, no border. */
34
- article: { thumb: 120, ratio: '1 / 1', pad: '0', gap: S6, align: 'items-start', thumbBg: 'var(--kol-fg-12)' },
34
+ /* article had NO hover of any kind the only row in the family you could
35
+ * point at and get nothing back. It has no surface of its own, so it takes
36
+ * the lightest step there is. Its thumb zooms: on an article row the image
37
+ * IS the subject. */
38
+ article: { thumb: 120, ratio: '1 / 1', pad: '0', gap: S6, align: 'items-start', thumbBg: 'var(--kol-fg-12)', hover: 'var(--kol-oq-02)', thumbZoom: true },
35
39
  /* work and typeface step UP at md — the shipped rows both do, and a work row
36
40
  * at a fixed 96 cannot hold the display-03 line it was ruled to carry. */
37
- 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 },
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, thumbZoom: true },
38
42
  /* typeface's row is a COLUMN, not a line: a header (name/styles left,
39
43
  * classification/year right) with a full-width specimen band under it. The
40
44
  * shipped item is `flex-col gap-6`, and forcing it into the horizontal
@@ -100,17 +104,22 @@ export default function ContentRow({
100
104
  ) : (
101
105
  <>
102
106
  {thumbPx > 0 && (
103
- /* THE THUMB FILLS THE ROW'S HEIGHT and its width follows the ratio
104
- * (user ruling 2026-08-15, true for EVERY row variant). `--kol-row-thumb`
105
- * is now a MINIMUM height, not a fixed width: a thumb sized off its own
106
- * width floated in the top-left of a tall row with the rest of the row
107
- * empty beside it, which is what a `work` row at 160 looked like. */
107
+ /* THE THUMB FILLS THE ROW'S HEIGHT (user ruling 2026-08-15).
108
+ *
109
+ * WIDTH stays pinned to `--kol-row-thumb` that is what stops it
110
+ * running away. Only the height stretches, and `ratio={null}` puts
111
+ * ContentMedia on `h-full` so the image object-covers into whatever
112
+ * height the row is. An earlier cut freed the width instead and let
113
+ * `aspect-ratio` fall back to the image's intrinsic size, which is how
114
+ * one card ate the page. */
108
115
  <div className="kol-row-thumb shrink-0 self-stretch">
109
116
  <ContentMedia
110
- fillHeight
111
- ratio={ratio ?? box.ratio}
117
+ ratio={null}
112
118
  border={box.thumbBorder ?? false}
113
119
  bg={box.thumbBg}
120
+ /* zoom where the thumb IS the subject (article · work), never on a
121
+ * 48px file chip or a between-header with no media at all */
122
+ zoom={box.thumbZoom ?? false}
114
123
  className={box.thumbRadius ? 'rounded-[var(--kol-radius-xs)]' : ''}
115
124
  >
116
125
  {media}
@@ -27,6 +27,23 @@
27
27
  * dateClass, sizeClass, metaClass — full class overrides
28
28
  */
29
29
 
30
+ /* THE ROW INK LADDER — three rungs, and only three (2026-08-15).
31
+ *
32
+ * A work row was rendering its small title, its big description AND its type
33
+ * label all at full emphasis, with only the year stepped down: four fields
34
+ * wearing two values, so nothing led. The ladder is the same one the header
35
+ * strips already use, which is why it reads as one system rather than a
36
+ * per-variant guess:
37
+ *
38
+ * emphasis THE THING YOU CAME FOR — one per row, never two
39
+ * body what identifies it — the title, the type
40
+ * meta metadata — the year, the count, the file size
41
+ *
42
+ * The ROLES, not raw `fg-*` opacities and not raw `oq-*` rungs. A role is the
43
+ * only thing that survives a theme flip and a consumer's own palette; the three
44
+ * of them are exactly the three the family needs, which is why there are three
45
+ * and not fourteen. */
46
+
30
47
  /* [variant][form][slot] → 'type-class ink-role', verbatim from the ruled table */
31
48
  const RAMP = {
32
49
  default: {
@@ -38,28 +55,37 @@ const RAMP = {
38
55
  row: { title: 'kol-mono-12 text-emphasis', detail: 'kol-mono-10 text-meta' },
39
56
  },
40
57
  print: {
41
- card: { title: 'kol-mono-14 text-body', detail: 'kol-mono-10 text-meta' },
42
- row: { title: 'kol-mono-10 text-body', detail: 'kol-mono-10 text-meta' },
58
+ card: { title: 'kol-mono-14 text-emphasis', detail: 'kol-mono-10 text-meta' },
59
+ /* mono-12 + emphasis, matching catalog — print's row IS catalog's row, so
60
+ * the two must not disagree about their own type. */
61
+ row: { title: 'kol-mono-12 text-emphasis', detail: 'kol-mono-10 text-meta' },
43
62
  },
44
63
  article: {
45
- card: { kicker: 'kol-mono-12 text-body', title: 'kol-sans-heading-03 text-emphasis', body: 'kol-mono-14 text-body', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-body', tags: 'flex flex-wrap gap-2' },
46
- row: { kicker: 'kol-mono-12 text-body', title: 'kol-sans-heading-04 text-emphasis', body: 'kol-mono-12 text-body', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-body', tags: 'flex flex-wrap gap-2' },
64
+ card: { kicker: 'kol-mono-12 text-meta', title: 'kol-sans-heading-03 text-emphasis', body: 'kol-mono-14 text-body', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta', tags: 'flex flex-wrap gap-2' },
65
+ row: { kicker: 'kol-mono-12 text-meta', title: 'kol-sans-heading-04 text-emphasis', body: 'kol-mono-12 text-body', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta', tags: 'flex flex-wrap gap-2' },
47
66
  },
48
67
  work: {
49
68
  /* INVERSE ink — the card's plate is the drawer, `surface-inverse`. Leaving
50
69
  * these on `text-emphasis` painted light type on a light plate and the
51
70
  * caption disappeared. */
52
- card: { title: 'kol-sans-display-03 text-fg-inverse', meta: 'kol-mono-12 text-fg-inverse-48', body: 'kol-mono-14 text-fg-inverse', date: 'kol-mono-12 text-fg-inverse-48', tags: 'flex flex-wrap gap-2' },
71
+ /* the drawer is an INVERSE surface, so the roles do not apply but the
72
+ * ladder does: one full ink, the rest stepped. */
73
+ card: { title: 'kol-sans-display-03 text-fg-inverse', meta: 'kol-mono-12 text-fg-inverse-64', body: 'kol-mono-14 text-fg-inverse-64', date: 'kol-mono-12 text-fg-inverse-48', tags: 'flex flex-wrap gap-2' },
53
74
  /* verbatim from WorkListItem: title `kol-mono-14` truncated · type
54
75
  * `kol-mono-12 md:kol-mono-14` at FULL ink, no opacity step · year
55
76
  * `kol-mono-12 text-fg-64` · description `kol-sans-heading-03 text-auto`.
56
77
  * Title and type are the pair — same rung, same full ink; only the year
57
78
  * steps down. */
58
- row: { title: 'kol-mono-12 text-emphasis uppercase truncate', body: 'kol-sans-heading-03 leading-tight text-emphasis truncate', meta: 'kol-mono-12 text-emphasis', date: 'kol-mono-12 text-fg-64', tags: 'flex flex-wrap items-center gap-1.5' },
79
+ 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' },
59
80
  },
60
81
  typeface: {
61
- card: { title: 'kol-mono-16 text-emphasis', body: 'kol-mono-14 text-fg-64', date: 'kol-mono-12 text-fg-64' },
62
- row: { title: 'kol-mono-14 uppercase text-emphasis', body: 'kol-mono-12 text-fg-64', detail: 'kol-mono-14 text-emphasis', date: 'kol-mono-12 text-fg-64' },
82
+ /* same as the row: the SPECIMEN carries the emphasis on a typeface card,
83
+ * so the name steps down. The glyph is what you came to look at. */
84
+ card: { title: 'kol-mono-16 text-body', body: 'kol-mono-12 text-meta', date: 'kol-mono-12 text-meta' },
85
+ /* the name steps DOWN to `body`: on a typeface row the SPECIMEN is the
86
+ * thing you came for and it carries the one emphasis. Two full inks in one
87
+ * block is the ladder broken, and the measurement caught it. */
88
+ row: { title: 'kol-mono-14 uppercase text-body', body: 'kol-mono-12 text-meta', detail: 'kol-mono-14 text-body', date: 'kol-mono-12 text-meta' },
63
89
  },
64
90
  }
65
91
 
@@ -162,17 +188,18 @@ export default function ContentText({
162
188
  * needs exactly that: classification over year at the right edge, which a
163
189
  * flat slot list cannot express and which was previously collapsed into one
164
190
  * `date` slot that lost a value. */
165
- const render = (entry, i) => {
191
+ const render = (entry, i, trailing = false) => {
166
192
  if (typeof entry === 'string') return line(entry)
167
193
  const [kind, ...slots] = entry
168
- const parts = slots.map((s, j) => render(s, j)).filter(Boolean)
194
+ const parts = slots.map((s, j) => render(s, j, kind === 'between' && j === slots.length - 1)).filter(Boolean)
169
195
  if (!parts.length) return null
170
196
  if (kind === 'stack') {
171
- /* items-start + text-left: a trailing column reads DOWN, so its labels
172
- * need one left edge. Right-aligned, `Client` / `Collection` / `Typeface`
173
- * gave the column a ragged edge on the side the eye actually scans. */
197
+ /* A TRAILING column right-aligns (user ruling 2026-08-15) it sits at
198
+ * the row's right edge, so its own right edge is the one that lines up
199
+ * down the list. `items-end` only applies where the stack is trailing;
200
+ * a leading stack still reads from the left. */
174
201
  return (
175
- <div key={`stack-${i}`} className="flex w-full min-w-0 flex-col items-start text-left" style={{ gap: STACK[variant] ?? 'var(--kol-spacing-1)' }}>
202
+ <div key={`stack-${i}`} className={`flex w-full min-w-0 flex-col ${trailing ? 'items-end text-right' : 'items-start text-left'}`} style={{ gap: STACK[variant] ?? 'var(--kol-spacing-1)' }}>
176
203
  {parts}
177
204
  </div>
178
205
  )
@@ -193,7 +220,7 @@ export default function ContentText({
193
220
  * empty space sat between the two columns. */}
194
221
  {kind === 'between' && parts.length > 1
195
222
  ? parts.map((p, j) => (
196
- <div key={j} className={j === 0 ? 'min-w-0 flex-1' : 'shrink-0 text-left'}>{p}</div>
223
+ <div key={j} className={j === 0 ? 'min-w-0 flex-1' : 'shrink-0 text-right'}>{p}</div>
197
224
  ))
198
225
  : parts}
199
226
  </div>