@kolkrabbi/kol-component 0.317.0 → 0.317.1
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.1",
|
|
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.1"
|
|
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,9 @@ 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, a 20px glyph, a 48px row, 24 between tabs */
|
|
270
|
+
const md = size === 'md'
|
|
268
271
|
const listRef = useRef(null)
|
|
269
272
|
const activeIdx = tabs.findIndex((t) => t.id === value)
|
|
270
273
|
|
|
@@ -278,14 +281,14 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
|
278
281
|
}
|
|
279
282
|
|
|
280
283
|
return (
|
|
281
|
-
<div className={`flex items-stretch gap-4 ${wrap ? 'min-h-10' : 'h-10'}`}>
|
|
284
|
+
<div className={`flex items-stretch gap-4 ${wrap ? (md ? 'min-h-12' : 'min-h-10') : md ? 'h-12' : 'h-10'}`}>
|
|
282
285
|
{onClose && (
|
|
283
286
|
/* the one close, at the rung whose glyph is the 12 this hand-rolled
|
|
284
287
|
* (2026-09-03) — xs SOLO is 12, so the drawing does not move */
|
|
285
288
|
<CloseButton onClick={onClose} size="xs" className="self-center" />
|
|
286
289
|
)}
|
|
287
290
|
|
|
288
|
-
<div ref={listRef} role="tablist" onKeyDown={onKeyDown} className={`flex items-stretch gap-4${wrap ? ' flex-wrap gap-y-1' : ''}`}>
|
|
291
|
+
<div ref={listRef} role="tablist" onKeyDown={onKeyDown} className={`flex items-stretch ${md ? 'gap-6' : 'gap-4'}${wrap ? ' flex-wrap gap-y-1' : ''}`}>
|
|
289
292
|
{tabs.map((t, i) => {
|
|
290
293
|
const isActive = t.id === value
|
|
291
294
|
return (
|
|
@@ -298,7 +301,7 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
|
298
301
|
tabIndex={isActive || (activeIdx === -1 && i === 0) ? 0 : -1}
|
|
299
302
|
onClick={() => onChange?.(t.id)}
|
|
300
303
|
className={[
|
|
301
|
-
|
|
304
|
+
`group ${md ? 'kol-sans-body-02 gap-2.5' : 'kol-mono-12 gap-2'} flex items-center cursor-pointer border-b-2`,
|
|
302
305
|
isActive
|
|
303
306
|
? 'text-emphasis border-fg'
|
|
304
307
|
: 'text-meta hover:text-emphasis border-transparent',
|
|
@@ -306,7 +309,7 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap }) {
|
|
|
306
309
|
>
|
|
307
310
|
{/* a tab may carry its glyph before the label (2026-10-29, Adobe's Base color · Image · Color wheel) */}
|
|
308
311
|
{/* 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'} />}
|
|
312
|
+
{t.icon && <Icon name={t.icon} size={md ? 20 : 16} className={isActive ? 'text-oq-96' : 'text-oq-48 group-hover:text-oq-96'} />}
|
|
310
313
|
{t.label}
|
|
311
314
|
</button>
|
|
312
315
|
)
|
|
@@ -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,8 +66,8 @@ 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) }}
|
|
@@ -72,7 +75,7 @@ function ChannelStrip({ ch, hex, hsva, onHsva }) {
|
|
|
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
77
|
<div className="w-14 shrink-0">
|
|
75
|
-
<Input size="sm" type="number" value={Math.round(value)} aria-label={`${ch.key} value`} onChange={(e) => { const v = Number(e.target.value); if (!Number.isNaN(v)) set(v) }} />
|
|
78
|
+
<Input size="sm" type="number" 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) }} />
|
|
76
79
|
</div>
|
|
77
80
|
</div>
|
|
78
81
|
)
|
|
@@ -198,36 +201,27 @@ export default function ColorPickerPopover({ color, onChange, defaultValue, size
|
|
|
198
201
|
)
|
|
199
202
|
|
|
200
203
|
if (inline) {
|
|
201
|
-
/*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (next) commit({ ...next, alpha: hsva.alpha })
|
|
210
|
-
} catch { /* cancelled */ }
|
|
211
|
-
}
|
|
204
|
+
/* ADOBE'S BASE COLOR PANEL (review 7, refs 68–72): the label row — the name, the mode as plain text at the right;
|
|
205
|
+
* in HEX, ONE field: the swatch, the hex, the lock (Input's slotLeft · slotRight); the square, THEN the hue strip;
|
|
206
|
+
* the mode's channels under them (RGB · HSB · Lab), and brightness — the ☀ ramp — in HEX and RGB */
|
|
207
|
+
const hexField = (!modes || mode === 'hex') && (
|
|
208
|
+
<Input size="md" prefix="#" maxLength={8} value={draft} onChange={onHex} aria-label={`${label} hex`} width="100%"
|
|
209
|
+
slotLeft={<ColorSwatch hex={isNone ? null : color} size={SWATCH.xs} radius="full" hoverable={false} />}
|
|
210
|
+
slotRight={hexEnd} />
|
|
211
|
+
)
|
|
212
212
|
return hsva && (
|
|
213
213
|
<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}
|
|
214
|
+
<div className="flex items-center justify-between gap-3">
|
|
215
|
+
<span className="kol-sans-body-02 text-emphasis">{title ?? label}</span>
|
|
216
|
+
{modes && <Dropdown variant="nav" size="sm" value={mode} onChange={setMode} options={PICKER_MODES} ariaLabel={`${label} mode`} />}
|
|
224
217
|
</div>
|
|
225
|
-
{
|
|
226
|
-
<HueStrip hue={hsva.hue} onChange={(hue) => commit({ ...hsva, hue })} />
|
|
218
|
+
{hexField}
|
|
227
219
|
<div className="w-full overflow-hidden rounded-[var(--kol-radius-xs)]" style={{ height: 200 }}>
|
|
228
220
|
<SBSquare hue={hsva.hue} sat={hsva.sat} val={hsva.val} onChange={(sat, val) => commit({ ...hsva, sat, val })} />
|
|
229
221
|
</div>
|
|
222
|
+
<HueStrip hue={hsva.hue} onChange={(hue) => commit({ ...hsva, hue })} />
|
|
230
223
|
{modes && mode !== 'hex' && CHANNELS[mode].map((ch) => <ChannelStrip key={ch.key} ch={ch} hex={hsvaToHex({ ...hsva, alpha: 1 })} hsva={hsva} onHsva={commit} />)}
|
|
224
|
+
{(!modes || mode === 'hex' || mode === 'rgb') && <ChannelStrip ch={BRIGHTNESS} hex={hsvaToHex({ ...hsva, alpha: 1 })} hsva={hsva} onHsva={commit} />}
|
|
231
225
|
{alpha && <AlphaStrip alpha={hsva.alpha} color={opaque} onChange={(a) => commit({ ...hsva, alpha: a })} />}
|
|
232
226
|
</div>
|
|
233
227
|
)
|