@kolkrabbi/kol-component 0.317.0 → 0.317.2
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.317.
|
|
3
|
+
"version": "0.317.2",
|
|
4
4
|
"description": "The core component library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@kolkrabbi/kol-icons": "^0.53.
|
|
41
|
+
"@kolkrabbi/kol-icons": "^0.53.2"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"src",
|
|
@@ -32,7 +32,8 @@ import { RAIL_ROW } from '../hooks/railRow.js'
|
|
|
32
32
|
* Every look but `stacked`, which has its phone bar
|
|
33
33
|
* @param {'underline'|'chip'|'text'|'stacked'} [look='underline']
|
|
34
34
|
* @param {boolean} [filled=false] - `chip` only: every chip shows at rest (Button `tab-filled`), so the row reads as choices
|
|
35
|
-
* underline — this row, as it always was; a tab's `icon` sits before its label
|
|
35
|
+
* underline — this row, as it always was; a tab's `icon` sits before its label. `size="md"` is the sans body
|
|
36
|
+
* face with a 20px glyph in a 48px row (the user's ref, Adobe's tab strip, 2026-10-29)
|
|
36
37
|
* chip — the active tab a filled chip, the rest quiet (was `TabChips`); `size` is the control rung
|
|
37
38
|
* text — flat helper-type tabs, the active one bright (was kol-shell's `TabStrip`); `size` is the
|
|
38
39
|
* helper size (default 14), `tracked` adds 1px tracking
|
|
@@ -76,7 +77,7 @@ export default function TabsRow({ tabs: raw = [], value, onChange, onClose, onMi
|
|
|
76
77
|
if (look === 'chip') return <Overflow mode={overflow} value={value}><ChipTabs tabs={tabs} value={value} onChange={onChange} size={size ?? 'sm'} filled={filled} ariaLabel={ariaLabel} className={`${wrap ? 'flex-wrap ' : ''}${className ?? ''}`} /></Overflow>
|
|
77
78
|
if (look === 'text') return <Overflow mode={overflow} value={value}><TextTabs tabs={tabs} value={value} onChange={onChange} size={size ?? 14} tracked={tracked} className={`${wrap ? 'flex-wrap gap-y-2 ' : ''}${className ?? 'gap-6'}`} style={style} /></Overflow>
|
|
78
79
|
if (look === 'stacked') return <StackedTabs tabs={tabs} value={value} onChange={onChange} placement={placement} shape={shape} collapsed={collapsed} onExpand={onExpand} edge={edge} border={border} shadow={shadow} dock={dock} active={active} foldable={foldable} folded={folded} onFoldChange={onFoldChange} foldSide={foldSide} ariaLabel={ariaLabel} className={className ?? ''} />
|
|
79
|
-
return <Overflow mode={overflow} value={value}><UnderlineTabs tabs={tabs} value={value} onChange={onChange} onClose={onClose} onMinimise={onMinimise} wrap={wrap} /></Overflow>
|
|
80
|
+
return <Overflow mode={overflow} value={value}><UnderlineTabs tabs={tabs} value={value} onChange={onChange} onClose={onClose} onMinimise={onMinimise} wrap={wrap} size={size} /></Overflow>
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/* `overflow="scroll"`: the row in a sideways scroller that fades its clipped edges and keeps the active tab (`data-active`)
|
|
@@ -264,7 +265,10 @@ function StackedTabs({ tabs, value, onChange, placement, shape = 'bar', collapse
|
|
|
264
265
|
)
|
|
265
266
|
}
|
|
266
267
|
|
|
267
|
-
function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
268
|
+
function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap, size }) {
|
|
269
|
+
/* `size="md"` (2026-10-29, the user's ref — Adobe's tab strip): the sans body face at medium weight, a 20px glyph at a
|
|
270
|
+
* 2 stroke (`.kol-tab-md`, the theme), a 48px row, 24 between tabs — review 8: *"the problem … was thickness and stroke"* */
|
|
271
|
+
const md = size === 'md'
|
|
268
272
|
const listRef = useRef(null)
|
|
269
273
|
const activeIdx = tabs.findIndex((t) => t.id === value)
|
|
270
274
|
|
|
@@ -278,14 +282,14 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
return (
|
|
281
|
-
<div className={`flex items-stretch gap-4 ${wrap ? 'min-h-10' : 'h-10'}`}>
|
|
285
|
+
<div className={`flex items-stretch gap-4 ${wrap ? (md ? 'min-h-12' : 'min-h-10') : md ? 'h-12' : 'h-10'}`}>
|
|
282
286
|
{onClose && (
|
|
283
287
|
/* the one close, at the rung whose glyph is the 12 this hand-rolled
|
|
284
288
|
* (2026-09-03) — xs SOLO is 12, so the drawing does not move */
|
|
285
289
|
<CloseButton onClick={onClose} size="xs" className="self-center" />
|
|
286
290
|
)}
|
|
287
291
|
|
|
288
|
-
<div ref={listRef} role="tablist" onKeyDown={onKeyDown} className={`flex items-stretch gap-4${wrap ? ' flex-wrap gap-y-1' : ''}`}>
|
|
292
|
+
<div ref={listRef} role="tablist" onKeyDown={onKeyDown} className={`flex items-stretch ${md ? 'gap-6' : 'gap-4'}${wrap ? ' flex-wrap gap-y-1' : ''}`}>
|
|
289
293
|
{tabs.map((t, i) => {
|
|
290
294
|
const isActive = t.id === value
|
|
291
295
|
return (
|
|
@@ -298,7 +302,7 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
|
298
302
|
tabIndex={isActive || (activeIdx === -1 && i === 0) ? 0 : -1}
|
|
299
303
|
onClick={() => onChange?.(t.id)}
|
|
300
304
|
className={[
|
|
301
|
-
|
|
305
|
+
`group ${md ? 'kol-tab-md kol-sans-body-02 font-medium gap-2.5' : 'kol-mono-12 gap-2'} flex items-center cursor-pointer border-b-2`,
|
|
302
306
|
isActive
|
|
303
307
|
? 'text-emphasis border-fg'
|
|
304
308
|
: 'text-meta hover:text-emphasis border-transparent',
|
|
@@ -306,7 +310,7 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
|
306
310
|
>
|
|
307
311
|
{/* a tab may carry its glyph before the label (2026-10-29, Adobe's Base color · Image · Color wheel) */}
|
|
308
312
|
{/* the glyph in OPAQUE ink (icons take oq, never fg) — full when active, meta otherwise, full under the pointer */}
|
|
309
|
-
{t.icon && <Icon name={t.icon} size={16} className={isActive ? 'text-oq-96' : 'text-oq-48 group-hover:text-oq-96'} />}
|
|
313
|
+
{t.icon && <Icon name={t.icon} size={md ? 20 : 16} className={isActive ? 'text-oq-96' : 'text-oq-48 group-hover:text-oq-96'} />}
|
|
310
314
|
{t.label}
|
|
311
315
|
</button>
|
|
312
316
|
)
|
|
@@ -4,7 +4,7 @@ import Input from '../atoms/Input.jsx'
|
|
|
4
4
|
import Slider from '../molecules/Slider.jsx'
|
|
5
5
|
import Dropdown from '../molecules/Dropdown.jsx'
|
|
6
6
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
7
|
-
import { usePopover, PopoverPanel
|
|
7
|
+
import { usePopover, PopoverPanel } from '../utilities/Popover.jsx'
|
|
8
8
|
import { HueStrip, SBSquare, AlphaStrip } from './SpectrumControls.jsx'
|
|
9
9
|
import { hexToHsva, hsvaToHex } from '../hooks/colorMath.js'
|
|
10
10
|
import { resolveCssColor } from '../hooks/cssVar.js'
|
|
@@ -50,6 +50,9 @@ const CHANNELS = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/* one channel: its letter, a strip painted with the channel's own range at the current colour, a thumb, its number */
|
|
53
|
+
/* brightness — Adobe's ☀ ramp under the hue strip in HEX and RGB: HSV value, black to the colour at full brightness */
|
|
54
|
+
const BRIGHTNESS = { key: 'B', label: 'Brightness', icon: 'brightness', min: 0, max: 100, read: (_, h) => h.val, write: null, hsva: (h, v) => ({ ...h, val: v }) }
|
|
55
|
+
|
|
53
56
|
function ChannelStrip({ ch, hex, hsva, onHsva }) {
|
|
54
57
|
const ref = useRef(null)
|
|
55
58
|
const value = ch.read(hex, hsva)
|
|
@@ -63,17 +66,16 @@ function ChannelStrip({ ch, hex, hsva, onHsva }) {
|
|
|
63
66
|
const pct = ((value - ch.min) / (ch.max - ch.min)) * 100
|
|
64
67
|
return (
|
|
65
68
|
<div className="flex items-center gap-2">
|
|
66
|
-
<span className="w-
|
|
67
|
-
<div ref={ref} role="slider" aria-label={ch.key} aria-valuemin={ch.min} aria-valuemax={ch.max} aria-valuenow={Math.round(value)} tabIndex={0}
|
|
69
|
+
<span className="flex w-4 shrink-0 items-center kol-mono-12 text-meta">{ch.icon ? <Icon name={ch.icon} size={16} className="text-oq-64" /> : ch.key}</span>
|
|
70
|
+
<div ref={ref} role="slider" aria-label={ch.label ?? ch.key} aria-valuemin={ch.min} aria-valuemax={ch.max} aria-valuenow={Math.round(value)} tabIndex={0}
|
|
68
71
|
className="relative h-4 flex-1 cursor-pointer rounded-full touch-none" style={{ background: `linear-gradient(to right, ${stops.join(', ')})` }}
|
|
69
72
|
onPointerDown={(e) => { e.currentTarget.setPointerCapture(e.pointerId); fromPointer(e) }}
|
|
70
73
|
onPointerMove={(e) => { if (e.currentTarget.hasPointerCapture(e.pointerId)) fromPointer(e) }}
|
|
71
74
|
onKeyDown={(e) => { const d = { ArrowRight: 1, ArrowUp: 1, ArrowLeft: -1, ArrowDown: -1 }[e.key]; if (d) { e.preventDefault(); set(value + d * (e.shiftKey ? 10 : 1)) } }}>
|
|
72
75
|
<span className="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full" style={{ left: `${pct}%`, boxShadow: '0 0 0 2px #fff, 0 0 0 3px rgba(0,0,0,0.35)', background: hex.slice(0, 7) }} />
|
|
73
76
|
</div>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
</div>
|
|
77
|
+
{/* the box hugs three characters by the Input's own `chars` — a utility width is not guaranteed in every consumer (review 8, ref 81) */}
|
|
78
|
+
<Input size="sm" type="number" chars={3} value={Math.round(value)} aria-label={`${ch.label ?? ch.key} value`} onChange={(e) => { const v = Number(e.target.value); if (!Number.isNaN(v)) set(v) }} />
|
|
77
79
|
</div>
|
|
78
80
|
)
|
|
79
81
|
}
|
|
@@ -198,36 +200,27 @@ export default function ColorPickerPopover({ color, onChange, defaultValue, size
|
|
|
198
200
|
)
|
|
199
201
|
|
|
200
202
|
if (inline) {
|
|
201
|
-
/*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (next) commit({ ...next, alpha: hsva.alpha })
|
|
210
|
-
} catch { /* cancelled */ }
|
|
211
|
-
}
|
|
203
|
+
/* ADOBE'S BASE COLOR PANEL (review 7, refs 68–72): the label row — the name, the mode as plain text at the right;
|
|
204
|
+
* in HEX, ONE field: the swatch, the hex, the lock (Input's slotLeft · slotRight); the square, THEN the hue strip;
|
|
205
|
+
* the mode's channels under them (RGB · HSB · Lab), and brightness — the ☀ ramp — in HEX and RGB */
|
|
206
|
+
const hexField = (!modes || mode === 'hex') && (
|
|
207
|
+
<Input size="md" prefix="#" maxLength={8} value={draft} onChange={onHex} aria-label={`${label} hex`} width="100%"
|
|
208
|
+
slotLeft={<ColorSwatch hex={isNone ? null : color} size={SWATCH.xs} radius="full" hoverable={false} />}
|
|
209
|
+
slotRight={hexEnd} />
|
|
210
|
+
)
|
|
212
211
|
return hsva && (
|
|
213
212
|
<div className={`flex w-full flex-col gap-3 ${className}`.trim()}>
|
|
214
|
-
<div className="flex items-center gap-3">
|
|
215
|
-
<
|
|
216
|
-
{
|
|
217
|
-
<Tooltip label="Pick a colour from the screen">
|
|
218
|
-
<button type="button" onClick={pick} aria-label="Eyedropper" className="inline-flex cursor-pointer text-oq-64 hover:text-oq-96"><Icon name="eyedrop" size={20} /></button>
|
|
219
|
-
</Tooltip>
|
|
220
|
-
)}
|
|
221
|
-
<span className="ml-auto" />
|
|
222
|
-
<Input size="sm" prefix="#" chars={6} maxLength={8} value={draft} onChange={onHex} aria-label={`${label} hex`} />
|
|
223
|
-
{hexEnd}
|
|
213
|
+
<div className="flex items-center justify-between gap-3">
|
|
214
|
+
<span className="kol-eyebrow text-fg-80">{title ?? label}</span>
|
|
215
|
+
{modes && <Dropdown variant="nav" size="sm" value={mode} onChange={setMode} options={PICKER_MODES} ariaLabel={`${label} mode`} />}
|
|
224
216
|
</div>
|
|
225
|
-
{
|
|
226
|
-
<HueStrip hue={hsva.hue} onChange={(hue) => commit({ ...hsva, hue })} />
|
|
217
|
+
{hexField}
|
|
227
218
|
<div className="w-full overflow-hidden rounded-[var(--kol-radius-xs)]" style={{ height: 200 }}>
|
|
228
219
|
<SBSquare hue={hsva.hue} sat={hsva.sat} val={hsva.val} onChange={(sat, val) => commit({ ...hsva, sat, val })} />
|
|
229
220
|
</div>
|
|
221
|
+
<HueStrip hue={hsva.hue} onChange={(hue) => commit({ ...hsva, hue })} />
|
|
230
222
|
{modes && mode !== 'hex' && CHANNELS[mode].map((ch) => <ChannelStrip key={ch.key} ch={ch} hex={hsvaToHex({ ...hsva, alpha: 1 })} hsva={hsva} onHsva={commit} />)}
|
|
223
|
+
{(!modes || mode === 'hex' || mode === 'rgb') && <ChannelStrip ch={BRIGHTNESS} hex={hsvaToHex({ ...hsva, alpha: 1 })} hsva={hsva} onHsva={commit} />}
|
|
231
224
|
{alpha && <AlphaStrip alpha={hsva.alpha} color={opaque} onChange={(a) => commit({ ...hsva, alpha: a })} />}
|
|
232
225
|
</div>
|
|
233
226
|
)
|