@kolkrabbi/kol-component 0.128.0 → 0.129.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.128.0",
3
+ "version": "0.129.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",
@@ -36,7 +36,7 @@
36
36
  "react-router-dom": "^6.0.0 || ^7.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@kolkrabbi/kol-icons": "^0.24.0"
39
+ "@kolkrabbi/kol-icons": "^0.25.0"
40
40
  },
41
41
  "files": [
42
42
  "src",
@@ -9,7 +9,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
9
9
  * variant="outline" — bordered, transparent bg — THE secondary
10
10
  * treatment (2026-07-08 chrome law: one
11
11
  * secondary, always subordinate to filled)
12
- * tone="inverse" — the dark chip (`fg-absolute-24`) for a washed plane
12
+ * tone="inverse" — the dark chip (`fg-ab-24`) for a washed plane
13
13
  * (ControlToneInverse, 2026-08-27); same prop on ViewToggle · Dropdown
14
14
  * variant="ghost" — legacy alias, resolves to outline
15
15
  * variant="property" — the Figma property field (PropertyField,
@@ -7,7 +7,7 @@ import { toneClass } from '../utilities/tone.js'
7
7
  * variant="text" — segmented bare buttons; active uses kol-control--filled,
8
8
  * inactive is bare text-meta with text-emphasis on hover.
9
9
  * variant="icon" — bordered chip-row container holding square icon
10
- * buttons. Active uses bg-fg-absolute-24.
10
+ * buttons. Active uses bg-fg-ab-24.
11
11
  * variant="single" — single button binary toggle. Click flips between the
12
12
  * two `options` values. The button shows the *current*
13
13
  * option's label; active = on (kol-control--filled),
@@ -28,7 +28,7 @@ import { toneClass } from '../utilities/tone.js'
28
28
  * is the active … it would fit better on the light grey"): on a washed plane
29
29
  * (`pageWash`, `fg-04`) the default grey well reads as a second plate, so the
30
30
  * icon variant's two values swap — the well takes the dark chip
31
- * (`fg-absolute-24`), the active chip the `fg-16` ink wash (user, on the live
31
+ * (`fg-ab-24`), the active chip the `fg-16` ink wash (user, on the live
32
32
  * page: "just use bg-fg-16"), the inactive hover a lighter wash. Theme rules on `.kol-tone-inverse`
33
33
  * (kol-theme ≥0.78.0); the same prop on `Dropdown` and `Input`.
34
34
  */
@@ -84,10 +84,10 @@ const ViewToggle = ({
84
84
  const buttonClasses = (isActive) => {
85
85
  if (isIconVariant) {
86
86
  // Inset "well" pattern — container is bg-fg-04 (slight lift from page),
87
- // active button uses bg-fg-absolute-24 (theme-invariant black, always
87
+ // active button uses bg-fg-ab-24 (theme-invariant black, always
88
88
  // darkens regardless of theme) so it reads as recessed/pressed.
89
89
  return `inline-flex items-center justify-center p-1.5 rounded transition-colors text-emphasis cursor-pointer ${
90
- isActive ? 'bg-fg-absolute-24' : 'hover:bg-fg-absolute-08'
90
+ isActive ? 'bg-fg-ab-24' : 'hover:bg-fg-ab-08'
91
91
  }`
92
92
  }
93
93
  /* Active = filled chip. Inactive = bare-text on the shell base — no
@@ -22,7 +22,7 @@ function useCover(src) {
22
22
  *
23
23
  * cover the same as VideoSheet — the artwork is the frame (`w-[min(78vh,100vw_-_10rem)]`,
24
24
  * square), the bar floating over its bottom edge (inset 16, radius 12)
25
- * sheet the QuickTime window — a dark plate (`bg-fg-absolute-88`, `min(100vw − 10rem, 1000px)`),
25
+ * sheet the QuickTime window — a dark plate (`bg-fg-ab-88`, `min(100vw − 10rem, 1000px)`),
26
26
  * the cover square left, `Time: mm:ss` beside it, the bar flush along the bottom
27
27
  *
28
28
  * Artwork = the file's embedded ID3 `APIC` cover (`readCover`); no cover → a light
@@ -39,14 +39,14 @@ export default function AudioSheet({ src, onDuration, variant = 'cover' }) {
39
39
  const audio = <audio ref={ref} src={src} autoPlay preload="metadata" {...handlers} />
40
40
  const art = cover
41
41
  ? <img src={cover} alt="" className="w-full h-full object-cover" />
42
- : <div className="w-full h-full flex items-center justify-center bg-absolute-white text-fg-absolute-32"><Icon name="music-note" size={64} /></div>
42
+ : <div className="w-full h-full flex items-center justify-center bg-ab-white text-fg-ab-32"><Icon name="music-note" size={64} /></div>
43
43
 
44
44
  if (variant === 'sheet') return (
45
- <div className="relative w-[min(100vw_-_10rem,1000px)] rounded overflow-hidden bg-fg-absolute-88">
45
+ <div className="relative w-[min(100vw_-_10rem,1000px)] rounded overflow-hidden bg-fg-ab-88">
46
46
  {audio}
47
47
  <div className="flex items-center gap-16 p-10 pb-24">
48
48
  <div className="w-[min(50vh,380px)] aspect-square shrink-0 overflow-hidden">{art}</div>
49
- <span className="kol-mono-16 text-absolute-white"><span className="opacity-48">Time: </span><b className="opacity-80">{clock(bar.duration)}</b></span>
49
+ <span className="kol-mono-16 text-ab-white"><span className="opacity-48">Time: </span><b className="opacity-80">{clock(bar.duration)}</b></span>
50
50
  </div>
51
51
  <PlaybackBar {...bar} place="left-0 right-0 bottom-0" />
52
52
  </div>
@@ -116,7 +116,7 @@ export default function ColorInputRow({
116
116
  {chip('stretch')}
117
117
  <span
118
118
  aria-hidden="true"
119
- className={`absolute inset-0 inline-flex items-center justify-center rounded-[var(--kol-radius-xs)] transition-opacity bg-fg-absolute-48 text-white pointer-events-none ${
119
+ className={`absolute inset-0 inline-flex items-center justify-center rounded-[var(--kol-radius-xs)] transition-opacity bg-fg-ab-48 text-white pointer-events-none ${
120
120
  locked ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
121
121
  }`}
122
122
  >
@@ -17,7 +17,7 @@ import { indicatorSize } from '../hooks/glyphLadders.js'
17
17
  * same fill (one piece: no border, no gap, hairline divider inside)
18
18
  * variant="grey" — oq-12 filled trigger (opaque per the fill
19
19
  * law); panel continues it
20
- * tone="inverse" — the dark chip (`fg-absolute-24`) for a washed
20
+ * tone="inverse" — the dark chip (`fg-ab-24`) for a washed
21
21
  * plane, the panel continuing it (ControlToneInverse,
22
22
  * kol-website 2026-08-27); same prop on ViewToggle · Input
23
23
  * variant="outline" — bordered trigger; open panel carries the
@@ -27,7 +27,7 @@ export function SelectIndicator({ on = false }) {
27
27
  style={
28
28
  on
29
29
  ? { background: 'var(--kol-surface-on-primary)', borderColor: 'var(--kol-surface-on-primary)' }
30
- : { background: 'var(--kol-fg-absolute-16, rgba(0,0,0,0.15))', borderColor: 'var(--kol-fg-absolute-48, rgba(0,0,0,0.4))' }
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
31
  }
32
32
  aria-hidden="true"
33
33
  >
@@ -76,7 +76,7 @@ export default function MediaCard({
76
76
  {selectMode ? (
77
77
  <span
78
78
  className="kol-frame-control kol-frame-control--top-left rounded p-1"
79
- style={{ background: 'var(--kol-fg-absolute-12, rgba(0,0,0,0.4))', backdropFilter: 'blur(4px)' }}
79
+ style={{ background: 'var(--kol-fg-ab-12, rgba(0,0,0,0.4))', backdropFilter: 'blur(4px)' }}
80
80
  >
81
81
  <SelectIndicator on={selected} />
82
82
  </span>
@@ -85,8 +85,8 @@ export default function MediaCard({
85
85
  href={downloadHref}
86
86
  aria-label="Download"
87
87
  title="Download"
88
- className="kol-frame-control inline-flex items-center justify-center w-8 h-8 rounded text-emphasis hover:bg-fg-absolute-24 transition-colors"
89
- style={{ background: 'var(--kol-fg-absolute-12, rgba(0,0,0,0.4))', backdropFilter: 'blur(4px)' }}
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
90
  onClick={(e) => e.stopPropagation()}
91
91
  >
92
92
  <Icon name="download" size={16} />
@@ -13,7 +13,7 @@ const RATES = [1, 1.5, 2]
13
13
  /**
14
14
  * PlaybackBar — the QuickTime bar, ruled against the reference (PlaybackBarAndAudioSheet,
15
15
  * kol-r2b2 2026-08-27; supersedes 0.107.0's strip): a frosted strip over media —
16
- * `bg-fg-absolute-48 backdrop-blur-xl`, **radius 12** (`rounded-xl`, the user's
16
+ * `bg-fg-ab-48 backdrop-blur-xl`, **radius 12** (`rounded-xl`, the user's
17
17
  * ruling on this surface — the 4px container law stands elsewhere), `h-16 px-8
18
18
  * gap-7` — white glyphs whatever the theme (`.kol-playback-bar`, kol-theme ≥0.75.0:
19
19
  * opacity .8, 1 on hover / focus-visible), the transport cluster (`skip-back-15` ·
@@ -44,7 +44,7 @@ export default function PlaybackBar({ playing, time, duration, rate = 1, onToggl
44
44
  const pop = usePopover({ open, onOpenChange: setOpen, placement: 'top' })
45
45
  const nextRate = RATES[(RATES.indexOf(rate) + 1) % RATES.length]
46
46
  return (
47
- <div className={`kol-playback-bar absolute h-16 px-8 flex items-center gap-7 bg-fg-absolute-48 backdrop-blur-xl text-absolute-white ${place}`}>
47
+ <div className={`kol-playback-bar absolute h-16 px-8 flex items-center gap-7 bg-fg-ab-48 backdrop-blur-xl text-ab-white ${place}`}>
48
48
  <div className="flex items-center gap-5">
49
49
  <IconFrame name="skip-back-15" variant="ghost" size="sm" onClick={() => onSeek(time - 15)} aria-label="Back 15 seconds" />
50
50
  <IconFrame name={playing ? 'pause' : 'play'} variant="ghost" size="sm" onClick={onToggle} aria-label={playing ? 'Pause' : 'Play'} />
@@ -98,7 +98,7 @@ function Media({ src, poster, className }) {
98
98
  * @param {boolean} alignRight right-align the card (ms-auto) vs fill (size-full)
99
99
  * @param {boolean} enableTilt master tilt switch (default true)
100
100
  * @param {string} titleClassName title classes — the family default is `kol-sans-heading-01
101
- * text-absolute-white` (confirmed 2026-08-27; the fork's was
101
+ * text-ab-white` (confirmed 2026-08-27; the fork's was
102
102
  * heading-02 uppercase — a consumer wanting that passes it)
103
103
  * @param {string} contentClassName inner content-box classes
104
104
  * @param {string} imageClassName media fit/position classes
@@ -118,7 +118,7 @@ export default function TiltBento({
118
118
  overlayOpacity = 60,
119
119
  alignRight = false,
120
120
  enableTilt = true,
121
- titleClassName = 'kol-sans-heading-01 text-absolute-white',
121
+ titleClassName = 'kol-sans-heading-01 text-ab-white',
122
122
  contentClassName = 'max-w-[384px]',
123
123
  imageClassName = 'object-cover object-center',
124
124
  contentStackClassName = 'relative z-20 h-full flex flex-col justify-start items-start gap-4 p-6 md:p-8',
@@ -173,8 +173,8 @@ export default function TiltBento({
173
173
  {/* kol-mono-14 — one rung above the mono-12 description, as the retired
174
174
  * fork drew it (TiltBentoVoiceAndDefaults, kol-website 2026-08-27: this
175
175
  * rode `kol-mono-text`, a class retired from the theme — zero rules emitted) */}
176
- {subtitle && <p className={`kol-mono-14 text-absolute-white ${revealClass}`}>{subtitle}</p>}
177
- {description && <p className={`kol-mono-12 text-absolute-white pb-6 ${revealClass}`}>{description}</p>}
176
+ {subtitle && <p className={`kol-mono-14 text-ab-white ${revealClass}`}>{subtitle}</p>}
177
+ {description && <p className={`kol-mono-12 text-ab-white pb-6 ${revealClass}`}>{description}</p>}
178
178
  {bodyContent}
179
179
  {href && (
180
180
  <div className={revealClass}>
@@ -192,7 +192,7 @@ function MediaInspector({ files, index, onClose, onPrev, onNext, mediaUrl, downl
192
192
  if (!o) return null
193
193
  const kind = kindOf(o)
194
194
  const poster = kind === 'video' ? posterFor(o.key, keySet) : null
195
- const ARROW = 'fixed top-1/2 -translate-y-1/2 z-10 w-10 h-10 flex items-center justify-center rounded text-fg-48 hover:text-fg-default hover:bg-fg-absolute-24 transition-colors'
195
+ const ARROW = 'fixed top-1/2 -translate-y-1/2 z-10 w-10 h-10 flex items-center justify-center rounded text-fg-48 hover:text-fg-default hover:bg-fg-ab-24 transition-colors'
196
196
  return (
197
197
  <FullscreenOverlay open onClose={onClose} closeButton={false}>
198
198
  <button type="button" className={`${ARROW} left-6`} onClick={onPrev} disabled={files.length <= 1} aria-label="Previous"><Icon name="chevron-left" size={22} /></button>
@@ -150,10 +150,16 @@ export function LabeledControlSection({ label, divided = false, rowGap = 2, chil
150
150
  /** SettingsChoice — the row's one-of-N control: the DS Dropdown, sm · primary
151
151
  * (SettingsPanelEyebrowAndDropdowns — user: "put the toggles inside a dropdown,
152
152
  * because it's super messy like it is"). Options are values or `{ value, label }`;
153
- * width is the call site's (`className="w-40"`). */
154
- export function SettingsChoice({ options = [], value, onChange, ariaLabel, className = '' }) {
153
+ * width is the call site's (`className="w-40"`).
154
+ *
155
+ * `tone` · `size` · `variant` pass through (kol-fxr 2026-08-28): they were
156
+ * hardcoded, so a settings page ruled sunken could not be told — and kol-fxr
157
+ * rebuilt this same Dropdown in a local `ChoiceRow`, which is the exact
158
+ * duplication this component exists to prevent. A component that wraps another
159
+ * has to forward the wrapped one's seams or it becomes a wall. */
160
+ export function SettingsChoice({ options = [], value, onChange, ariaLabel, tone = 'default', size = 'sm', variant = 'primary', className = '' }) {
155
161
  const opts = options.map((o) => (o != null && typeof o === 'object' ? o : { value: o, label: String(o) }))
156
- return <Dropdown size="sm" variant="primary" value={value} onChange={onChange} options={opts} className={`w-full ${className}`.trim()} aria-label={ariaLabel} />
162
+ return <Dropdown size={size} variant={variant} tone={tone} value={value} onChange={onChange} options={opts} className={`w-full ${className}`.trim()} aria-label={ariaLabel} />
157
163
  }
158
164
 
159
165
  /* THE CONTROL CHIP (SettingsPanelCompliance, user 2026-08-27: "WROOOONG" on the
@@ -173,13 +179,13 @@ export const chipCls = (on) => `${CHIP_CLS} ${on ? 'kol-control--filled' : 'text
173
179
  * @param {Function} onToggle (value) => void
174
180
  * @param {string} noun the trigger's noun (default 'kinds')
175
181
  */
176
- export function SettingsMulti({ options = [], selected = [], onToggle, noun = 'kinds', className = '' }) {
182
+ export function SettingsMulti({ options = [], selected = [], onToggle, noun = 'kinds', tone = 'default', size = 'sm', className = '' }) {
177
183
  const on = selected instanceof Set ? selected : new Set(selected)
178
184
  const opts = [
179
185
  { value: '__summary', label: `${on.size} of ${options.length} ${noun}` },
180
186
  ...options.map((o) => ({ value: o.value, label: `${on.has(o.value) ? '✓ ' : ''}${o.label ?? String(o.value)}` })),
181
187
  ]
182
- return <Dropdown size="sm" variant="primary" value="__summary" options={opts} onChange={(v) => (v === '__summary' ? null : onToggle?.(v))} className={`w-full ${className}`.trim()} />
188
+ return <Dropdown size={size} variant="primary" tone={tone} value="__summary" options={opts} onChange={(v) => (v === '__summary' ? null : onToggle?.(v))} className={`w-full ${className}`.trim()} />
183
189
  }
184
190
 
185
191
  /**
@@ -1,7 +1,7 @@
1
1
  /* ONE surface prop across the section family (SectionBackgroundProp, kol-website
2
2
  * 2026-08-27 — user: "ALL section family should pass bg as props"). A named
3
3
  * surface, `none`, or a raw utility / token string for the odd case
4
- * (`'bg-fg-absolute-16'`). Each section's default is what it painted before,
4
+ * (`'bg-fg-ab-16'`). Each section's default is what it painted before,
5
5
  * so nothing moves on the bump. `theme` keeps scoping ink; this is the paint. */
6
6
  export const SURFACES = {
7
7
  primary: 'bg-surface-primary',