@kolkrabbi/kol-component 0.119.0 → 0.121.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 +1 -1
- package/src/atoms/Button.jsx +8 -1
- package/src/atoms/IconFrame.jsx +4 -1
- package/src/atoms/Input.jsx +2 -1
- package/src/atoms/ViewToggle.jsx +6 -2
- package/src/molecules/ContentCard.jsx +9 -3
- package/src/molecules/ContentMedia.jsx +4 -2
- package/src/molecules/Dropdown.jsx +3 -2
- package/src/molecules/SearchInput.jsx +3 -2
- package/src/utilities/tone.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.121.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/atoms/Button.jsx
CHANGED
|
@@ -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
|
-
|
|
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) => {
|
package/src/atoms/IconFrame.jsx
CHANGED
|
@@ -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
|
-
|
|
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
|
package/src/atoms/Input.jsx
CHANGED
|
@@ -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
|
|
94
|
+
toneClass(tone),
|
|
94
95
|
isProperty && 'w-full',
|
|
95
96
|
className,
|
|
96
97
|
].filter(Boolean).join(' ')
|
package/src/atoms/ViewToggle.jsx
CHANGED
|
@@ -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="
|
|
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
|
|
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) => {
|
|
@@ -44,8 +44,11 @@ const BOX = {
|
|
|
44
44
|
* like I've said that before" — the ListingCardThumbBorder ruling): the selected
|
|
45
45
|
* state reads from the checked ToggleCheckbox, not a fg-64 border */
|
|
46
46
|
default: { layout: 'stack', border: null, bg: 'var(--kol-fg-02)', pad: 'var(--kol-pad-card-sm)' },
|
|
47
|
-
/*
|
|
48
|
-
|
|
47
|
+
/* THE FRAME READS BACKWARDS (CatalogCardFrameAndZoom, kol-website 2026-08-28 — user, on a 212-tile
|
|
48
|
+
* grid: no frame at rest; the old rest value is the hover): a wall of fg-04 frames is a grid of boxes,
|
|
49
|
+
* louder than what they hold. Rest `transparent` (the 1px stays, so the hover step never relayouts),
|
|
50
|
+
* hover fg-04. Was fg-04 → fg-16 (ShellHomeSystem, 2026-08-27). */
|
|
51
|
+
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)' },
|
|
49
52
|
/* flip: PrintGridCard's 3D turn on `isFlipped` → `selected` (ContentRowsAndPrintCard, 2026-08-27) */
|
|
50
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 },
|
|
51
54
|
article: { layout: 'stack', border: null, bg: null, pad: '0', mediaGap: 'var(--kol-spacing-4)' },
|
|
@@ -106,6 +109,9 @@ export default function ContentCard({
|
|
|
106
109
|
frame,
|
|
107
110
|
ring,
|
|
108
111
|
zoom,
|
|
112
|
+
/* plateRule (CatalogCardFrameAndZoom): the plate's top hairline — default the variant's (catalog and
|
|
113
|
+
* print draw it); `false` turns it off without an `!important` in a consumer sheet */
|
|
114
|
+
plateRule,
|
|
109
115
|
control,
|
|
110
116
|
controlStart,
|
|
111
117
|
reveal,
|
|
@@ -162,7 +168,7 @@ export default function ContentCard({
|
|
|
162
168
|
'--kol-plate-pad-md': box.padMd,
|
|
163
169
|
padding,
|
|
164
170
|
marginTop: box.layout === 'stack' ? box.mediaGap : undefined,
|
|
165
|
-
borderTop: box.plateTop ? '1px solid var(--kol-fg-04)' : undefined,
|
|
171
|
+
borderTop: (plateRule ?? box.plateTop) ? '1px solid var(--kol-fg-04)' : undefined,
|
|
166
172
|
background: box.layout === 'drawer' ? 'var(--kol-surface-inverse)' : box.plateBg,
|
|
167
173
|
color: box.layout === 'drawer' ? 'var(--kol-fg-inverse)' : undefined,
|
|
168
174
|
position: 'relative',
|
|
@@ -72,7 +72,9 @@ import AssetPlaceholder from '../utilities/AssetPlaceholder.jsx'
|
|
|
72
72
|
* floating in a tall row.
|
|
73
73
|
* @param {boolean} fade an <img> child fades in on load (500ms, house curve) and
|
|
74
74
|
* takes loading="lazy" — PrintGridCard's move (2026-08-27)
|
|
75
|
-
* @param {ReactNode} children the real media
|
|
75
|
+
* @param {ReactNode} children the real media — ANY element: the zoom scales the wrapper's child
|
|
76
|
+
* whatever it is (CatalogCardFrameAndZoom, 2026-08-28 — a glyph on a
|
|
77
|
+
* specimen plate got the class and no motion while the rule named img/video)
|
|
76
78
|
*/
|
|
77
79
|
const FIT = {
|
|
78
80
|
/* a consumer WRAPPER div fills the frame as an img/video does (ColumnBrowser round, 2026-08-27 — kol-r2b2's wrapped thumb fell back to the image's intrinsic size) */
|
|
@@ -129,7 +131,7 @@ export default function ContentMedia({
|
|
|
129
131
|
{fade ? withFade(children) : children}
|
|
130
132
|
{/* OVER the artwork, and inert — a hairline that must not eat the click
|
|
131
133
|
* the card above it is listening for. */}
|
|
132
|
-
{ring && <div className={`pointer-events-none absolute inset-0 border border-fg-08 ${round}`} />}
|
|
134
|
+
{ring && <div className={`kol-media-ring pointer-events-none absolute inset-0 border border-fg-08 ${round}`} />}
|
|
133
135
|
</div>
|
|
134
136
|
)
|
|
135
137
|
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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' : '')
|