@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 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-loader'
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.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-loader": "0.3.0"
27
+ "@kolkrabbi/kol-icons": "0.4.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": "^18.3.0 || ^19.0.0",
@@ -5,7 +5,7 @@
5
5
  * CSS classes live in components.css under 2-LABELS → Badges.
6
6
  */
7
7
 
8
- import { Icon } from '@kolkrabbi/kol-loader'
8
+ import { Icon } from '@kolkrabbi/kol-icons'
9
9
 
10
10
  const VARIANT_MAP = {
11
11
  default: 'kol-badge-default',
@@ -1,5 +1,5 @@
1
1
  import { isValidElement } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
 
4
4
  /**
5
5
  * Button — canonical KOL button. Emits kol-btn* classes (CSS in @kol/theme).
@@ -1,4 +1,4 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
3
  /**
4
4
  * Input — single-input atom built on the .kol-control shell.
@@ -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
- setResolvedSize(size)
24
- return
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 >= 1024) {
51
- setComponentWidth('180px')
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
- const increment = () => {
66
- if (value < max) onChange?.(value + 1)
67
- }
68
-
69
- const decrement = () => {
70
- if (value > min) onChange?.(value - 1)
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
- paddingRight: `${metrics.paddingX}px`,
89
- fontSize: `${metrics.fontSize}px`,
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
- {/* Chevrons - absolute, OUTSIDE the padded content */}
98
- <div
99
- style={{
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
- type="button"
140
- onClick={decrement}
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>
@@ -1,4 +1,4 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
3
  /**
4
4
  * SearchInput — controlled search field on the .kol-control shell. The
@@ -1,4 +1,4 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
3
  export default function SectionLabel({
4
4
  text,
@@ -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-loader'
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
@@ -1,4 +1,4 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
3
  const ICON_SIZES = { sm: 10, md: 12, lg: 14 }
4
4
 
@@ -1,4 +1,4 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
3
  /**
4
4
  * Textarea — multi-line text atom built on the .kol-control shell with the
@@ -1,4 +1,4 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
3
  /**
4
4
  * ViewToggle — control for switching between view modes.
@@ -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-loader's Icon).
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-loader's
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-loader Icon/Graphic are
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-loader'
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-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
- /* taxonomy-ok: nests only kol-loader's Icon (a package import the
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,5 +1,5 @@
1
1
  import { useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
  import ColorSwatch from './ColorSwatch'
4
4
  import Input from '../atoms/Input'
5
5
  import LabeledControl from '../atoms/LabeledControl'
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
  import { MenuDropdownItem } from './MenuItem.jsx'
4
4
  import { PopoverPanel, usePopover } from '../atoms/Popover.jsx'
5
5
 
@@ -1,6 +1,6 @@
1
- import { Icon } from '@kolkrabbi/kol-loader'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
2
 
3
- /* taxonomy-ok: nests kol-loader's Icon (a package import the relative-import
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
  /**
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
  import { PopoverPanel, usePopover } from '../atoms/Popover.jsx'
4
4
 
5
5
  /**
@@ -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-loader'
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-loader'
3
+ import { Icon } from '@kolkrabbi/kol-icons'
4
4
  import usePrefersReducedMotion from '../hooks/usePrefersReducedMotion.js'
5
5
 
6
- /* taxonomy-ok: nests kol-loader's Icon (a package import the relative-import
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,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
  import ColorSwatch from './ColorSwatch.jsx'
4
4
 
5
5
  /* taxonomy-ok: the default composed control nests the same-file SwatchStack
@@ -1,7 +1,7 @@
1
1
  import { useRef } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
 
4
- /* taxonomy-ok: nests kol-loader's Icon */
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-loader'
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-loader's
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-loader'
4
+ import { Icon } from '@kolkrabbi/kol-icons'
5
5
  import ViewToggle from '../atoms/ViewToggle'
6
6
 
7
7
  /**
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
3
3
  import FullscreenOverlay from '../atoms/FullscreenOverlay.jsx'
4
4
 
5
5
  /**
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
- import { Icon } from '@kolkrabbi/kol-loader'
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-loader's Icon (infrastructure). */
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