@kolkrabbi/kol-component 0.169.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.
|
|
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
|
|
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
|
|
@@ -38,6 +38,7 @@ const FOCUSABLE =
|
|
|
38
38
|
* @param {number|string} height panel height (px number or CSS length); omit for a content-sized sheet — `bottom` only
|
|
39
39
|
* @param {ReactNode} header header-row content beside the close button (replaces the source's baked-in wordmark)
|
|
40
40
|
* @param {boolean} backdrop render the dimming scrim (default true); false = panel alone, no darken/blur, close via × / Esc
|
|
41
|
+
* @param {'xs'|'sm'|'md'|'lg'} closeSize the close control's rung (default 'sm' — match the controls in the panel)
|
|
41
42
|
* @param {ReactNode} children scrollable panel body
|
|
42
43
|
* @param {string} className extra classes on the panel
|
|
43
44
|
*/
|
|
@@ -50,6 +51,14 @@ export default function ShellDrawer({
|
|
|
50
51
|
header,
|
|
51
52
|
closeSide = 'end',
|
|
52
53
|
backdrop = true,
|
|
54
|
+
/* THE CLOSE SITS ON THE ROW'S RUNG (user 2026-09-03, on the settings drawer:
|
|
55
|
+
* *"does this button follow the size ladder?"*). It was pinned `md` (32) while
|
|
56
|
+
* every control in the panel below it — the switches, the dropdowns, the reset
|
|
57
|
+
* frame — is `sm` (26), so the one control that is not a setting was the
|
|
58
|
+
* largest thing on the surface. A size is a height and a row is one height;
|
|
59
|
+
* `sm` is the default because a drawer header sits over its own controls.
|
|
60
|
+
* Named `closeSize`, not `size` — `size` is already the panel's own box. */
|
|
61
|
+
closeSize = 'sm',
|
|
53
62
|
children,
|
|
54
63
|
className = '',
|
|
55
64
|
/* SettingsPanelApproved (2026-08-27): the settings drawer has neither */
|
|
@@ -188,14 +197,7 @@ export default function ShellDrawer({
|
|
|
188
197
|
{closeSide === 'start' && (
|
|
189
198
|
/* bare glyph, no container (user 2026-08-09: "the X close icon
|
|
190
199
|
* does not need a container") — body ink at rest, emphasis on hover */
|
|
191
|
-
<
|
|
192
|
-
type="button"
|
|
193
|
-
aria-label="Close"
|
|
194
|
-
onClick={onClose}
|
|
195
|
-
className="inline-flex shrink-0 cursor-pointer border-0 bg-transparent p-0 text-body transition-colors duration-150 hover:text-emphasis"
|
|
196
|
-
>
|
|
197
|
-
<Icon name="x" size={18} />
|
|
198
|
-
</button>
|
|
200
|
+
<CloseButton onClick={onClose} size={closeSize} className="shrink-0" />
|
|
199
201
|
)}
|
|
200
202
|
{header != null && <div className="min-w-0 flex-1">{header}</div>}
|
|
201
203
|
{/* A NORMAL ICON BUTTON (user 2026-09-02: *"it should just be like a
|
|
@@ -205,14 +207,7 @@ export default function ShellDrawer({
|
|
|
205
207
|
* the md rung's 32px around a glyph six under it, and it read as an
|
|
206
208
|
* oversized empty square. No override: the rung sets both. */}
|
|
207
209
|
{closeSide !== 'start' && (
|
|
208
|
-
<
|
|
209
|
-
variant="nav"
|
|
210
|
-
size="md"
|
|
211
|
-
iconOnly="x"
|
|
212
|
-
onClick={onClose}
|
|
213
|
-
aria-label="Close"
|
|
214
|
-
className="ml-auto shrink-0"
|
|
215
|
-
/>
|
|
210
|
+
<CloseButton onClick={onClose} size={closeSize} className="ml-auto shrink-0" />
|
|
216
211
|
)}
|
|
217
212
|
</div>
|
|
218
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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,13 +1,16 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
|
-
import
|
|
2
|
+
import CloseButton from './CloseButton.jsx'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* FullscreenOverlay — the scrim + centred sheet every overlay in the repo
|
|
6
6
|
* wears. Owns dismissal (Escape, backdrop, close button), scroll lock and
|
|
7
7
|
* stacking; the consumer supplies the panel.
|
|
8
8
|
*
|
|
9
|
-
* The close control is the DS `Button` (
|
|
10
|
-
*
|
|
9
|
+
* The close control is the DS `Button` (icon-only, the `x` glyph) — the estate's
|
|
10
|
+
* ONE close idiom, and therefore ONE SIZE: `sm` (26), matching `ShellDrawer`'s
|
|
11
|
+
* (2026-09-03). It took Button's `md` default while the drawer's went to `sm`,
|
|
12
|
+
* which is two sizes for one idiom — the thing the single-idiom ruling exists to
|
|
13
|
+
* stop. It was a hand-rolled <button> printing a
|
|
11
14
|
* literal `×` TEXT CHARACTER until 2026-08-01: no icon, no states, and a
|
|
12
15
|
* typographic multiplication sign standing in for a glyph the icon set has
|
|
13
16
|
* always shipped.
|
|
@@ -69,15 +72,9 @@ export default function FullscreenOverlay({ open, onClose, closeButton = true, c
|
|
|
69
72
|
* user ruling): the estate's close X is the drawer trigger's bare
|
|
70
73
|
* `nav` glyph — the boxed outline treatment was a second design one
|
|
71
74
|
* tap away from the first, and had he kept a box it would have worn
|
|
72
|
-
* `primary`, never `outline`. Same variant
|
|
73
|
-
* the
|
|
74
|
-
<
|
|
75
|
-
variant="nav"
|
|
76
|
-
iconOnly="x"
|
|
77
|
-
className="kol-overlay-close"
|
|
78
|
-
onClick={onClose}
|
|
79
|
-
aria-label="Close"
|
|
80
|
-
/>
|
|
75
|
+
* `primary`, never `outline`. Same variant as the trigger; the SQUARE
|
|
76
|
+
* is `sm` since 2026-09-03, one size for the one idiom. */
|
|
77
|
+
<CloseButton className="kol-overlay-close" onClick={onClose} />
|
|
81
78
|
)}
|
|
82
79
|
{children}
|
|
83
80
|
</div>
|