@kolkrabbi/kol-component 0.123.0 → 0.125.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.
|
|
3
|
+
"version": "0.125.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",
|
|
@@ -47,6 +47,33 @@ const Dropdown = ({
|
|
|
47
47
|
size,
|
|
48
48
|
variant = 'primary',
|
|
49
49
|
tone = 'default',
|
|
50
|
+
/* DropdownGhostWidthAndListHeight (kol-mirror 2026-08-28 — user, on the
|
|
51
|
+
* studio's 30-option Source picker in a ~300px shelf: "1 it's way too tall,
|
|
52
|
+
* 2 it's not fitting"):
|
|
53
|
+
* `maxRows` how many rows the panel shows before the list scrolls
|
|
54
|
+
* (default 10). Popover's size middleware clamps the panel to
|
|
55
|
+
* the VIEWPORT, which does nothing for a 30-row slab opened at
|
|
56
|
+
* the top of a tall page — and its inline maxHeight cannot be
|
|
57
|
+
* overridden from a consumer stylesheet, so the ceiling is ours.
|
|
58
|
+
* `rowHeight` the row pitch (number = px, or any CSS length) for a chrome
|
|
59
|
+
* whose own rows are shorter than the h-8 rung. Inline on the
|
|
60
|
+
* row, because `h-8` is a utility and a rule cannot out-rank it.
|
|
61
|
+
* Both write variables on the panel that `.kol-dd-list` reads. */
|
|
62
|
+
maxRows = 10,
|
|
63
|
+
rowHeight,
|
|
64
|
+
/* `onOptionHover(value | null)` (DropdownOptionHoverPreview, kol-mirror
|
|
65
|
+
* 2026-08-28): the pointer entering a row reports its value, leaving reports
|
|
66
|
+
* `null` — so a picker over a VISUAL setting can preview the hovered option
|
|
67
|
+
* live and revert on leave. mirror's blend-mode picker applies the hovered
|
|
68
|
+
* mode to the composite: you scrub 16 modes against the actual image instead
|
|
69
|
+
* of committing to each one to look at it. The same case is every picker over
|
|
70
|
+
* easing curves, palettes, filters, fonts — the DS owns the panel, only the
|
|
71
|
+
* consumer knows what to preview, so this reports and nothing else.
|
|
72
|
+
*
|
|
73
|
+
* `null` fires ON CLOSE too, and that half is load-bearing: a panel dismissed
|
|
74
|
+
* while a row is hovered would otherwise leave the consumer previewing
|
|
75
|
+
* forever. It never fires while closed — a closed dropdown has no rows. */
|
|
76
|
+
onOptionHover,
|
|
50
77
|
defaultOpen = false,
|
|
51
78
|
className = ''
|
|
52
79
|
}) => {
|
|
@@ -82,6 +109,21 @@ const Dropdown = ({
|
|
|
82
109
|
setIsOpen(false)
|
|
83
110
|
}
|
|
84
111
|
|
|
112
|
+
/* the hover report, and the close that ends it. `hovered` is a ref, not
|
|
113
|
+
* state — the preview is the consumer's business and re-rendering the panel
|
|
114
|
+
* on every row crossed would be a render per pointermove for nothing. */
|
|
115
|
+
const hovered = useRef(null)
|
|
116
|
+
const reportHover = (v) => {
|
|
117
|
+
if (hovered.current === v) return
|
|
118
|
+
hovered.current = v
|
|
119
|
+
onOptionHover?.(v)
|
|
120
|
+
}
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (isOpen || hovered.current == null) return
|
|
123
|
+
hovered.current = null
|
|
124
|
+
onOptionHover?.(null)
|
|
125
|
+
}, [isOpen]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
126
|
+
|
|
85
127
|
const currentOption = options.find((opt) => opt.value === value) || options[0]
|
|
86
128
|
|
|
87
129
|
/* A clamped list (Popover caps the panel to the viewport) can open with the
|
|
@@ -108,7 +150,7 @@ const Dropdown = ({
|
|
|
108
150
|
].filter(Boolean).join(' ')
|
|
109
151
|
|
|
110
152
|
return (
|
|
111
|
-
<div className={`relative inline-block align-middle ${className}`}>
|
|
153
|
+
<div className={`kol-dd-root relative inline-block align-middle ${className}`}>
|
|
112
154
|
<button
|
|
113
155
|
ref={popover.refs.setReference}
|
|
114
156
|
{...popover.getReferenceProps()}
|
|
@@ -139,6 +181,10 @@ const Dropdown = ({
|
|
|
139
181
|
panel={false}
|
|
140
182
|
focus={false}
|
|
141
183
|
className={`kol-dd-panel kol-dd-panel--${resolvedVariant} ${toneClass(tone)}`.trim()}
|
|
184
|
+
style={{
|
|
185
|
+
'--kol-dd-max-rows': maxRows ?? 10,
|
|
186
|
+
...(rowHeight != null ? { '--kol-dd-row-h': typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight } : null),
|
|
187
|
+
}}
|
|
142
188
|
>
|
|
143
189
|
{(resolvedVariant === 'primary' || resolvedVariant === 'grey') && <div className="kol-dd-div" />}
|
|
144
190
|
|
|
@@ -155,6 +201,9 @@ const Dropdown = ({
|
|
|
155
201
|
* all — and this was the last one left, the option row's ink
|
|
156
202
|
* brighten. The check mark is what marks the current value. */
|
|
157
203
|
hover={false}
|
|
204
|
+
height={rowHeight}
|
|
205
|
+
onPointerEnter={onOptionHover ? () => reportHover(option.value) : undefined}
|
|
206
|
+
onPointerLeave={onOptionHover ? () => reportHover(null) : undefined}
|
|
158
207
|
onClick={() => handleSelect(option)}
|
|
159
208
|
shortcut={isActive ? <Icon name="check" size={11} /> : undefined}
|
|
160
209
|
>
|
|
@@ -79,6 +79,11 @@ export function MenuItem({
|
|
|
79
79
|
/**
|
|
80
80
|
* MenuDropdownItem — action row inside a MenuItem's dropdown panel.
|
|
81
81
|
*
|
|
82
|
+
* `height` (default none — the `h-8` rung) — an inline row height, for a panel
|
|
83
|
+
* in a chrome whose own rows are shorter (DropdownGhostWidthAndListHeight,
|
|
84
|
+
* kol-mirror 2026-08-28: its controls sit in 24px rows and the DS row was a
|
|
85
|
+
* third taller). Inline because `h-8` is a utility and a rule cannot out-rank it.
|
|
86
|
+
*
|
|
82
87
|
* `hover` (default true) — `false` drops the ink brighten, for a panel ruled
|
|
83
88
|
* to carry NO hover state at all (user 2026-08-28, on `Dropdown`: "delete any
|
|
84
89
|
* hover state on dropdowns"). Scoped to the caller: a MenuItem's own menu keeps
|
|
@@ -94,14 +99,17 @@ export function MenuItem({
|
|
|
94
99
|
* - children — main label, flex-1.
|
|
95
100
|
* - shortcut — trailing content (text shortcut hint, ✓ marker, or icon).
|
|
96
101
|
*/
|
|
97
|
-
export function MenuDropdownItem({ onClick, disabled, prefix, iconLeft, shortcut, hover = true, children }) {
|
|
102
|
+
export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disabled, prefix, iconLeft, shortcut, hover = true, height, children }) {
|
|
98
103
|
return (
|
|
99
104
|
<button
|
|
100
105
|
type="button"
|
|
101
106
|
data-menu-item
|
|
102
107
|
onClick={onClick}
|
|
108
|
+
onPointerEnter={onPointerEnter}
|
|
109
|
+
onPointerLeave={onPointerLeave}
|
|
103
110
|
disabled={disabled}
|
|
104
111
|
role="menuitem"
|
|
112
|
+
style={height != null ? { height } : undefined}
|
|
105
113
|
className={`w-full kol-helper-12 px-3 h-8 shrink-0 inline-flex items-center gap-2 text-body ${hover ? 'hover:text-emphasis' : ''} disabled:opacity-40 disabled:cursor-not-allowed text-left`}
|
|
106
114
|
>
|
|
107
115
|
{prefix && <span className="shrink-0 inline-flex items-center">{prefix}</span>}
|