@kolkrabbi/kol-component 0.238.0 → 0.239.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.238.0",
3
+ "version": "0.239.0",
4
4
  "description": "The core component library",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,8 +26,8 @@
26
26
  "embla-carousel-react": "^8.6.0",
27
27
  "pdfjs-dist": "^6.3.289",
28
28
  "react-syntax-highlighter": "^16.1.1",
29
- "@kolkrabbi/kol-markdown": "^0.1.2",
30
- "@kolkrabbi/kol-search": "^0.3.0"
29
+ "@kolkrabbi/kol-search": "^0.3.0",
30
+ "@kolkrabbi/kol-markdown": "^0.1.3"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@kolkrabbi/kol-icons": ">=0.22.0",
@@ -12,7 +12,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
12
12
  *
13
13
  * @param {Object} props
14
14
  * @param {ReactNode} props.children - Button content
15
- * @param {'accent'|'danger'|'nav'} props.variant - The intent; the GROUND is `tone`. `primary · secondary · outline · ghost · grey · control` are deprecated aliases of tones (2026-10-01, on the retirement ledger): pass `tone` — `secondary` is `tone="inverted"`, `control` is `tone="ghost"`, the rest keep their name. Visual variant. UNSET (the default) inherits the tone of the nearest `kol-tone-*` wrapper, else renders primary; the five ground variants are aliases of `tone`. `danger` is the destructive treatment (--ui-error fill); `nav` is the chrome rung — transparent, oq-64 ink, one step brighter than `ghost`; `control` is an alias for `ghost` (legacy call-sites).
15
+ * @param {'accent'|'danger'|'nav'|'tab'} props.variant - The intent; the GROUND is `tone`. `tab` is the tab chip (2026-10-02): quiet text at rest, a soft filled chip while `pressed`. `primary · secondary · outline · ghost · grey · control` are deprecated aliases of tones (2026-10-01, on the retirement ledger): pass `tone` — `secondary` is `tone="inverted"`, `control` is `tone="ghost"`, the rest keep their name. Visual variant. UNSET (the default) inherits the tone of the nearest `kol-tone-*` wrapper, else renders primary; the five ground variants are aliases of `tone`. `danger` is the destructive treatment (--ui-error fill); `nav` is the chrome rung — transparent, oq-64 ink, one step brighter than `ghost`; `control` is an alias for `ghost` (legacy call-sites).
16
16
  * @param {'xs'|'sm'|'md'|'lg'} props.size - Button size (default: 'md'); xs is the panel rung (ControlsXsRung, 2026-09-01) — kol-mono-8 in a 22px shell (20 icon-only), opt-in by prop
17
17
  * @param {string} props.iconLeft - Icon name to display on the left
18
18
  * @param {string} props.iconRight - Icon name to display on the right
@@ -93,7 +93,7 @@ const Button = ({
93
93
  warned.add(variant)
94
94
  console.warn(`Button: variant="${variant}" is deprecated — use tone="${VARIANT_TONE[variant]}"`)
95
95
  }
96
- const KNOWN = ['primary', 'secondary', 'accent', 'outline', 'ghost', 'nav', 'danger', 'grey']
96
+ const KNOWN = ['primary', 'secondary', 'accent', 'outline', 'ghost', 'nav', 'tab', 'danger', 'grey']
97
97
  const known = !resolvedVariant || KNOWN.includes(resolvedVariant)
98
98
  if (!known && import.meta.env.DEV) console.warn(`Button: unknown variant "${resolvedVariant}" — rendering the inherited tone (primary)`)
99
99
  const variantClass = resolvedVariant && known ? `kol-btn-${resolvedVariant}` : ''
@@ -1,3 +1,5 @@
1
+ import Button from '../atoms/Button.jsx'
2
+
1
3
  /**
2
4
  * TabChips — Tabs as a row of chips. the active tab is a filled chip, the rest are quiet text.
3
5
  *
@@ -10,28 +12,37 @@
10
12
  *
11
13
  * Labels render as authored. Selection is the parent's.
12
14
  *
15
+ * ON THE CONTROL SIZE RAMP (user 2026-10-02: *"can we make the tabtoggle align with the size ramp
16
+ * controls use?"*). A chip was a bare button at its own 24px, beside a 26px dropdown in the same
17
+ * bar. Each chip is a `Button variant="tab"` now, so `size` is the ramp's (22 · 26 · 32 · 40) and
18
+ * the chip's look is a Button state anything else can wear.
19
+ *
13
20
  * @param {Array} tabs `[{ id, label }]`
14
21
  * @param {string} value id of the active tab (controlled)
15
22
  * @param {Function} onChange `(id) => void`
23
+ * @param {'xs'|'sm'|'md'|'lg'} [size='sm'] the control ramp's rung
16
24
  * @param {string} [ariaLabel] accessible name for the strip
17
25
  * @param {string} [className] extra classes on the row
18
26
  */
19
- export default function TabChips({ tabs = [], value, onChange, ariaLabel, className = '' }) {
27
+ export default function TabChips({ tabs = [], value, onChange, size = 'sm', ariaLabel, className = '' }) {
20
28
  return (
21
29
  <div className={`flex items-center gap-1 ${className}`.trim()} role="tablist" aria-label={ariaLabel}>
22
30
  {tabs.map((t) => {
23
31
  const active = t.id === value
24
32
  return (
25
- <button
33
+ /* a tab is selected, not pressed — the role's own attribute replaces Button's aria-pressed */
34
+ <Button
26
35
  key={t.id}
27
- type="button"
36
+ variant="tab"
37
+ size={size}
38
+ pressed={active}
28
39
  role="tab"
29
40
  aria-selected={active}
41
+ aria-pressed={undefined}
30
42
  onClick={() => onChange?.(t.id)}
31
- className={`kol-mono-12 rounded-[var(--kol-radius-sm)] px-3 py-1 transition-colors [@media(pointer:coarse)]:min-h-8 ${active ? 'bg-fg-08 text-emphasis' : 'text-meta hover:text-emphasis'}`}
32
43
  >
33
44
  {t.label}
34
- </button>
45
+ </Button>
35
46
  )
36
47
  })}
37
48
  </div>
@@ -47,7 +47,10 @@ import { minHeightClass } from './sectionHeights.js'
47
47
  * @param {ReactNode} media image / video / interactive node for the visual column
48
48
  * @param {string} [ratio='4/5'] aspect ratio of the media frame. THE FRAME IS BOUNDED BY THE
49
49
  * RUNG (SectionSplitMediaBounded, 2026-08-27): its height is the section's `height` rung minus
50
- * the vertical padding, its width follows the ratio (capped at the column). Before, the frame
50
+ * the vertical padding, its width follows the ratio (capped at the column) — the RUNG is the
51
+ * dimension that holds and the WIDTH the one that yields, so a frame can stand narrower than its
52
+ * column; take a taller rung for bigger media. Below 901px, stacked, it is the other way round:
53
+ * the frame is as wide as the column and its height follows the ratio. Before, the frame
51
54
  * was as wide as its column and set the section's height itself, so `40` · `60` · `80`
52
55
  * rendered identically — a ladder that only floors is decorative for a card with media.
53
56
  * @param {boolean} [mediaHover=false] zoom the media on frame hover (the CardFeatureItem numbers)
@@ -251,7 +254,15 @@ export default function SectionSplit({
251
254
  * silently, which is why it was reported as a ProfileCard crop.
252
255
  * Below 901 the frame is `w-full` + ratio: width decides, height
253
256
  * follows, media never clips. ≥901 nothing moves. */
254
- className={`kol-section-split-visual relative w-full max-w-full justify-self-center rounded-[var(--kol-radius-sm)] min-[901px]:h-[calc(var(--kol-section-h,60vh)_-_2*var(--kol-section-py,4rem))] ${mediaClip ? 'overflow-hidden' : ''} ${mediaHover ? 'is-hoverable' : ''} ${mediaFirst ? 'order-1' : ''} ${centred ? 'max-w-[640px]' : ''}`.replace(/\s+/g, ' ').trim()}
257
+ /* …AND THE WIDTH FOLLOWS AGAIN FROM 901 (section-split-frame-ratio-holds-at-desktop,
258
+ * kol-website 2026-10-02; user: *"why is this no longer its original ratio? its
259
+ * squished?"*). `w-full` was written for the stack and applied at every width, so
260
+ * beside the text the frame had BOTH dimensions fixed — the column's width and the
261
+ * rung's height — and `aspect-ratio` had nothing left to decide: a `5/4` frame
262
+ * rendered 2.22 at 1600 and cropped its image. From 901 the width is `auto` again:
263
+ * the rung sets the height, the ratio sets the width, the column caps it — the
264
+ * 2026-08-27 rule written on the `ratio` prop. */
265
+ className={`kol-section-split-visual relative w-full min-[901px]:w-auto max-w-full justify-self-center rounded-[var(--kol-radius-sm)] min-[901px]:h-[calc(var(--kol-section-h,60vh)_-_2*var(--kol-section-py,4rem))] ${mediaClip ? 'overflow-hidden' : ''} ${mediaHover ? 'is-hoverable' : ''} ${mediaFirst ? 'order-1' : ''} ${centred ? 'max-w-[640px]' : ''}`.replace(/\s+/g, ' ').trim()}
255
266
  style={{ aspectRatio: ratio }}
256
267
  >
257
268
  {media ?? (placeholder === true