@kolkrabbi/kol-component 0.3.0 → 0.4.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/README.md +1 -1
- package/package.json +2 -2
- package/src/atoms/Badge.jsx +1 -1
- package/src/atoms/Button.jsx +1 -1
- package/src/atoms/Input.jsx +1 -1
- package/src/atoms/QuantityInput.jsx +76 -114
- package/src/atoms/SearchInput.jsx +1 -1
- package/src/atoms/SectionLabel.jsx +1 -1
- package/src/atoms/Stepper.jsx +1 -1
- package/src/atoms/Tag.jsx +1 -1
- package/src/atoms/Textarea.jsx +1 -1
- package/src/atoms/ViewToggle.jsx +1 -1
- package/src/graphics/Graphic.jsx +1 -1
- package/src/graphics/graphicData.js +1 -1
- package/src/index.js +2 -3
- package/src/molecules/CardFeatureItem.jsx +2 -2
- package/src/molecules/ColorInputRow.jsx +1 -1
- package/src/molecules/Dropdown.jsx +1 -1
- package/src/molecules/MediaCard.jsx +2 -2
- package/src/molecules/MenuItem.jsx +1 -1
- package/src/molecules/MenuPopover.jsx +4 -27
- package/src/molecules/ShapeDropdown.jsx +1 -1
- package/src/molecules/ShellDrawer.jsx +2 -2
- package/src/molecules/SwatchControls.jsx +1 -1
- package/src/molecules/TabsRow.jsx +2 -2
- package/src/molecules/foundry/SpecimenSectionHeader.jsx +2 -2
- package/src/organisms/ContentFilters.jsx +1 -1
- package/src/organisms/MediaViewer.jsx +1 -1
- package/src/organisms/WorkViewToggle.jsx +2 -2
- package/src/atoms/QuantityStepper.jsx +0 -149
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Requires a **Vite + Tailwind v4** app and the theme CSS imported (see the theme
|
|
|
15
15
|
|
|
16
16
|
```jsx
|
|
17
17
|
import { Button, Tag, Badge, Slider, Dropdown, Table } from '@kolkrabbi/kol-component'
|
|
18
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
18
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
19
19
|
|
|
20
20
|
<Button variant="primary" iconLeft="plus">New</Button>
|
|
21
21
|
<Badge variant="success">Active</Badge>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@floating-ui/react": "^0.27.19",
|
|
26
26
|
"embla-carousel-react": "^8.6.0",
|
|
27
|
-
"@kolkrabbi/kol-
|
|
27
|
+
"@kolkrabbi/kol-icons": "0.4.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": "^18.3.0 || ^19.0.0",
|
package/src/atoms/Badge.jsx
CHANGED
package/src/atoms/Button.jsx
CHANGED
package/src/atoms/Input.jsx
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* QuantityInput — a compact integer quantity picker with two control layouts.
|
|
5
|
+
*
|
|
6
|
+
* controls="chevron" (default) — value with a stacked up/down chevron pair
|
|
7
|
+
* on the right, inside a full-width field.
|
|
8
|
+
* controls="split" — a − value + pill (minus and plus flank the
|
|
9
|
+
* value). The former `QuantityStepper`.
|
|
10
|
+
*
|
|
11
|
+
* Display-only value (not a text-editable input) — for an editable number
|
|
12
|
+
* field with bump chevrons use `Stepper`. Size auto-adapts to the viewport
|
|
13
|
+
* (sm < 768 ≤ md < 1024 ≤ lg) unless `size` pins it. Controlled: `value` +
|
|
14
|
+
* `(next) => onChange`, clamped to [min, max].
|
|
15
|
+
*
|
|
16
|
+
* @param {number} value current quantity (default 1)
|
|
17
|
+
* @param {Function} onChange (next) => void
|
|
18
|
+
* @param {number} min lower bound (default 1)
|
|
19
|
+
* @param {number} max upper bound (default 99)
|
|
20
|
+
* @param {string} controls 'chevron' (default) | 'split'
|
|
21
|
+
* @param {string} size 'sm' | 'md' | 'lg' — pins the responsive size
|
|
22
|
+
* @param {string} className extra classes on the root
|
|
23
|
+
*/
|
|
24
|
+
|
|
3
25
|
const SIZE_MAP = {
|
|
4
26
|
sm: { fontSize: 11, paddingY: 12, paddingX: 24, radius: 20, icon: 10 },
|
|
5
27
|
md: { fontSize: 12, paddingY: 14, paddingX: 24, radius: 22, icon: 12 },
|
|
6
|
-
lg: { fontSize: 14, paddingY: 16, paddingX: 24, radius: 24, icon: 14 }
|
|
28
|
+
lg: { fontSize: 14, paddingY: 16, paddingX: 24, radius: 24, icon: 14 },
|
|
7
29
|
}
|
|
8
30
|
|
|
9
31
|
const QuantityInput = ({
|
|
@@ -11,33 +33,21 @@ const QuantityInput = ({
|
|
|
11
33
|
onChange,
|
|
12
34
|
min = 1,
|
|
13
35
|
max = 99,
|
|
36
|
+
controls = 'chevron',
|
|
14
37
|
size,
|
|
15
|
-
className = ''
|
|
38
|
+
className = '',
|
|
16
39
|
}) => {
|
|
17
40
|
const [resolvedSize, setResolvedSize] = useState('md')
|
|
18
41
|
const [componentWidth, setComponentWidth] = useState('180px')
|
|
19
42
|
|
|
20
43
|
useEffect(() => {
|
|
21
44
|
const determineSize = () => {
|
|
22
|
-
if (size) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (typeof window === 'undefined') {
|
|
28
|
-
setResolvedSize('md')
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (window.innerWidth >= 1024) {
|
|
33
|
-
setResolvedSize('lg')
|
|
34
|
-
} else if (window.innerWidth >= 768) {
|
|
35
|
-
setResolvedSize('md')
|
|
36
|
-
} else {
|
|
37
|
-
setResolvedSize('sm')
|
|
38
|
-
}
|
|
45
|
+
if (size) { setResolvedSize(size); return }
|
|
46
|
+
if (typeof window === 'undefined') { setResolvedSize('md'); return }
|
|
47
|
+
if (window.innerWidth >= 1024) setResolvedSize('lg')
|
|
48
|
+
else if (window.innerWidth >= 768) setResolvedSize('md')
|
|
49
|
+
else setResolvedSize('sm')
|
|
39
50
|
}
|
|
40
|
-
|
|
41
51
|
determineSize()
|
|
42
52
|
window.addEventListener('resize', determineSize)
|
|
43
53
|
return () => window.removeEventListener('resize', determineSize)
|
|
@@ -46,14 +56,9 @@ const QuantityInput = ({
|
|
|
46
56
|
useEffect(() => {
|
|
47
57
|
const updateWidth = () => {
|
|
48
58
|
if (typeof window === 'undefined') return
|
|
49
|
-
|
|
50
|
-
if (window.innerWidth >=
|
|
51
|
-
|
|
52
|
-
} else if (window.innerWidth >= 768) {
|
|
53
|
-
setComponentWidth('140px')
|
|
54
|
-
} else {
|
|
55
|
-
setComponentWidth('100px')
|
|
56
|
-
}
|
|
59
|
+
if (window.innerWidth >= 1024) setComponentWidth('180px')
|
|
60
|
+
else if (window.innerWidth >= 768) setComponentWidth('140px')
|
|
61
|
+
else setComponentWidth('100px')
|
|
57
62
|
}
|
|
58
63
|
updateWidth()
|
|
59
64
|
window.addEventListener('resize', updateWidth)
|
|
@@ -61,108 +66,65 @@ const QuantityInput = ({
|
|
|
61
66
|
}, [])
|
|
62
67
|
|
|
63
68
|
const metrics = SIZE_MAP[resolvedSize] || SIZE_MAP.md
|
|
69
|
+
const increment = () => { if (value < max) onChange?.(value + 1) }
|
|
70
|
+
const decrement = () => { if (value > min) onChange?.(value - 1) }
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
/* controls="split" — the − value + pill (formerly QuantityStepper). */
|
|
73
|
+
if (controls === 'split') {
|
|
74
|
+
const btn = (disabled) => ({
|
|
75
|
+
padding: `${metrics.paddingY}px 12px`,
|
|
76
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
77
|
+
backgroundColor: 'transparent', border: 'none',
|
|
78
|
+
color: 'var(--kol-surface-on-primary)',
|
|
79
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
80
|
+
opacity: disabled ? 0.3 : 1,
|
|
81
|
+
transition: 'opacity 0.15s',
|
|
82
|
+
fontFamily: 'var(--kol-font-family-mono)',
|
|
83
|
+
fontSize: `${metrics.fontSize}px`, lineHeight: '120%',
|
|
84
|
+
})
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
className={`flex items-center justify-center ${className}`}
|
|
88
|
+
style={{
|
|
89
|
+
width: componentWidth, minWidth: componentWidth,
|
|
90
|
+
border: '1px solid var(--kol-border-default)',
|
|
91
|
+
borderRadius: `${metrics.radius}px`,
|
|
92
|
+
backgroundColor: 'var(--kol-surface-primary)',
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<button type="button" onClick={decrement} disabled={value <= min} style={btn(value <= min)} aria-label="Decrease quantity">−</button>
|
|
96
|
+
<span style={{ minWidth: '24px', textAlign: 'center', color: 'var(--kol-surface-on-primary)', fontFamily: 'var(--kol-font-family-mono)', fontSize: `${metrics.fontSize}px`, lineHeight: '120%' }}>{value}</span>
|
|
97
|
+
<button type="button" onClick={increment} disabled={value >= max} style={btn(value >= max)} aria-label="Increase quantity">+</button>
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
71
100
|
}
|
|
72
101
|
|
|
102
|
+
/* controls="chevron" (default) — value + stacked chevron pair on the right. */
|
|
73
103
|
return (
|
|
74
|
-
<div
|
|
75
|
-
className={`relative block ${className}`}
|
|
76
|
-
>
|
|
77
|
-
{/* Border wrapper - EXPLICIT 42.5px height */}
|
|
104
|
+
<div className={`relative block ${className}`}>
|
|
78
105
|
<div
|
|
79
106
|
className="w-full flex items-center"
|
|
80
107
|
style={{
|
|
81
|
-
position: 'relative',
|
|
82
|
-
height: '42.5px',
|
|
108
|
+
position: 'relative', height: '42.5px',
|
|
83
109
|
border: '1px solid var(--kol-border-default)',
|
|
84
110
|
borderRadius: `${metrics.radius}px`,
|
|
85
111
|
backgroundColor: 'var(--kol-surface-primary)',
|
|
86
112
|
color: 'var(--kol-surface-on-primary)',
|
|
87
|
-
paddingLeft: `${metrics.paddingX}px`,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
lineHeight: '120%',
|
|
91
|
-
fontFamily: 'var(--kol-font-family-mono)',
|
|
92
|
-
boxSizing: 'border-box'
|
|
113
|
+
paddingLeft: `${metrics.paddingX}px`, paddingRight: `${metrics.paddingX}px`,
|
|
114
|
+
fontSize: `${metrics.fontSize}px`, lineHeight: '120%',
|
|
115
|
+
fontFamily: 'var(--kol-font-family-mono)', boxSizing: 'border-box',
|
|
93
116
|
}}
|
|
94
117
|
>
|
|
95
118
|
<span>{value}</span>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
position: 'absolute',
|
|
101
|
-
right: `${metrics.paddingX}px`,
|
|
102
|
-
top: '50%',
|
|
103
|
-
transform: 'translateY(-50%)',
|
|
104
|
-
display: 'flex',
|
|
105
|
-
flexDirection: 'column'
|
|
106
|
-
}}
|
|
107
|
-
>
|
|
108
|
-
<button
|
|
109
|
-
type="button"
|
|
110
|
-
onClick={increment}
|
|
111
|
-
disabled={value >= max}
|
|
112
|
-
style={{
|
|
113
|
-
backgroundColor: 'transparent',
|
|
114
|
-
border: 'none',
|
|
115
|
-
padding: '0',
|
|
116
|
-
cursor: value >= max ? 'not-allowed' : 'pointer',
|
|
117
|
-
opacity: value >= max ? 0.3 : 1,
|
|
118
|
-
lineHeight: 0,
|
|
119
|
-
display: 'block'
|
|
120
|
-
}}
|
|
121
|
-
aria-label="Increase quantity"
|
|
122
|
-
>
|
|
123
|
-
<svg
|
|
124
|
-
width={metrics.icon}
|
|
125
|
-
height={metrics.icon}
|
|
126
|
-
viewBox="0 0 12 12"
|
|
127
|
-
fill="none"
|
|
128
|
-
>
|
|
129
|
-
<path
|
|
130
|
-
d="m3 7 3-3 3 3"
|
|
131
|
-
stroke="currentColor"
|
|
132
|
-
strokeWidth="1.25"
|
|
133
|
-
strokeLinecap="round"
|
|
134
|
-
strokeLinejoin="round"
|
|
135
|
-
/>
|
|
119
|
+
<div style={{ position: 'absolute', right: `${metrics.paddingX}px`, top: '50%', transform: 'translateY(-50%)', display: 'flex', flexDirection: 'column' }}>
|
|
120
|
+
<button type="button" onClick={increment} disabled={value >= max} style={{ backgroundColor: 'transparent', border: 'none', padding: '0', cursor: value >= max ? 'not-allowed' : 'pointer', opacity: value >= max ? 0.3 : 1, lineHeight: 0, display: 'block' }} aria-label="Increase quantity">
|
|
121
|
+
<svg width={metrics.icon} height={metrics.icon} viewBox="0 0 12 12" fill="none">
|
|
122
|
+
<path d="m3 7 3-3 3 3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
136
123
|
</svg>
|
|
137
124
|
</button>
|
|
138
|
-
<button
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
disabled={value <= min}
|
|
142
|
-
style={{
|
|
143
|
-
backgroundColor: 'transparent',
|
|
144
|
-
border: 'none',
|
|
145
|
-
padding: '0',
|
|
146
|
-
cursor: value <= min ? 'not-allowed' : 'pointer',
|
|
147
|
-
opacity: value <= min ? 0.3 : 1,
|
|
148
|
-
lineHeight: 0,
|
|
149
|
-
display: 'block'
|
|
150
|
-
}}
|
|
151
|
-
aria-label="Decrease quantity"
|
|
152
|
-
>
|
|
153
|
-
<svg
|
|
154
|
-
width={metrics.icon}
|
|
155
|
-
height={metrics.icon}
|
|
156
|
-
viewBox="0 0 12 12"
|
|
157
|
-
fill="none"
|
|
158
|
-
>
|
|
159
|
-
<path
|
|
160
|
-
d="m3 5 3 3 3-3"
|
|
161
|
-
stroke="currentColor"
|
|
162
|
-
strokeWidth="1.25"
|
|
163
|
-
strokeLinecap="round"
|
|
164
|
-
strokeLinejoin="round"
|
|
165
|
-
/>
|
|
125
|
+
<button type="button" onClick={decrement} disabled={value <= min} style={{ backgroundColor: 'transparent', border: 'none', padding: '0', cursor: value <= min ? 'not-allowed' : 'pointer', opacity: value <= min ? 0.3 : 1, lineHeight: 0, display: 'block' }} aria-label="Decrease quantity">
|
|
126
|
+
<svg width={metrics.icon} height={metrics.icon} viewBox="0 0 12 12" fill="none">
|
|
127
|
+
<path d="m3 5 3 3 3-3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
166
128
|
</svg>
|
|
167
129
|
</button>
|
|
168
130
|
</div>
|
package/src/atoms/Stepper.jsx
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* stroke icons in the kol curated set. For a plain number input without
|
|
9
9
|
* bump affordance, use `<Input type="number" />`.
|
|
10
10
|
*/
|
|
11
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
11
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
12
12
|
|
|
13
13
|
const SIZE_TYPE = { sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }
|
|
14
14
|
const CHEVRON_SIZE = { sm: 8, md: 10, lg: 12 }
|
package/src/atoms/Tag.jsx
CHANGED
package/src/atoms/Textarea.jsx
CHANGED
package/src/atoms/ViewToggle.jsx
CHANGED
package/src/graphics/Graphic.jsx
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* The raw SVG strings (~4.8 MB) live in ./graphicData.js behind a single
|
|
8
8
|
* dynamic import — their chunk streams in parallel with boot instead of
|
|
9
|
-
* bloating the consumer's entry chunk (same pattern as kol-
|
|
9
|
+
* bloating the consumer's entry chunk (same pattern as kol-icons's Icon).
|
|
10
10
|
* Until the chunk lands, renders a sized empty box (no layout shift); a
|
|
11
11
|
* genuinely missing asset renders an AssetPlaceholder so it's visible rather
|
|
12
12
|
* than silently empty.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ~4.8 MB of illustration SVGs (some wrap embedded raster images) eager-inline
|
|
5
5
|
* here, but this module is only reached via the dynamic `import()` in
|
|
6
6
|
* Graphic.jsx — the bundler splits it into its own async chunk instead of
|
|
7
|
-
* folding it into the consumer's entry chunk. Same pattern as kol-
|
|
7
|
+
* folding it into the consumer's entry chunk. Same pattern as kol-icons's
|
|
8
8
|
* iconData.js (kol-labs-single 2026-06-19 entry-chunk fix).
|
|
9
9
|
*/
|
|
10
10
|
const svgModules = import.meta.glob('./svg/**/*.svg', { eager: true, query: '?raw', import: 'default' })
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Placement follows the taxonomy rules in
|
|
9
9
|
* docs/documentation/02-components/02-placement.md:
|
|
10
|
-
* atom — nests no KOL component (kol-
|
|
10
|
+
* atom — nests no KOL component (kol-icons Icon/Graphic are
|
|
11
11
|
* infrastructure, they don't count)
|
|
12
12
|
* molecule — nests at least one KOL component
|
|
13
13
|
* organism — a self-contained composed UI region
|
|
@@ -41,7 +41,6 @@ export { usePopover, PopoverPanel, Tooltip } from './atoms/Popover.jsx'
|
|
|
41
41
|
export { default as PriceDisplay } from './atoms/PriceDisplay.jsx'
|
|
42
42
|
export { default as ProsePreview } from './atoms/ProsePreview.jsx'
|
|
43
43
|
export { default as QuantityInput } from './atoms/QuantityInput.jsx'
|
|
44
|
-
export { default as QuantityStepper } from './atoms/QuantityStepper.jsx'
|
|
45
44
|
export { default as RotaryDial } from './atoms/RotaryDial.jsx'
|
|
46
45
|
export { default as SearchInput } from './atoms/SearchInput.jsx'
|
|
47
46
|
export { default as Section } from './atoms/Section.jsx'
|
|
@@ -125,7 +124,7 @@ export { default as SpectrumGrid } from './organisms/SpectrumGrid.jsx'
|
|
|
125
124
|
export { default as Table } from './organisms/Table.jsx'
|
|
126
125
|
|
|
127
126
|
// loaders (re-export — infrastructure, documented on /docs/loaders)
|
|
128
|
-
export { Icon } from '@kolkrabbi/kol-
|
|
127
|
+
export { Icon } from '@kolkrabbi/kol-icons'
|
|
129
128
|
|
|
130
129
|
// graphics (SVG illustration loader — globs its own ./graphics/svg/**)
|
|
131
130
|
export { default as Graphic, GRAPHICS } from './graphics/Graphic.jsx'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
1
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
2
|
|
|
3
|
-
/* taxonomy-ok: nests only kol-
|
|
3
|
+
/* taxonomy-ok: nests only kol-icons's Icon (a package import the
|
|
4
4
|
* relative-import check can't see). */
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
1
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
2
|
|
|
3
|
-
/* taxonomy-ok: nests kol-
|
|
3
|
+
/* taxonomy-ok: nests kol-icons's Icon (a package import the relative-import
|
|
4
4
|
* check can't see) plus the same-file SelectIndicator. */
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -10,34 +10,11 @@ import { MenuItem as MenuTrigger } from './MenuItem.jsx'
|
|
|
10
10
|
* fixed positioning, MenuItem on floating-ui (portal, auto-flip, focus
|
|
11
11
|
* management). One implementation now: MenuItem. This alias keeps existing
|
|
12
12
|
* call-sites working; migrate imports to MenuItem. Removal in the next major.
|
|
13
|
+
*
|
|
14
|
+
* Compose the rows with the exported `MenuDropdownItem` / `MenuDropdownDivider`
|
|
15
|
+
* / `MenuDropdownNest` from MenuItem.jsx — this file no longer ships its own
|
|
16
|
+
* duplicate row/divider components (they were dead: never barrel-exported).
|
|
13
17
|
*/
|
|
14
18
|
export function MenuPopover(props) {
|
|
15
19
|
return <MenuTrigger {...props} />
|
|
16
20
|
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* MenuItem — action row inside a MenuPopover. Renders as a button so it
|
|
20
|
-
* picks up disabled state, focus, and keyboard activation. The popover
|
|
21
|
-
* closes automatically when an item is clicked (via the wrapper's
|
|
22
|
-
* delegate click — the data-menu-item attr marks rows for that match).
|
|
23
|
-
*/
|
|
24
|
-
export function MenuItem({ onClick, disabled, shortcut, iconLeft, children }) {
|
|
25
|
-
return (
|
|
26
|
-
<button
|
|
27
|
-
type="button"
|
|
28
|
-
data-menu-item
|
|
29
|
-
onClick={onClick}
|
|
30
|
-
disabled={disabled}
|
|
31
|
-
role="menuitem"
|
|
32
|
-
className="w-full kol-helper-12 px-3 h-8 inline-flex items-center gap-2 text-meta hover:text-emphasis hover:bg-fg-08 disabled:opacity-40 disabled:cursor-not-allowed text-left"
|
|
33
|
-
>
|
|
34
|
-
{iconLeft && <span className="shrink-0 w-4 inline-flex items-center justify-center text-meta">{iconLeft}</span>}
|
|
35
|
-
<span className="flex-1">{children}</span>
|
|
36
|
-
{shortcut && <span className="kol-helper-10 text-subtle shrink-0">{shortcut}</span>}
|
|
37
|
-
</button>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function MenuDivider() {
|
|
42
|
-
return <div className="border-t border-fg-08 my-1" />
|
|
43
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
2
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
3
3
|
import Button from '../atoms/Button.jsx'
|
|
4
4
|
import { PopoverPanel, usePopover } from '../atoms/Popover.jsx'
|
|
5
5
|
import { MenuDropdownItem } from './MenuItem.jsx'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
3
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
4
4
|
import usePrefersReducedMotion from '../hooks/usePrefersReducedMotion.js'
|
|
5
5
|
|
|
6
|
-
/* taxonomy-ok: nests kol-
|
|
6
|
+
/* taxonomy-ok: nests kol-icons's Icon (a package import the relative-import
|
|
7
7
|
* check can't see). */
|
|
8
8
|
|
|
9
9
|
const FOCUSABLE =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRef } from 'react'
|
|
2
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
2
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
3
3
|
|
|
4
|
-
/* taxonomy-ok: nests kol-
|
|
4
|
+
/* taxonomy-ok: nests kol-icons's Icon */
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* TabsRow — labeled underline tab strip: text tabs where the active tab gets
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
1
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
2
|
import Dropdown from '../Dropdown.jsx'
|
|
3
3
|
import Divider from '../../atoms/Divider.jsx'
|
|
4
4
|
|
|
5
|
-
/* taxonomy-ok: nests DS Dropdown + Divider (relative imports) plus kol-
|
|
5
|
+
/* taxonomy-ok: nests DS Dropdown + Divider (relative imports) plus kol-icons's
|
|
6
6
|
* Icon (a package import the relative-import check can't see). */
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect } from 'react'
|
|
2
2
|
import Tag from '../atoms/Tag.jsx'
|
|
3
3
|
import Divider from '../atoms/Divider.jsx'
|
|
4
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
4
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
5
5
|
import ViewToggle from '../atoms/ViewToggle'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Icon } from '@kolkrabbi/kol-
|
|
2
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
3
3
|
|
|
4
4
|
/* taxonomy-ok: composites a segmented toggle + an inline search into one
|
|
5
|
-
* space-trading control (organism); nests kol-
|
|
5
|
+
* space-trading control (organism); nests kol-icons's Icon (infrastructure). */
|
|
6
6
|
|
|
7
7
|
const CUBIC_EASE = 'cubic-bezier(0.16, 1, 0.3, 1)'
|
|
8
8
|
/* The pill uses a springier overshoot ease, distinct from CUBIC_EASE. */
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
// Match Dropdown SIZE_MAP for consistent height
|
|
4
|
-
const SIZE_MAP = {
|
|
5
|
-
sm: { fontSize: 11, paddingY: 12, paddingX: 24, radius: 20 },
|
|
6
|
-
md: { fontSize: 12, paddingY: 14, paddingX: 24, radius: 22 },
|
|
7
|
-
lg: { fontSize: 14, paddingY: 16, paddingX: 24, radius: 24 }
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const QuantityStepper = ({
|
|
11
|
-
value = 1,
|
|
12
|
-
onChange,
|
|
13
|
-
min = 1,
|
|
14
|
-
max = 10,
|
|
15
|
-
size,
|
|
16
|
-
className = ''
|
|
17
|
-
}) => {
|
|
18
|
-
const [resolvedSize, setResolvedSize] = useState('md')
|
|
19
|
-
const [componentWidth, setComponentWidth] = useState('180px')
|
|
20
|
-
|
|
21
|
-
// Responsive size
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const determineSize = () => {
|
|
24
|
-
if (size) {
|
|
25
|
-
setResolvedSize(size)
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (typeof window === 'undefined') {
|
|
30
|
-
setResolvedSize('md')
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (window.innerWidth >= 1024) {
|
|
35
|
-
setResolvedSize('lg')
|
|
36
|
-
} else if (window.innerWidth >= 768) {
|
|
37
|
-
setResolvedSize('md')
|
|
38
|
-
} else {
|
|
39
|
-
setResolvedSize('sm')
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
determineSize()
|
|
44
|
-
window.addEventListener('resize', determineSize)
|
|
45
|
-
return () => window.removeEventListener('resize', determineSize)
|
|
46
|
-
}, [size])
|
|
47
|
-
|
|
48
|
-
// Width management - match Dropdown
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const updateWidth = () => {
|
|
51
|
-
if (typeof window === 'undefined') return
|
|
52
|
-
|
|
53
|
-
if (window.innerWidth >= 1024) {
|
|
54
|
-
setComponentWidth('180px')
|
|
55
|
-
} else if (window.innerWidth >= 768) {
|
|
56
|
-
setComponentWidth('140px')
|
|
57
|
-
} else {
|
|
58
|
-
setComponentWidth('100px')
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
updateWidth()
|
|
62
|
-
window.addEventListener('resize', updateWidth)
|
|
63
|
-
return () => window.removeEventListener('resize', updateWidth)
|
|
64
|
-
}, [])
|
|
65
|
-
|
|
66
|
-
const metrics = SIZE_MAP[resolvedSize] || SIZE_MAP.md
|
|
67
|
-
|
|
68
|
-
const decrement = () => {
|
|
69
|
-
if (value > min) onChange?.(value - 1)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const increment = () => {
|
|
73
|
-
if (value < max) onChange?.(value + 1)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const buttonStyle = {
|
|
77
|
-
padding: `${metrics.paddingY}px 12px`,
|
|
78
|
-
display: 'flex',
|
|
79
|
-
alignItems: 'center',
|
|
80
|
-
justifyContent: 'center',
|
|
81
|
-
backgroundColor: 'transparent',
|
|
82
|
-
border: 'none',
|
|
83
|
-
color: 'var(--kol-surface-on-primary)',
|
|
84
|
-
cursor: 'pointer',
|
|
85
|
-
transition: 'opacity 0.15s',
|
|
86
|
-
fontFamily: 'var(--kol-font-family-mono)',
|
|
87
|
-
fontSize: `${metrics.fontSize}px`,
|
|
88
|
-
lineHeight: '120%'
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const disabledStyle = {
|
|
92
|
-
opacity: 0.3,
|
|
93
|
-
cursor: 'not-allowed'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<div
|
|
98
|
-
className={`flex items-center justify-center ${className}`}
|
|
99
|
-
style={{
|
|
100
|
-
width: componentWidth,
|
|
101
|
-
minWidth: componentWidth,
|
|
102
|
-
border: '1px solid var(--kol-border-default)',
|
|
103
|
-
borderRadius: `${metrics.radius}px`,
|
|
104
|
-
backgroundColor: 'var(--kol-surface-primary)'
|
|
105
|
-
}}
|
|
106
|
-
>
|
|
107
|
-
<button
|
|
108
|
-
type="button"
|
|
109
|
-
onClick={decrement}
|
|
110
|
-
disabled={value <= min}
|
|
111
|
-
style={{
|
|
112
|
-
...buttonStyle,
|
|
113
|
-
...(value <= min ? disabledStyle : {})
|
|
114
|
-
}}
|
|
115
|
-
aria-label="Decrease quantity"
|
|
116
|
-
>
|
|
117
|
-
−
|
|
118
|
-
</button>
|
|
119
|
-
|
|
120
|
-
<span
|
|
121
|
-
style={{
|
|
122
|
-
minWidth: '24px',
|
|
123
|
-
textAlign: 'center',
|
|
124
|
-
color: 'var(--kol-surface-on-primary)',
|
|
125
|
-
fontFamily: 'var(--kol-font-family-mono)',
|
|
126
|
-
fontSize: `${metrics.fontSize}px`,
|
|
127
|
-
lineHeight: '120%'
|
|
128
|
-
}}
|
|
129
|
-
>
|
|
130
|
-
{value}
|
|
131
|
-
</span>
|
|
132
|
-
|
|
133
|
-
<button
|
|
134
|
-
type="button"
|
|
135
|
-
onClick={increment}
|
|
136
|
-
disabled={value >= max}
|
|
137
|
-
style={{
|
|
138
|
-
...buttonStyle,
|
|
139
|
-
...(value >= max ? disabledStyle : {})
|
|
140
|
-
}}
|
|
141
|
-
aria-label="Increase quantity"
|
|
142
|
-
>
|
|
143
|
-
+
|
|
144
|
-
</button>
|
|
145
|
-
</div>
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export default QuantityStepper
|