@kolkrabbi/kol-component 0.158.0 → 0.160.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/Badge.jsx +2 -1
- package/src/atoms/Button.jsx +3 -1
- package/src/atoms/IconFrame.jsx +1 -1
- package/src/atoms/Input.jsx +38 -5
- package/src/atoms/SegmentedToggle.jsx +1 -1
- package/src/atoms/Tag.jsx +2 -2
- package/src/atoms/Textarea.jsx +1 -1
- package/src/atoms/ToggleSwitch.jsx +4 -3
- package/src/hooks/glyphLadders.js +3 -3
- package/src/molecules/Dropdown.jsx +6 -2
- package/src/molecules/MenuItem.jsx +9 -2
- package/src/molecules/SearchInput.jsx +2 -2
- package/src/molecules/Stepper.jsx +27 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.160.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/Badge.jsx
CHANGED
|
@@ -23,12 +23,13 @@ const VARIANT_MAP = {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const SIZE_MAP = {
|
|
26
|
+
xs: 'kol-badge-xs',
|
|
26
27
|
sm: 'kol-badge-sm',
|
|
27
28
|
md: 'kol-badge-md',
|
|
28
29
|
lg: 'kol-badge-lg'
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const ICON_SIZES = { sm: 12, md: 14, lg: 16 }
|
|
32
|
+
const ICON_SIZES = { xs: 10, sm: 12, md: 14, lg: 16 }
|
|
32
33
|
|
|
33
34
|
const Badge = ({
|
|
34
35
|
children,
|
package/src/atoms/Button.jsx
CHANGED
|
@@ -13,7 +13,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
13
13
|
* @param {Object} props
|
|
14
14
|
* @param {ReactNode} props.children - Button content
|
|
15
15
|
* @param {'primary'|'secondary'|'accent'|'outline'|'ghost'|'nav'|'danger'|'grey'|'control'} props.variant - Visual variant. `danger` is the destructive treatment (--ui-error fill); `nav` is the chrome rung — transparent, oq-64 ink, one step brighter than `ghost`; `control` is an alias for `ghost` (legacy call-sites).
|
|
16
|
-
* @param {'sm'|'md'|'lg'} props.size - Button size (default: 'md')
|
|
16
|
+
* @param {'xs'|'sm'|'md'|'lg'} props.size - Button size (default: 'md'); xs is the panel rung (ControlsXsRung, 2026-09-01) — kol-mono-8 in a 22px shell (20 icon-only), opt-in by prop
|
|
17
17
|
* @param {string} props.iconLeft - Icon name to display on the left
|
|
18
18
|
* @param {string} props.iconRight - Icon name to display on the right
|
|
19
19
|
* @param {string} props.iconLeftHover - Icon to show on hover (left position)
|
|
@@ -98,6 +98,8 @@ const Button = ({
|
|
|
98
98
|
// Add size class — pairs the padding rule with its mono type class.
|
|
99
99
|
const sizeClass = size === 'sm'
|
|
100
100
|
? 'kol-btn-sm kol-mono-12'
|
|
101
|
+
: size === 'xs'
|
|
102
|
+
? 'kol-btn-xs kol-mono-8'
|
|
101
103
|
: size === 'lg'
|
|
102
104
|
? 'kol-btn-lg kol-mono-16'
|
|
103
105
|
: 'kol-btn-md kol-mono-14'
|
package/src/atoms/IconFrame.jsx
CHANGED
|
@@ -61,7 +61,7 @@ import { toneClass } from '../utilities/tone.js'
|
|
|
61
61
|
*
|
|
62
62
|
* @param {string} name icon name (kol-icons)
|
|
63
63
|
* @param {string} variant primary|secondary|accent|outline|ghost|nav|grey|danger
|
|
64
|
-
* @param {string} size sm|md|lg — square + glyph together
|
|
64
|
+
* @param {string} size xs|sm|md|lg — square + glyph together (xs = 20px square, 12 glyph; the panel rung, 2026-09-01)
|
|
65
65
|
* @param {string} radius sm (default, the system's 4px) | full (9999px).
|
|
66
66
|
* Two values, nothing between: a round frame is its
|
|
67
67
|
* own chrome idiom (edge-straddling controls, avatars),
|
package/src/atoms/Input.jsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
1
2
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
3
|
import { toneClass } from '../utilities/tone.js'
|
|
3
4
|
import { glyphSize } from '../hooks/glyphLadders.js'
|
|
@@ -11,6 +12,13 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
11
12
|
* secondary, always subordinate to filled)
|
|
12
13
|
* tone="inverse" — the dark chip (`fg-ab-24`) for a washed plane
|
|
13
14
|
* (ControlToneInverse, 2026-08-27); same prop on ViewToggle · Dropdown
|
|
15
|
+
* size="xs" — the panel rung (ControlsXsRung, 2026-09-01):
|
|
16
|
+
* kol-mono-8 in a 22px shell; opt-in by prop
|
|
17
|
+
* onCommit — `(trimmed) => void` on blur / Enter (Escape
|
|
18
|
+
* restores): the rack commits a module name or a
|
|
19
|
+
* scope expression, not every keystroke. With it
|
|
20
|
+
* the field keeps a local draft seeded from
|
|
21
|
+
* `value`; `onChange` still fires live if given.
|
|
14
22
|
* variant="ghost" — legacy alias, resolves to outline
|
|
15
23
|
* variant="property" — the Figma property field (PropertyField,
|
|
16
24
|
* 2026-08-12): filled chrome, dim `affordance`
|
|
@@ -46,12 +54,13 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
46
54
|
* Input owns prefix/suffix/icon layout + the inner <input> styling.
|
|
47
55
|
*/
|
|
48
56
|
|
|
49
|
-
const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
57
|
+
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
50
58
|
|
|
51
59
|
export default function Input({
|
|
52
60
|
type = 'text',
|
|
53
61
|
value,
|
|
54
62
|
onChange,
|
|
63
|
+
onCommit,
|
|
55
64
|
variant = 'filled',
|
|
56
65
|
tone = 'default',
|
|
57
66
|
size = 'md',
|
|
@@ -100,6 +109,26 @@ export default function Input({
|
|
|
100
109
|
* `${len}ch` hugs the value with no probe element, and stays number-safe
|
|
101
110
|
* where the HTML `size` attr is ignored (<input type="number">). +2px keeps
|
|
102
111
|
* the caret from clipping at the end. Tracks `value` → controlled only. */
|
|
112
|
+
/* commit-on-blur/Enter (ControlsXsRung, 2026-09-01 — kol-monitor's panel
|
|
113
|
+
* TextInput collapsing onto this atom): a local draft, seeded from `value`,
|
|
114
|
+
* committed trimmed; Escape restores. Only when `onCommit` is given. */
|
|
115
|
+
const [draft, setDraft] = useState(value ?? '')
|
|
116
|
+
/* the ref mirrors the draft so a blur that lands in the same tick as the
|
|
117
|
+
* last keystroke commits what was typed, not the last RENDER's draft */
|
|
118
|
+
const draftRef = useRef(value ?? '')
|
|
119
|
+
useEffect(() => { if (onCommit) { setDraft(value ?? ''); draftRef.current = value ?? '' } }, [value, onCommit])
|
|
120
|
+
const commitProps = onCommit
|
|
121
|
+
? {
|
|
122
|
+
value: draft,
|
|
123
|
+
onChange: (e) => { draftRef.current = e.target.value; setDraft(e.target.value); onChange?.(e) },
|
|
124
|
+
onBlur: () => onCommit(String(draftRef.current).trim()),
|
|
125
|
+
onKeyDown: (e) => {
|
|
126
|
+
if (e.key === 'Enter') e.currentTarget.blur()
|
|
127
|
+
if (e.key === 'Escape') { draftRef.current = value ?? ''; setDraft(value ?? ''); e.currentTarget.blur() }
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
: null
|
|
131
|
+
|
|
103
132
|
const propertyLen = Math.max(String(value ?? placeholder ?? '').length, 1)
|
|
104
133
|
|
|
105
134
|
/* Pin inner input height to the typography token's line-height. Without
|
|
@@ -108,7 +137,7 @@ export default function Input({
|
|
|
108
137
|
* from the font's ascender+descender (font-metric), not strictly from
|
|
109
138
|
* CSS line-height. Result: kol-control-sm ends up 26.5px instead of 26.
|
|
110
139
|
* h-4 / h-[18px] / h-[22px] match the kol-mono-12 / -14 / -16 line-heights. */
|
|
111
|
-
const heightCls = size === 'sm' ? 'h-4' : size === 'md' ? 'h-[18px]' : 'h-[22px]'
|
|
140
|
+
const heightCls = size === 'xs' ? 'h-3' : size === 'sm' ? 'h-4' : size === 'md' ? 'h-[18px]' : 'h-[22px]'
|
|
112
141
|
|
|
113
142
|
const inputCls = [
|
|
114
143
|
'min-w-0 bg-transparent border-none outline-none text-auto',
|
|
@@ -149,9 +178,11 @@ export default function Input({
|
|
|
149
178
|
* Controlled + no onChange = deliberate display-only → readOnly. */}
|
|
150
179
|
<input
|
|
151
180
|
type={type}
|
|
152
|
-
{...(
|
|
153
|
-
?
|
|
154
|
-
:
|
|
181
|
+
{...(commitProps
|
|
182
|
+
? commitProps
|
|
183
|
+
: value !== undefined
|
|
184
|
+
? { value: value ?? '', onChange, readOnly: !onChange || undefined }
|
|
185
|
+
: { onChange })}
|
|
155
186
|
placeholder={placeholder}
|
|
156
187
|
disabled={disabled}
|
|
157
188
|
spellCheck={false}
|
|
@@ -159,6 +190,8 @@ export default function Input({
|
|
|
159
190
|
className={inputCls}
|
|
160
191
|
style={isProperty ? { width: `calc(${propertyLen}ch + 2px)` } : undefined}
|
|
161
192
|
{...inputProps}
|
|
193
|
+
/* the commit pair LAST, so nothing spread above it can shadow onBlur / onKeyDown */
|
|
194
|
+
{...(commitProps || {})}
|
|
162
195
|
/>
|
|
163
196
|
{unit !== undefined && (
|
|
164
197
|
<span aria-hidden="true" className="text-meta shrink-0">{unit}</span>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* className — additional classes on the outer shell
|
|
48
48
|
*/
|
|
49
49
|
export default function SegmentedToggle({ value, onChange, options = [], variant = 'default', size = 'md', ariaLabel, className = '' }) {
|
|
50
|
-
const cellType = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }[size]
|
|
50
|
+
const cellType = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }[size]
|
|
51
51
|
const stateless = value == null
|
|
52
52
|
const focusIdx = Math.max(0, options.findIndex((opt) => opt.value === value))
|
|
53
53
|
|
package/src/atoms/Tag.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
2
|
|
|
3
|
-
const ICON_SIZES = { sm: 10, md: 12, lg: 14 }
|
|
3
|
+
const ICON_SIZES = { xs: 8, sm: 10, md: 12, lg: 14 }
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Tag — the INTERACTIVE chip: filterable, selectable, removable.
|
|
@@ -35,7 +35,7 @@ const ICON_SIZES = { sm: 10, md: 12, lg: 14 }
|
|
|
35
35
|
* @param {ReactNode} children label content
|
|
36
36
|
* @param {string} text content fallback when no children (brand SwatchControls)
|
|
37
37
|
* @param {string} variant 'primary' | 'secondary' | 'inverse' · `tertiary` = secondary's fill, no outline, mono at fg-80 (2026-08-27)
|
|
38
|
-
* @param {string} size 'sm' | 'md' | 'lg' — `sm` is the default and
|
|
38
|
+
* @param {string} size 'xs' | 'sm' | 'md' | 'lg' — `sm` is the default and
|
|
39
39
|
* should stay the answer; `lg` needs a reason
|
|
40
40
|
* @param {boolean} active selected state (filter chips)
|
|
41
41
|
* @param {boolean} hash prepend `#` (default true); false for plain labels
|
package/src/atoms/Textarea.jsx
CHANGED
|
@@ -31,7 +31,7 @@ import { Icon } from '@kolkrabbi/kol-icons'
|
|
|
31
31
|
* Pairs with `<Input>` for single-line.
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
34
|
+
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
35
35
|
|
|
36
36
|
export default function Textarea({
|
|
37
37
|
value,
|
|
@@ -7,15 +7,16 @@ import React from 'react'
|
|
|
7
7
|
* variant="primary" — filled shell (surface-secondary), button geometry
|
|
8
8
|
* variant="outline" — bordered shell (border-oq-16), button geometry
|
|
9
9
|
*
|
|
10
|
-
* size="sm" / "md" (default) / "lg" — shells match button heights
|
|
11
|
-
* (26/32/40); the track scales with size in all variants.
|
|
10
|
+
* size="xs" / "sm" / "md" (default) / "lg" — shells match button heights
|
|
11
|
+
* (22/26/32/40); the track scales with size in all variants. xs is the
|
|
12
|
+
* panel rung (2026-09-01).
|
|
12
13
|
*
|
|
13
14
|
* Legacy aliases: 'plain' → bare · 'default' (the old boxed look) → outline.
|
|
14
15
|
* Type via kol-mono-{12,14,16}; no auto-uppercase — casing is authored at
|
|
15
16
|
* the call site (KOL no-auto-casing rule).
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
19
|
+
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
19
20
|
const LEGACY_VARIANTS = { plain: 'bare', default: 'outline' }
|
|
20
21
|
|
|
21
22
|
const ToggleSwitch = ({
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/** Icon alone in a pinned square. Pairs with squares 28 · 32 · 36. */
|
|
27
|
-
export const SOLO = { sm: 16, md: 20, lg: 24 }
|
|
27
|
+
export const SOLO = { xs: 12, sm: 16, md: 20, lg: 24 }
|
|
28
28
|
|
|
29
29
|
/** Icon beside a label, inside the rung's line box. */
|
|
30
|
-
export const ADJACENT = { sm: 14, md: 16, lg: 18 }
|
|
30
|
+
export const ADJACENT = { xs: 10, sm: 14, md: 16, lg: 18 }
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Indicator glyph — a caret/chevron that DECORATES a control rather than
|
|
@@ -37,7 +37,7 @@ export const ADJACENT = { sm: 14, md: 16, lg: 18 }
|
|
|
37
37
|
* Dropdown's caret each hand-typed their number (Dropdown took the ADJACENT
|
|
38
38
|
* rung, the oversize the user called): two transcriptions, the folklore
|
|
39
39
|
* threshold. */
|
|
40
|
-
export const INDICATOR = { sm: 12, md: 14, lg: 16 }
|
|
40
|
+
export const INDICATOR = { xs: 8, sm: 12, md: 14, lg: 16 }
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Resolve a glyph size. `solo` picks the ladder; `size` indexes it.
|
|
@@ -32,7 +32,8 @@ import { indicatorSize } from '../hooks/glyphLadders.js'
|
|
|
32
32
|
* (lg ≥1024 / md ≥768 / sm below) is gone.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/* xs (ControlsXsRung, 2026-09-01): the panel rung, opt-in by prop — the default stays sm (2026-07-28 law) */
|
|
36
|
+
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
36
37
|
/* Caret size comes from the INDICATOR ladder (glyphLadders.js) — the private
|
|
37
38
|
* map that lived here was a transcription of ADJACENT, which is the wrong
|
|
38
39
|
* ladder for a decoration: it put a caret one rung HEAVIER than the label
|
|
@@ -183,6 +184,8 @@ const Dropdown = ({
|
|
|
183
184
|
className={`kol-dd-panel kol-dd-panel--${resolvedVariant} ${toneClass(tone)}`.trim()}
|
|
184
185
|
style={{
|
|
185
186
|
'--kol-dd-max-rows': maxRows ?? 10,
|
|
187
|
+
/* xs rows are 20px; the panel's max-height reads the row height */
|
|
188
|
+
...(resolvedSize === 'xs' && rowHeight == null ? { '--kol-dd-row-h': '20px' } : null),
|
|
186
189
|
...(rowHeight != null ? { '--kol-dd-row-h': typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight } : null),
|
|
187
190
|
}}
|
|
188
191
|
>
|
|
@@ -201,11 +204,12 @@ const Dropdown = ({
|
|
|
201
204
|
* all — and this was the last one left, the option row's ink
|
|
202
205
|
* brighten. The check mark is what marks the current value. */
|
|
203
206
|
hover={false}
|
|
207
|
+
size={resolvedSize}
|
|
204
208
|
height={rowHeight}
|
|
205
209
|
onPointerEnter={onOptionHover ? () => reportHover(option.value) : undefined}
|
|
206
210
|
onPointerLeave={onOptionHover ? () => reportHover(null) : undefined}
|
|
207
211
|
onClick={() => handleSelect(option)}
|
|
208
|
-
shortcut={isActive ? <Icon name="check" size={11} /> : undefined}
|
|
212
|
+
shortcut={isActive ? <Icon name="check" size={resolvedSize === 'xs' ? indicatorSize('xs') : 11} /> : undefined}
|
|
209
213
|
>
|
|
210
214
|
{option.label}
|
|
211
215
|
</MenuDropdownItem>
|
|
@@ -99,7 +99,14 @@ export function MenuItem({
|
|
|
99
99
|
* - children — main label, flex-1.
|
|
100
100
|
* - shortcut — trailing content (text shortcut hint, ✓ marker, or icon).
|
|
101
101
|
*/
|
|
102
|
-
|
|
102
|
+
/* the row follows the trigger's rung (DropdownXsList, kol-monitor 2026-09-01):
|
|
103
|
+
* at xs the list kept sm rows — kol-helper-12 in a 32px pitch — inside a panel
|
|
104
|
+
* fused to an 8px-type trigger, so the options truncated. One map, both ends. */
|
|
105
|
+
/* xs wears the TRIGGER's face (kol-mono-8), not the helper: the trigger's ghost
|
|
106
|
+
* stack reserves the widest label in its own face, and a row set in a different
|
|
107
|
+
* one measured 3px wider and clipped the selected row beside its check. */
|
|
108
|
+
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' }
|
|
109
|
+
export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disabled, prefix, iconLeft, shortcut, hover = true, height, size = 'sm', children }) {
|
|
103
110
|
return (
|
|
104
111
|
<button
|
|
105
112
|
type="button"
|
|
@@ -110,7 +117,7 @@ export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disa
|
|
|
110
117
|
disabled={disabled}
|
|
111
118
|
role="menuitem"
|
|
112
119
|
style={height != null ? { height } : undefined}
|
|
113
|
-
className={`w-full
|
|
120
|
+
className={`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`}
|
|
114
121
|
>
|
|
115
122
|
{prefix && <span className="shrink-0 inline-flex items-center">{prefix}</span>}
|
|
116
123
|
{iconLeft && <span className="shrink-0 w-4 inline-flex items-center justify-center">{iconLeft}</span>}
|
|
@@ -33,7 +33,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
33
33
|
* @param {Function} onClear () => void — trailing × click; × renders only when set and value is non-empty
|
|
34
34
|
* @param {string} shortcutHint caller-authored kbd chip (e.g. "⌘K"); shown while the value is empty
|
|
35
35
|
* @param {Function} onFocus (event) => void — input focus
|
|
36
|
-
* @param {string} size 'sm' | 'md' — kol-control size + matched mono type class
|
|
36
|
+
* @param {string} size 'xs' | 'sm' | 'md' — kol-control size + matched mono type class (xs = the panel rung, 2026-09-01)
|
|
37
37
|
* @param {string} variant 'filled' | 'ghost' | 'outline' — kol-control variant, same chrome as Input (ignored when bare/expanding)
|
|
38
38
|
* @param {string} tone 'default' | 'inverse' — the dark chip for a washed plane (ControlToneInverse, 2026-08-27); on the expanding pill it is the OPEN fill
|
|
39
39
|
* @param {boolean} bare borderless inline field for overlay panels (full width, no shell chrome; keeps icon/clear/chip slots)
|
|
@@ -67,7 +67,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
67
67
|
*
|
|
68
68
|
* The flat `{ sm: 14, md: 14 }` it used to carry was the ADJACENT sm rung
|
|
69
69
|
* frozen for both sizes — right ladder, no size. */
|
|
70
|
-
const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14' }
|
|
70
|
+
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14' }
|
|
71
71
|
|
|
72
72
|
export default function SearchInput({
|
|
73
73
|
value = '',
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stepper — number input + chevron buttons, built on the .kol-control shell.
|
|
3
3
|
*
|
|
4
|
-
* size="sm" (default) / "md" / "lg" — matched padding + type class.
|
|
5
|
-
* Chevron scale follows the size: 8 / 10 / 12 px each, stacked.
|
|
4
|
+
* size="xs" / "sm" (default) / "md" / "lg" — matched padding + type class.
|
|
5
|
+
* Chevron scale follows the size: 6 / 8 / 10 / 12 px each, stacked. xs is the
|
|
6
|
+
* panel rung (ControlsXsRung, kol-monitor 2026-09-01).
|
|
7
|
+
*
|
|
8
|
+
* options — step through a LIST instead of a number range (the rack's
|
|
9
|
+
* ‹ value › Selector, as a variant of this on the ladder; user: "selector
|
|
10
|
+
* could be a variant of ours if we make it follow the size ladder"). `value`
|
|
11
|
+
* is one of the options, the field is read-only, the chevrons step and WRAP
|
|
12
|
+
* at both ends, and `onChange` reports the option in the same event shape
|
|
13
|
+
* as the number path — `{ target: { value: option } }`.
|
|
6
14
|
*
|
|
7
15
|
* Loaded from `00-kol/chevron-{up,down}.svg` via the Icon registry — first
|
|
8
16
|
* stroke icons in the kol curated set. For a plain number input without
|
|
@@ -10,8 +18,8 @@
|
|
|
10
18
|
*/
|
|
11
19
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
12
20
|
|
|
13
|
-
const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
14
|
-
const CHEVRON_SIZE = { sm: 8, md: 10, lg: 12 }
|
|
21
|
+
const SIZE_TYPE = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
22
|
+
const CHEVRON_SIZE = { xs: 6, sm: 8, md: 10, lg: 12 }
|
|
15
23
|
|
|
16
24
|
export default function Stepper({
|
|
17
25
|
value,
|
|
@@ -19,18 +27,27 @@ export default function Stepper({
|
|
|
19
27
|
min,
|
|
20
28
|
max,
|
|
21
29
|
step = 1,
|
|
30
|
+
options,
|
|
22
31
|
size = 'sm',
|
|
23
32
|
className = '',
|
|
24
33
|
style = {},
|
|
25
34
|
...props
|
|
26
35
|
}) {
|
|
36
|
+
/* the list path: wrap at both ends, report the option */
|
|
37
|
+
const stepList = (dir) => {
|
|
38
|
+
const i = options.indexOf(value)
|
|
39
|
+
const n = i < 0 ? 0 : (i + dir + options.length) % options.length
|
|
40
|
+
onChange?.({ target: { value: options[n] } })
|
|
41
|
+
}
|
|
27
42
|
const handleIncrement = () => {
|
|
43
|
+
if (options) return stepList(1)
|
|
28
44
|
const newValue = Number(value) + step
|
|
29
45
|
if (max !== undefined && newValue > max) return
|
|
30
46
|
onChange?.({ target: { value: newValue } })
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
const handleDecrement = () => {
|
|
50
|
+
if (options) return stepList(-1)
|
|
34
51
|
const newValue = Number(value) - step
|
|
35
52
|
if (min !== undefined && newValue < min) return
|
|
36
53
|
onChange?.({ target: { value: newValue } })
|
|
@@ -64,12 +81,13 @@ export default function Stepper({
|
|
|
64
81
|
return (
|
|
65
82
|
<div className={shellCls} style={style}>
|
|
66
83
|
<input
|
|
67
|
-
type=
|
|
84
|
+
type={options ? 'text' : 'number'}
|
|
85
|
+
readOnly={options ? true : undefined}
|
|
68
86
|
value={value ?? ''}
|
|
69
|
-
onChange={handleInputChange}
|
|
70
|
-
min={min}
|
|
71
|
-
max={max}
|
|
72
|
-
step={step}
|
|
87
|
+
onChange={options ? undefined : handleInputChange}
|
|
88
|
+
min={options ? undefined : min}
|
|
89
|
+
max={options ? undefined : max}
|
|
90
|
+
step={options ? undefined : step}
|
|
73
91
|
className="w-full min-w-0 bg-transparent border-none outline-none text-auto hide-number-spinners"
|
|
74
92
|
style={{ paddingRight: `${inputPaddingRight}px` }}
|
|
75
93
|
{...props}
|