@kolkrabbi/kol-component 0.119.0 → 0.120.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.119.0",
3
+ "version": "0.120.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",
@@ -1,4 +1,5 @@
1
1
  import { isValidElement } from 'react'
2
+ import { toneClass } from '../utilities/tone.js'
2
3
  import { Icon } from '@kolkrabbi/kol-icons'
3
4
  import { glyphSize } from '../hooks/glyphLadders.js'
4
5
 
@@ -17,6 +18,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
17
18
  * @param {string} props.iconRight - Icon name to display on the right
18
19
  * @param {string} props.iconLeftHover - Icon to show on hover (left position)
19
20
  * @param {string} props.iconRightHover - Icon to show on hover (right position)
21
+ * @param {'default'|'sunken'} props.tone - `sunken` = the control set's dark well + fg-96 ink (ControlToneSunken); `inverse` aliased
20
22
  * @param {string} props.iconOnly - Icon name for icon-only button
21
23
  * @param {string} props.iconOnlyHover - Icon to show on hover (icon-only)
22
24
  * @param {boolean} props.animateIcon - Disable default hover states to focus on icon animation
@@ -58,6 +60,7 @@ const Button = ({
58
60
  selected = false,
59
61
  iconComponent,
60
62
  pressed,
63
+ tone = 'default',
61
64
  ...props
62
65
  }) => {
63
66
  // Two ladders, split on whether a label sits beside the glyph. An icon-only
@@ -117,7 +120,11 @@ const Button = ({
117
120
  // The rule for `full` is shared with .kol-icon-frame-radius-full, one
118
121
  // selector, so the two controls can never disagree on the value.
119
122
  const radiusClass = radius === 'full' ? 'kol-btn-radius-full' : ''
120
- const combinedClass = `kol-btn ${variantClass} ${sizeClass} ${animateClass} ${quietClass} ${pressedClass} ${iconOnlyClass} ${radiusClass} ${className}`.trim().replace(/\s+/g, ' ')
123
+ /* `tone="sunken"` (ControlToneSunken, 2026-08-28): the dark well + fg-96 ink of the control set, so an
124
+ * icon-only button beside a sunken Dropdown is declared, not painted (kol-website hand-wrote the fill
125
+ * AND guessed the ink — the secondary's dark ink vanished on the forced dark fill). Theme rules. */
126
+ const toneCls = toneClass(tone)
127
+ const combinedClass = `kol-btn ${variantClass} ${sizeClass} ${animateClass} ${quietClass} ${pressedClass} ${iconOnlyClass} ${radiusClass} ${toneCls} ${className}`.trim().replace(/\s+/g, ' ')
121
128
 
122
129
  // Render icon with optional hover state
123
130
  const renderIcon = (iconName, iconHoverName) => {
@@ -1,5 +1,6 @@
1
1
  import { Icon } from '@kolkrabbi/kol-icons'
2
2
  import { SOLO } from '../hooks/glyphLadders.js'
3
+ import { toneClass } from '../utilities/tone.js'
3
4
 
4
5
  /**
5
6
  * IconFrame — a STATIC square frame holding one icon.
@@ -83,13 +84,15 @@ export default function IconFrame({
83
84
  iconSize = null,
84
85
  onClick,
85
86
  href,
87
+ tone = 'default',
86
88
  className = '',
87
89
  ...rest
88
90
  }) {
89
91
  if (!name) return null
90
92
  const radiusCls = radius === 'full' ? ' kol-icon-frame-radius-full' : ''
91
93
  const resolvedIconSize = iconSize ?? GLYPH[size] ?? GLYPH.md
92
- const cls = `kol-icon-frame kol-icon-frame-${variant} kol-icon-frame-${size}${radiusCls} ${className}`.trim()
94
+ /* `tone="sunken"` (ControlToneSunken, 2026-08-28) — the control set's dark well + fg-96 ink, no states, as ever */
95
+ const cls = `kol-icon-frame kol-icon-frame-${variant} kol-icon-frame-${size}${radiusCls} ${toneClass(tone)} ${className}`.replace(/\s+/g, ' ').trim()
93
96
  const glyph = <Icon name={name} size={resolvedIconSize} />
94
97
 
95
98
  /* The element follows the affordance, and the CLASS is identical in all three
@@ -1,4 +1,5 @@
1
1
  import { Icon } from '@kolkrabbi/kol-icons'
2
+ import { toneClass } from '../utilities/tone.js'
2
3
  import { glyphSize } from '../hooks/glyphLadders.js'
3
4
 
4
5
  /**
@@ -90,7 +91,7 @@ export default function Input({
90
91
  SIZE_TYPE[size],
91
92
  'cursor-text',
92
93
  /* the dark chip on a washed plane (ControlToneInverse, kol-website 2026-08-27) */
93
- tone === 'inverse' && 'kol-tone-inverse',
94
+ toneClass(tone),
94
95
  isProperty && 'w-full',
95
96
  className,
96
97
  ].filter(Boolean).join(' ')
@@ -1,4 +1,5 @@
1
1
  import { Icon } from '@kolkrabbi/kol-icons'
2
+ import { toneClass } from '../utilities/tone.js'
2
3
 
3
4
  /**
4
5
  * ViewToggle — control for switching between view modes.
@@ -19,7 +20,10 @@ import { Icon } from '@kolkrabbi/kol-icons'
19
20
  * 14px). For `variant="single"`, the FIRST option in `options` is the "off"
20
21
  * value; the SECOND is "on".
21
22
  *
22
- * `tone="inverse"` (ControlToneInverse, kol-website 2026-08-27 user: "a
23
+ * `tone="sunken"` `inverse` is its alias since 0.120.0 (ControlToneSunken,
24
+ * 2026-08-28: the control does not invert, it sits BELOW its plane; the
25
+ * active chip is `fg-08` now, down from `fg-16`). As first ruled:
26
+ * (ControlToneInverse, kol-website 2026-08-27 — user: "a
23
27
  * flipped version of this color scheme, where the darker is background and grey
24
28
  * is the active … it would fit better on the light grey"): on a washed plane
25
29
  * (`pageWash`, `fg-04`) the default grey well reads as a second plate, so the
@@ -70,7 +74,7 @@ const ViewToggle = ({
70
74
  }
71
75
 
72
76
  const containerClasses = isIconVariant
73
- ? `kol-view-toggle inline-flex items-center gap-1 p-1 bg-surface-secondary rounded ${tone === 'inverse' ? 'kol-tone-inverse' : ''} ${className}`.replace(/\s+/g, ' ').trim()
77
+ ? `kol-view-toggle inline-flex items-center gap-1 p-1 bg-surface-secondary rounded ${toneClass(tone)} ${className}`.replace(/\s+/g, ' ').trim()
74
78
  : `flex gap-2 ${className}`
75
79
 
76
80
  const buttonClasses = (isActive) => {
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
+ import { toneClass } from '../utilities/tone.js'
2
3
  import { Icon } from '@kolkrabbi/kol-icons'
3
4
  import { MenuDropdownItem } from './MenuItem.jsx'
4
5
  import { PopoverPanel, usePopover } from '../utilities/Popover.jsx'
@@ -103,7 +104,7 @@ const Dropdown = ({
103
104
  'kol-dd-trigger',
104
105
  isOpen && 'kol-dd-trigger--open',
105
106
  /* the dark chip on a washed plane; the panel continues it (ControlToneInverse, 2026-08-27) */
106
- tone === 'inverse' && 'kol-tone-inverse',
107
+ toneClass(tone),
107
108
  ].filter(Boolean).join(' ')
108
109
 
109
110
  return (
@@ -137,7 +138,7 @@ const Dropdown = ({
137
138
  popover={popover}
138
139
  panel={false}
139
140
  focus={false}
140
- className={`kol-dd-panel kol-dd-panel--${resolvedVariant} ${tone === 'inverse' ? 'kol-tone-inverse' : ''}`.trim()}
141
+ className={`kol-dd-panel kol-dd-panel--${resolvedVariant} ${toneClass(tone)}`.trim()}
141
142
  >
142
143
  {(resolvedVariant === 'primary' || resolvedVariant === 'grey') && <div className="kol-dd-div" />}
143
144
 
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
+ import { toneClass } from '../utilities/tone.js'
2
3
  import { Icon } from '@kolkrabbi/kol-icons'
3
4
  import { glyphSize } from '../hooks/glyphLadders.js'
4
5
 
@@ -156,7 +157,7 @@ export default function SearchInput({
156
157
  control: sm 28 · md 32 · lg 36 (hooks/glyphLadders.js). This was a
157
158
  hardcoded 36 — the LG square — so an expanding search sat beside a
158
159
  `kol-btn-md` filter button at two different sizes. */
159
- className={`kol-expand flex items-center rounded-full ${isOpen ? (tone === 'inverse' ? 'kol-tone-inverse' : 'bg-fg-04') : ''} ${className}`.trim()}
160
+ className={`kol-expand flex items-center rounded-full ${isOpen ? (toneClass(tone) ? 'kol-tone-sunken' : 'bg-fg-04') : ''} ${className}`.trim()}
160
161
  style={{ height: isOpen ? fieldH : square, width: isOpen ? expandedWidth : square }}
161
162
  >
162
163
  {/* THE GLYPH IS THE CLOSED STATE, and only that (user ruling
@@ -198,7 +199,7 @@ export default function SearchInput({
198
199
  const shellCls = [
199
200
  bare
200
201
  ? 'flex w-full gap-2.5 px-4 py-3'
201
- : `kol-control kol-control--${variant} kol-control-${size} gap-2${tone === 'inverse' ? ' kol-tone-inverse' : ''}`,
202
+ : `kol-control kol-control--${variant} kol-control-${size} gap-2${toneClass(tone) ? ' kol-tone-sunken' : ''}`,
202
203
  'items-center cursor-text',
203
204
  SIZE_TYPE[size],
204
205
  className,
@@ -0,0 +1,8 @@
1
+ /**
2
+ * toneClass — the control set's ONE sunken tone (ControlToneSunken,
3
+ * kol-website 2026-08-28; user ruling: it is not "inverse" — the control does
4
+ * not invert anything, it sits BELOW the plane it is on — call it `sunken`).
5
+ * `inverse` (0.117.0's name) is an alias: every consumer on it renders the same
6
+ * pixel. The rules are kol-theme's `.kol-tone-sunken`.
7
+ */
8
+ export const toneClass = (tone) => (tone === 'sunken' || tone === 'inverse' ? 'kol-tone-sunken' : '')