@kolkrabbi/kol-component 0.237.0 → 0.238.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.238.0",
|
|
4
4
|
"description": "The core component library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"module": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./src/index.js",
|
|
11
|
+
"./atoms/RotaryDial": "./src/molecules/Knob.jsx",
|
|
11
12
|
"./atoms/*": "./src/atoms/*.jsx",
|
|
12
13
|
"./molecules/*": "./src/molecules/*.jsx",
|
|
13
14
|
"./organisms/*": "./src/organisms/*.jsx",
|
|
@@ -25,8 +26,8 @@
|
|
|
25
26
|
"embla-carousel-react": "^8.6.0",
|
|
26
27
|
"pdfjs-dist": "^6.3.289",
|
|
27
28
|
"react-syntax-highlighter": "^16.1.1",
|
|
28
|
-
"@kolkrabbi/kol-
|
|
29
|
-
"@kolkrabbi/kol-
|
|
29
|
+
"@kolkrabbi/kol-markdown": "^0.1.2",
|
|
30
|
+
"@kolkrabbi/kol-search": "^0.3.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@kolkrabbi/kol-icons": ">=0.22.0",
|
package/src/atoms/Input.jsx
CHANGED
|
@@ -159,8 +159,11 @@ export default function Input({
|
|
|
159
159
|
* from the font's ascender+descender (font-metric), not strictly from
|
|
160
160
|
* CSS line-height. Result: kol-control-sm ends up 26.5px instead of 26.
|
|
161
161
|
* h-4 / h-[18px] / h-[22px] match the kol-mono-12 / -14 / -16 line-heights; on touch every size is
|
|
162
|
-
* the 16 / 22 rung (D5, 2026-09-29 — see SearchInput)
|
|
163
|
-
|
|
162
|
+
* the 16 / 22 rung (D5, 2026-09-29 — see SearchInput) — except inside a hardware panel
|
|
163
|
+
* (`.kol-hw-panel`, 2026-10-02): a module's field keeps its desk height on touch, as the rest of
|
|
164
|
+
* the plate does (kol-components-controls.css). The type still goes to 16px there, so iOS does
|
|
165
|
+
* not zoom into it. */
|
|
166
|
+
const heightCls = `${size === 'xs' ? 'h-3' : size === 'sm' ? 'h-4' : size === 'md' ? 'h-[18px]' : 'h-[22px]'} pointer-coarse:not-[.kol-hw-panel_*]:h-[22px]`
|
|
164
167
|
|
|
165
168
|
const inputCls = [
|
|
166
169
|
'min-w-0 bg-transparent border-none outline-none text-auto',
|
|
@@ -36,6 +36,19 @@ import { glyphSize, indicatorSize } from '../hooks/glyphLadders.js'
|
|
|
36
36
|
|
|
37
37
|
/* xs (ControlsXsRung, 2026-09-01): the panel rung, opt-in by prop — the default stays sm (2026-07-28 law) */
|
|
38
38
|
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
39
|
+
/* THE LIST IS THE TRIGGER'S, CONTINUED (2026-10-02 — user: *"the list has different padding then the
|
|
40
|
+
* button"*). Two things were off at every size but one. The rows were set in `kol-helper-12`
|
|
41
|
+
* whatever the trigger wore, and the helper is wider — "Newest first" fitted its trigger and was
|
|
42
|
+
* cut off in its own row. And the rows sat 4px in from the trigger's text, because the list pads
|
|
43
|
+
* itself by 4 on top of the row's padding. So: the row wears the trigger's face, and its side
|
|
44
|
+
* padding is the trigger's (8 · 12 · 16 · 20) less the list's 4. */
|
|
45
|
+
const ROW_PAD = { xs: 'px-1', sm: 'px-2', md: 'px-3', lg: 'px-4' }
|
|
46
|
+
/* …AND THE TRIGGER'S HEIGHT (same day — user: *"is it always the same regardless of size?"*). It was:
|
|
47
|
+
* 32px rows at sm · md · lg whatever the trigger stood (26 · 32 · 40), 20 at xs. A row is the
|
|
48
|
+
* trigger's box now, on the control ramp (22 · 26 · 32 · 40 — `--kol-ctl-*`, so the touch rung and
|
|
49
|
+
* a hardware panel move rows and trigger together). */
|
|
50
|
+
const ROW_H = { xs: 'h-[var(--kol-ctl-xs)]', sm: 'h-[var(--kol-ctl-sm)]', md: 'h-[var(--kol-ctl-md)]', lg: 'h-[var(--kol-ctl-lg)]' }
|
|
51
|
+
const ROW = { xs: `kol-mono-8 ${ROW_PAD.xs} ${ROW_H.xs}`, sm: `kol-mono-12 ${ROW_PAD.sm} ${ROW_H.sm}`, md: `kol-mono-14 ${ROW_PAD.md} ${ROW_H.md}`, lg: `kol-mono-16 ${ROW_PAD.lg} ${ROW_H.lg}` }
|
|
39
52
|
/* Caret size comes from the INDICATOR ladder (glyphLadders.js) — the private
|
|
40
53
|
* map that lived here was a transcription of ADJACENT, which is the wrong
|
|
41
54
|
* ladder for a decoration: it put a caret one rung HEAVIER than the label
|
|
@@ -95,6 +108,11 @@ const Dropdown = ({
|
|
|
95
108
|
* has one implementation and the drawing stays the caller's. */
|
|
96
109
|
triggerAdornment,
|
|
97
110
|
defaultOpen = false,
|
|
111
|
+
/* `stayOpen` — a click outside does not close the list (the trigger and Esc still do). For a
|
|
112
|
+
* surface where the open list is the thing being looked at while other controls are worked:
|
|
113
|
+
* the showcase's preview, with its toolbar pickers beside it (user 2026-10-02: *"can we
|
|
114
|
+
* disclude the preview from that behaviour?"*). */
|
|
115
|
+
stayOpen = false,
|
|
98
116
|
className = ''
|
|
99
117
|
}) => {
|
|
100
118
|
const [isOpen, setIsOpen] = useState(defaultOpen)
|
|
@@ -107,12 +125,26 @@ const Dropdown = ({
|
|
|
107
125
|
* ambient case is the one only this can serve. */
|
|
108
126
|
const triggerRef = useRef(null)
|
|
109
127
|
const [ambient, setAmbient] = useState(null)
|
|
128
|
+
/* …AND SO IS A ZOOM (2026-10-02, kol-monitor's rack — user: *"what about the dropdown in general?
|
|
129
|
+
* its kinda wrong no"*). A trigger inside a zoomed surface (the rack at 80%) is drawn smaller,
|
|
130
|
+
* but the portalled list was not: it kept its full-size type inside a panel pinned to the
|
|
131
|
+
* trigger's SHRUNKEN width, so the names were cut off — 18 of monitor's 40 presets at 80%, all
|
|
132
|
+
* of them at 50%. The trigger's scale is its drawn width over its layout width; the list's
|
|
133
|
+
* content takes the same zoom, so trigger and list stay one piece at any scale. */
|
|
134
|
+
const [scale, setScale] = useState(1)
|
|
135
|
+
const [inPanel, setInPanel] = useState(false)
|
|
110
136
|
useLayoutEffect(() => {
|
|
111
137
|
if (!isOpen || !triggerRef.current) return
|
|
112
138
|
const cs = getComputedStyle(triggerRef.current)
|
|
113
139
|
const vars = {}
|
|
114
140
|
for (const v of TONE_VARS) { const val = cs.getPropertyValue(v).trim(); if (val) vars[v] = val }
|
|
115
141
|
setAmbient(vars)
|
|
142
|
+
const laid = triggerRef.current.offsetWidth
|
|
143
|
+
const s = laid ? triggerRef.current.getBoundingClientRect().width / laid : 1
|
|
144
|
+
setScale(Math.abs(s - 1) > 0.01 ? s : 1)
|
|
145
|
+
/* and the panel it opened from: a hardware plate keeps its desk sizes on touch, and the
|
|
146
|
+
* portalled list would otherwise take the touch rung its trigger skipped */
|
|
147
|
+
setInPanel(!!triggerRef.current.closest('.kol-hw-panel'))
|
|
116
148
|
}, [isOpen])
|
|
117
149
|
|
|
118
150
|
// sm everywhere unless explicitly overridden (see docblock size law).
|
|
@@ -132,6 +164,7 @@ const Dropdown = ({
|
|
|
132
164
|
flip: false,
|
|
133
165
|
matchReferenceWidth: true,
|
|
134
166
|
role: 'listbox',
|
|
167
|
+
dismiss: stayOpen ? { outsidePress: false } : true,
|
|
135
168
|
})
|
|
136
169
|
|
|
137
170
|
/* Width belongs to the CALL SITE (2026-08-09 user call — "width without any
|
|
@@ -241,21 +274,25 @@ const Dropdown = ({
|
|
|
241
274
|
popover={popover}
|
|
242
275
|
panel={false}
|
|
243
276
|
focus={false}
|
|
244
|
-
className={`kol-dd-panel ${resolvedVariant ? `kol-dd-panel--${resolvedVariant}` : ''} ${toneClass(tone)}`.replace(/\s+/g, ' ').trim()}
|
|
277
|
+
className={`kol-dd-panel ${resolvedVariant ? `kol-dd-panel--${resolvedVariant}` : ''} ${toneClass(tone)} ${inPanel ? 'kol-hw-panel' : ''}`.replace(/\s+/g, ' ').trim()}
|
|
245
278
|
style={{
|
|
246
279
|
...ambient,
|
|
247
280
|
'--kol-dd-max-rows': maxRows ?? 10,
|
|
248
|
-
/*
|
|
249
|
-
...(
|
|
281
|
+
/* a row is the trigger's height; the panel's max-height reads it */
|
|
282
|
+
...(rowHeight == null ? { '--kol-dd-row-h': `var(--kol-ctl-${resolvedSize})` } : null),
|
|
250
283
|
...(rowHeight != null ? { '--kol-dd-row-h': typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight } : null),
|
|
251
284
|
}}
|
|
252
285
|
>
|
|
253
|
-
{
|
|
286
|
+
{/* THE HAIRLINE UNDER THE TRIGGER, ALWAYS (2026-10-02 — user: *"I see it sometimes and sometimes
|
|
287
|
+
* not"*). It was drawn only for `variant="primary" | "grey"`: a dropdown toned by `tone=`
|
|
288
|
+
* got none, and an outline one lost its divider too — the panel sits 1px over the trigger's
|
|
289
|
+
* bottom border, which was meant to be it. One rule: an open dropdown has the line. */}
|
|
290
|
+
<div className="kol-dd-div" />
|
|
254
291
|
|
|
255
|
-
<div ref={listRef} className="kol-dd-list" role="listbox">
|
|
292
|
+
<div ref={listRef} className="kol-dd-list" role="listbox" style={scale !== 1 ? { zoom: scale } : undefined}>
|
|
256
293
|
{options.map((option, i) => {
|
|
257
294
|
if (option?.divider) return <MenuDropdownDivider key={`divider-${i}`} />
|
|
258
|
-
if (option?.heading) return <div key={`heading-${i}`} className={`${resolvedSize === 'xs' ? 'kol-mono-8
|
|
295
|
+
if (option?.heading) return <div key={`heading-${i}`} className={`${resolvedSize === 'xs' ? 'kol-mono-8' : 'kol-helper-12'} ${ROW_H[resolvedSize] ?? ROW_H.sm} ${ROW_PAD[resolvedSize] ?? ROW_PAD.sm} flex shrink-0 items-center text-meta`}>{option.heading}</div>
|
|
259
296
|
const isActive = option.value === currentOption?.value
|
|
260
297
|
return (
|
|
261
298
|
<MenuDropdownItem
|
|
@@ -268,6 +305,7 @@ const Dropdown = ({
|
|
|
268
305
|
* brighten. The check mark is what marks the current value. */
|
|
269
306
|
hover={false}
|
|
270
307
|
size={resolvedSize}
|
|
308
|
+
rowClass={ROW[resolvedSize] ?? ROW.sm}
|
|
271
309
|
height={rowHeight}
|
|
272
310
|
onPointerEnter={onOptionHover ? () => reportHover(option.value) : undefined}
|
|
273
311
|
onPointerLeave={onOptionHover ? () => reportHover(null) : undefined}
|
|
@@ -122,7 +122,10 @@ export function MenuItem({
|
|
|
122
122
|
* stack reserves the widest label in its own face, and a row set in a different
|
|
123
123
|
* one measured 3px wider and clipped the selected row beside its check. */
|
|
124
124
|
const ROW_BY_SIZE = { xs: 'kol-mono-8 px-2 h-5', sm: 'kol-helper-12 px-3 h-8', md: 'kol-helper-12 px-3 h-8', lg: 'kol-helper-12 px-3 h-8' }
|
|
125
|
-
|
|
125
|
+
/* `rowClass` — the row's type, side padding and height, whole, in place of the size's own
|
|
126
|
+
* (Dropdown passes it, 2026-10-02: its rows wear the trigger's face and sit on the trigger's
|
|
127
|
+
* text edge; a menu's rows keep `ROW_BY_SIZE`). */
|
|
128
|
+
export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disabled, prefix, iconLeft, shortcut, hover = true, height, size = 'sm', rowClass, children }) {
|
|
126
129
|
return (
|
|
127
130
|
<button
|
|
128
131
|
type="button"
|
|
@@ -133,7 +136,7 @@ export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disa
|
|
|
133
136
|
disabled={disabled}
|
|
134
137
|
role="menuitem"
|
|
135
138
|
style={height != null ? { height } : undefined}
|
|
136
|
-
className={`kol-menu-btn w-full ${ROW_BY_SIZE[size] ?? ROW_BY_SIZE.sm} shrink-0 inline-flex items-center gap-2 text-body ${hover ? 'hover:text-emphasis' : ''} disabled:opacity-40 disabled:cursor-not-allowed text-left`}
|
|
139
|
+
className={`kol-menu-btn w-full ${rowClass ?? ROW_BY_SIZE[size] ?? ROW_BY_SIZE.sm} shrink-0 inline-flex items-center gap-2 text-body ${hover ? 'hover:text-emphasis' : ''} disabled:opacity-40 disabled:cursor-not-allowed text-left`}
|
|
137
140
|
>
|
|
138
141
|
{prefix && <span className="shrink-0 inline-flex items-center">{prefix}</span>}
|
|
139
142
|
{iconLeft && <span className="shrink-0 w-4 inline-flex items-center justify-center">{iconLeft}</span>}
|
|
@@ -90,6 +90,9 @@ export function HighlightMatch({ label, query, ranges }) {
|
|
|
90
90
|
* @param {Function} [onOpenResults] (query) => void — ⌘/Ctrl+Enter: every hit, on the consumer's
|
|
91
91
|
* own results surface. Plain Enter keeps its meaning.
|
|
92
92
|
* @param {string} [resultsLabel] the footer's second line for ⌘Enter (e.g. `All results`)
|
|
93
|
+
* @param {string} [selectLabel] the footer's Enter line while a row is highlighted (default
|
|
94
|
+
* `Go to page`) — a modal whose rows are not pages says what
|
|
95
|
+
* Enter does instead (the rack's module search: `Add module`)
|
|
93
96
|
*/
|
|
94
97
|
export default function ShellSearchOverlay({
|
|
95
98
|
open,
|
|
@@ -112,6 +115,7 @@ export default function ShellSearchOverlay({
|
|
|
112
115
|
enterLabel,
|
|
113
116
|
onOpenResults,
|
|
114
117
|
resultsLabel = 'All results',
|
|
118
|
+
selectLabel = 'Go to page',
|
|
115
119
|
}) {
|
|
116
120
|
const panelRef = useRef(null)
|
|
117
121
|
const listRef = useRef(null)
|
|
@@ -289,7 +293,7 @@ export default function ShellSearchOverlay({
|
|
|
289
293
|
{(results.length > 0 || enterLabel) && (
|
|
290
294
|
<p className="flex items-center gap-2 kol-helper-12 text-fg-48 mx-2 px-4 border-x border-transparent">
|
|
291
295
|
<Kbd icon="corner-down-left" />
|
|
292
|
-
{results.length > 0 ?
|
|
296
|
+
{results.length > 0 ? selectLabel : enterLabel}
|
|
293
297
|
</p>
|
|
294
298
|
)}
|
|
295
299
|
{/* THE RESULTS LINE IS A LINK (user 2026-10-01: *"make 'all results...' at the bottom
|
|
@@ -123,7 +123,9 @@ export function usePopover({
|
|
|
123
123
|
useClick(data.context, { enabled: click }),
|
|
124
124
|
useHover(data.context, { enabled: hover, delay: hoverDelay, move: false }),
|
|
125
125
|
useFocus(data.context, { enabled: focus }),
|
|
126
|
-
|
|
126
|
+
/* `dismiss` may be an object of floating-ui's own options — `{ outsidePress: false }` keeps Esc
|
|
127
|
+
* and drops the outside click (Dropdown `stayOpen`) */
|
|
128
|
+
useDismiss(data.context, typeof dismiss === 'object' && dismiss ? { enabled: true, ...dismiss } : { enabled: dismiss }),
|
|
127
129
|
useRole(data.context, { role }),
|
|
128
130
|
])
|
|
129
131
|
|