@kolkrabbi/kol-component 0.170.0 → 0.171.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-component",
3
- "version": "0.170.0",
3
+ "version": "0.171.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/index.js CHANGED
@@ -22,6 +22,7 @@ export { default as AssetPlaceholder } from './utilities/AssetPlaceholder.jsx'
22
22
  export { default as Avatar } from './atoms/Avatar.jsx'
23
23
  export { default as Badge } from './atoms/Badge.jsx'
24
24
  export { default as Button } from './atoms/Button.jsx'
25
+ export { default as CloseButton } from './utilities/CloseButton.jsx'
25
26
  export { default as ActionButton } from './atoms/ActionButton.jsx'
26
27
  export { default as SizeOrDownload } from './atoms/SizeOrDownload.jsx'
27
28
  export { default as SortHeader } from './atoms/SortHeader.jsx'
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
3
  import { Icon } from '@kolkrabbi/kol-icons'
4
- import Button from '../atoms/Button.jsx'
4
+ import CloseButton from '../utilities/CloseButton.jsx'
5
5
  import usePrefersReducedMotion from '../hooks/usePrefersReducedMotion.js'
6
6
 
7
7
  /* taxonomy-ok: nests kol-icons's Icon (a package import the relative-import
@@ -197,14 +197,7 @@ export default function ShellDrawer({
197
197
  {closeSide === 'start' && (
198
198
  /* bare glyph, no container (user 2026-08-09: "the X close icon
199
199
  * does not need a container") — body ink at rest, emphasis on hover */
200
- <button
201
- type="button"
202
- aria-label="Close"
203
- onClick={onClose}
204
- className="inline-flex shrink-0 cursor-pointer border-0 bg-transparent p-0 text-body transition-colors duration-150 hover:text-emphasis"
205
- >
206
- <Icon name="x" size={18} />
207
- </button>
200
+ <CloseButton onClick={onClose} size={closeSize} className="shrink-0" />
208
201
  )}
209
202
  {header != null && <div className="min-w-0 flex-1">{header}</div>}
210
203
  {/* A NORMAL ICON BUTTON (user 2026-09-02: *"it should just be like a
@@ -214,14 +207,7 @@ export default function ShellDrawer({
214
207
  * the md rung's 32px around a glyph six under it, and it read as an
215
208
  * oversized empty square. No override: the rung sets both. */}
216
209
  {closeSide !== 'start' && (
217
- <Button
218
- variant="nav"
219
- size={closeSize}
220
- iconOnly="x"
221
- onClick={onClose}
222
- aria-label="Close"
223
- className="ml-auto shrink-0"
224
- />
210
+ <CloseButton onClick={onClose} size={closeSize} className="ml-auto shrink-0" />
225
211
  )}
226
212
  </div>
227
213
  <div className="flex-1 overflow-y-auto pr-1" style={{ overflowAnchor: 'none' }}>
@@ -1,4 +1,5 @@
1
1
  import { useRef } from 'react'
2
+ import CloseButton from '../utilities/CloseButton.jsx'
2
3
  import { Icon } from '@kolkrabbi/kol-icons'
3
4
 
4
5
  /* taxonomy-ok: nests kol-icons's Icon */
@@ -36,15 +37,9 @@ export default function TabsRow({ tabs = [], value, onChange, onClose, onMinimis
36
37
  return (
37
38
  <div className="flex items-stretch gap-4 h-10">
38
39
  {onClose && (
39
- <button
40
- type="button"
41
- onClick={onClose}
42
- aria-label="Close"
43
- className="text-meta hover:text-emphasis self-center"
44
- style={{ lineHeight: 0 }}
45
- >
46
- <Icon name="x" size={12} />
47
- </button>
40
+ /* the one close, at the rung whose glyph is the 12 this hand-rolled
41
+ * (2026-09-03) — xs SOLO is 12, so the drawing does not move */
42
+ <CloseButton onClick={onClose} size="xs" className="self-center" />
48
43
  )}
49
44
 
50
45
  <div ref={listRef} role="tablist" onKeyDown={onKeyDown} className="flex items-stretch gap-4">
@@ -0,0 +1,77 @@
1
+ import Button from '../atoms/Button.jsx'
2
+ import IconFrame from '../atoms/IconFrame.jsx'
3
+
4
+ /**
5
+ * CloseButton — the X that dismisses a thing. One component, so there is one.
6
+ *
7
+ * WHY IT EXISTS (user 2026-09-03: *"why are you FUCKING MAKING INDIVIDUAL
8
+ * CHANGES this isnt difficult, this is a button component yes or no"*). It is a
9
+ * Button, and that was the problem: "close" was never a component, it was four
10
+ * props retyped at four call sites, so the four drifted —
11
+ *
12
+ * ShellDrawer Button variant="nav" size="md" (+ a second,
13
+ * hand-rolled <button> with an 18px icon for closeSide="start")
14
+ * FullscreenOverlay Button variant="nav", Button's md default
15
+ * TabsRow a hand-rolled <button> with a 12px icon and its own hover
16
+ * ShellLayout Button variant="outline" quiet size="sm"
17
+ *
18
+ * — three sizes and two variants for one control, and `ShellLayout` was still
19
+ * shipping the boxed outline that the 2026-09-01 one-idiom ruling retired. The
20
+ * ruling was written down and then re-typed wrong three times, which is the
21
+ * `HEADER_ICON` lesson again: a number is advice, a component is the only thing
22
+ * that makes the box unwritable by hand.
23
+ *
24
+ * THE IDIOM, in one place: `variant="nav"` — bare glyph, no box, the wash on
25
+ * hover — at `sm` (26), the same rung as the controls a close usually sits
26
+ * beside. `size` is here because a close in a taller chrome row legitimately
27
+ * takes that row's rung; it is the ONLY thing a call site may vary, and it must
28
+ * be a rung, never an `iconSize`. The glyph follows from the rung.
29
+ *
30
+ * UTILITY, by the placement test (2026-08-09): an X on a canvas alone means
31
+ * nothing — it is only ever worn BY the thing it dismisses, the same row
32
+ * `FullscreenOverlay` is held on. It is also the only tier that works: a close
33
+ * is needed by a utility (`FullscreenOverlay`), a molecule (`ShellDrawer`,
34
+ * `TabsRow`) and a package shell alike, and utilities are the one folder every
35
+ * tier may import while importing only atoms itself.
36
+ *
37
+ * TWO BASES, ONE IDIOM (user 2026-09-03: *"sometimes you dont want states"*).
38
+ * `states` (default true) is a Button — hover wash, press, focus ring — which is
39
+ * what a close in reachable chrome should be. `states={false}` is `IconFrame`,
40
+ * which has NO interactive states by contract: for a close that is decoration
41
+ * over something already dismissible, or one inside a surface that owns the
42
+ * whole hit area, where a lighting-up X is noise. Same glyph, same variant, same
43
+ * rung either way — only the state machine differs, which is exactly the
44
+ * distinction IconFrame was promoted for (2026-08-01).
45
+ *
46
+ * @param {Function} onClick dismiss
47
+ * @param {boolean} states interactive states (default true → Button); false → IconFrame, no states
48
+ * @param {'xs'|'sm'|'md'|'lg'} size the rung (default 'sm'); match the row it sits in
49
+ * @param {string} label accessible name (default 'Close')
50
+ * @param {string} className extra classes — positioning, never chrome
51
+ */
52
+ export default function CloseButton({ onClick, size = 'sm', label = 'Close', states = true, className = '', ...rest }) {
53
+ if (!states) {
54
+ return (
55
+ <IconFrame
56
+ name="x"
57
+ variant="nav"
58
+ size={size}
59
+ onClick={onClick}
60
+ aria-label={label}
61
+ className={className}
62
+ {...rest}
63
+ />
64
+ )
65
+ }
66
+ return (
67
+ <Button
68
+ variant="nav"
69
+ size={size}
70
+ iconOnly="x"
71
+ onClick={onClick}
72
+ aria-label={label}
73
+ className={className}
74
+ {...rest}
75
+ />
76
+ )
77
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from 'react'
2
- import Button from '../atoms/Button.jsx'
2
+ import CloseButton from './CloseButton.jsx'
3
3
 
4
4
  /**
5
5
  * FullscreenOverlay — the scrim + centred sheet every overlay in the repo
@@ -74,14 +74,7 @@ export default function FullscreenOverlay({ open, onClose, closeButton = true, c
74
74
  * tap away from the first, and had he kept a box it would have worn
75
75
  * `primary`, never `outline`. Same variant as the trigger; the SQUARE
76
76
  * is `sm` since 2026-09-03, one size for the one idiom. */
77
- <Button
78
- variant="nav"
79
- size="sm"
80
- iconOnly="x"
81
- className="kol-overlay-close"
82
- onClick={onClose}
83
- aria-label="Close"
84
- />
77
+ <CloseButton className="kol-overlay-close" onClick={onClose} />
85
78
  )}
86
79
  {children}
87
80
  </div>