@kolkrabbi/kol-component 0.229.0 → 0.231.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 +4 -4
- package/src/atoms/ActionButton.jsx +1 -1
- package/src/atoms/Button.jsx +2 -2
- package/src/atoms/IconFrame.jsx +1 -1
- package/src/atoms/Kbd.jsx +33 -0
- package/src/atoms/SizeOrDownload.jsx +1 -1
- package/src/atoms/Tag.jsx +3 -3
- package/src/hooks/colorMath.js +1 -1
- package/src/index.js +7 -6
- package/src/molecules/ColorInputRow.jsx +1 -1
- package/src/molecules/ContentCard.jsx +5 -5
- package/src/molecules/ContentMedia.jsx +1 -1
- package/src/molecules/ContentRow.jsx +2 -2
- package/src/{utilities → molecules}/ContextMenu.jsx +1 -1
- package/src/molecules/DocsToc.jsx +1 -1
- package/src/molecules/PaletteHarmonyWheel.jsx +4 -4
- package/src/molecules/ProfileCard.jsx +1 -1
- package/src/molecules/QuickLookFrame.jsx +1 -1
- package/src/molecules/SearchInput.jsx +10 -9
- package/src/molecules/ShellDrawer.jsx +1 -1
- package/src/molecules/SwatchControls.jsx +3 -3
- package/src/molecules/TabsRow.jsx +1 -1
- package/src/organisms/Canvas.jsx +1 -1
- package/src/organisms/ContentFilters.jsx +1 -1
- package/src/organisms/MediaLibraryPages.jsx +1 -1
- package/src/organisms/SectionCards.jsx +1 -1
- package/src/organisms/SectionCta.jsx +1 -1
- package/src/organisms/SectionFaq.jsx +1 -1
- package/src/organisms/SectionNewsletter.jsx +1 -1
- package/src/organisms/ShellSearchOverlay.jsx +82 -38
- package/src/organisms/sectionBleed.js +1 -1
- package/src/utilities/FullscreenOverlay.jsx +1 -1
- package/src/utilities/LoaderOverlay.jsx +2 -2
- package/src/utilities/Popover.jsx +10 -1
- package/src/utilities/tone.js +1 -1
- /package/src/{utilities → atoms}/CloseButton.jsx +0 -0
- /package/src/{atoms → utilities}/CropOverlay.jsx +0 -0
- /package/src/{atoms → utilities}/CurveOverlay.jsx +0 -0
- /package/src/{atoms → utilities}/PathNodeOverlay.jsx +0 -0
- /package/src/{atoms → utilities}/SelectionOverlay.jsx +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.231.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",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"embla-carousel-react": "^8.6.0",
|
|
26
26
|
"pdfjs-dist": "^6.3.289",
|
|
27
27
|
"react-syntax-highlighter": "^16.1.1",
|
|
28
|
-
"@kolkrabbi/kol-
|
|
29
|
-
"@kolkrabbi/kol-
|
|
28
|
+
"@kolkrabbi/kol-search": "^0.2.0",
|
|
29
|
+
"@kolkrabbi/kol-markdown": "^0.1.2"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@kolkrabbi/kol-icons": ">=0.22.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@kolkrabbi/kol-icons": "^0.
|
|
40
|
+
"@kolkrabbi/kol-icons": "^0.31.0"
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"src",
|
|
@@ -146,7 +146,7 @@ export default function ActionButton({
|
|
|
146
146
|
const rest = restRef.current
|
|
147
147
|
const on = onRef.current
|
|
148
148
|
/* no confirmIcon = ONE glyph, and the state is carried by the class alone
|
|
149
|
-
* (a fill, a
|
|
149
|
+
* (a fill, a color). Nothing to crossfade. */
|
|
150
150
|
if (!rest || !on) return undefined
|
|
151
151
|
|
|
152
152
|
if (first.current) {
|
package/src/atoms/Button.jsx
CHANGED
|
@@ -18,7 +18,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
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)
|
|
20
20
|
* @param {string} props.iconRightHover - Icon to show on hover (right position)
|
|
21
|
-
* @param {'primary'|'secondary'|'inverted'|'outline'|'ghost'|'grey'|'sunken'} props.tone - the ground (`secondary` = the page surface, `inverted` = the text
|
|
21
|
+
* @param {'primary'|'secondary'|'inverted'|'outline'|'ghost'|'grey'|'sunken'} props.tone - the ground (`secondary` = the page surface, `inverted` = the text color as fill — what `variant="secondary"` paints) (tone-is-the-ground-axis, 2026-09-03) — wins over `variant` on the same element; unset = inherit the wrapper's. `sunken` = the control set's dark well + fg-96 ink (ControlToneSunken); `inverse` aliased
|
|
22
22
|
* @param {string} props.iconOnly - Icon name for icon-only button
|
|
23
23
|
* @param {string} props.iconOnlyHover - Icon to show on hover (icon-only)
|
|
24
24
|
* @param {boolean} props.animateIcon - Disable default hover states to focus on icon animation
|
|
@@ -82,7 +82,7 @@ const Button = ({
|
|
|
82
82
|
* rung, oq-80 ink + aria-current — it had lived in the theme with no
|
|
83
83
|
* component able to emit it, the direct cause of the four-container header. */
|
|
84
84
|
/* An UNKNOWN variant is a typo, not a request for the inverted fill — it fell back to
|
|
85
|
-
* `kol-btn-secondary` (the text
|
|
85
|
+
* `kol-btn-secondary` (the text color as fill) until 2026-09-29, so a misspelt variant
|
|
86
86
|
* shipped inverted. It now stamps nothing, like an unset variant: the wrapper's tone,
|
|
87
87
|
* else primary — and says so in dev. */
|
|
88
88
|
const KNOWN = ['primary', 'secondary', 'accent', 'outline', 'ghost', 'nav', 'danger', 'grey']
|
package/src/atoms/IconFrame.jsx
CHANGED
|
@@ -22,7 +22,7 @@ import { toneClass } from '../utilities/tone.js'
|
|
|
22
22
|
* frame's own classes declare the same background, foreground and geometry and
|
|
23
23
|
* simply have no state rules to inherit.
|
|
24
24
|
*
|
|
25
|
-
* `variant` borrows the kol-btn
|
|
25
|
+
* `variant` borrows the kol-btn COLOR SET verbatim so the frame sits in the
|
|
26
26
|
* same visual system as real buttons; `primary` and `secondary` are inverse
|
|
27
27
|
* pairs that flip with the theme, so light/dark comes free from the tokens with
|
|
28
28
|
* no per-theme props.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kbd — a key cap: one key or chord (↵, ⌘K, Esc) shown as an affordance.
|
|
3
|
+
* Replaces the two hand-rolled <kbd> chips (SearchInput's shortcut hint and
|
|
4
|
+
* the palette footer) that had drifted apart (2026-09-30). A plate, so it
|
|
5
|
+
* takes oq — never fg (icons-use-oq law).
|
|
6
|
+
*
|
|
7
|
+
* aria-hidden by default: a cap is a hint beside a label, not the label.
|
|
8
|
+
*
|
|
9
|
+
* @param {'sm'|'md'} size sm = inside a control (16px, helper-10) ·
|
|
10
|
+
* md = beside helper-12 text (20px)
|
|
11
|
+
* @param {string} icon kol-icons name drawn before the children — a key
|
|
12
|
+
* with a symbol (↵ `corner-down-left`, ⌘ `command`)
|
|
13
|
+
* is a GLYPH, never a typed character in the mono face
|
|
14
|
+
*/
|
|
15
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
16
|
+
|
|
17
|
+
const SIZE = {
|
|
18
|
+
sm: 'h-4 min-w-4 px-1 kol-helper-10',
|
|
19
|
+
md: 'h-5 min-w-5 px-1.5 kol-helper-12',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function Kbd({ size = 'md', icon, className = '', children, ...rest }) {
|
|
23
|
+
return (
|
|
24
|
+
<kbd
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
className={`inline-flex items-center justify-center gap-0.5 shrink-0 rounded-[var(--kol-radius-xs)] bg-oq-08 text-oq-64 ${SIZE[size] ?? SIZE.md} ${className}`.trim()}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
{icon && <Icon name={icon} size={12} />}
|
|
30
|
+
{children}
|
|
31
|
+
</kbd>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -21,7 +21,7 @@ import { Icon } from '@kolkrabbi/kol-icons'
|
|
|
21
21
|
* click, and this one is inside the link rather than being it.
|
|
22
22
|
*
|
|
23
23
|
* Both hover parts ink on the OPACITY scale (`text-oq-80`), not an `fg-*` role:
|
|
24
|
-
* a stroke glyph on a flat fg
|
|
24
|
+
* a stroke glyph on a flat fg color reads wrong against the plate, and oq is
|
|
25
25
|
* what the rest of the chrome uses.
|
|
26
26
|
*
|
|
27
27
|
* ONE type class throughout — kol-mono-12. helper-12 is line-height 1 against
|
package/src/atoms/Tag.jsx
CHANGED
|
@@ -18,7 +18,7 @@ const ICON_SIZES = { xs: 8, sm: 10, md: 12, lg: 14 }
|
|
|
18
18
|
* `:hover` rule between them, on `.tag-control`. Every other path rendered
|
|
19
19
|
* dead.
|
|
20
20
|
* - `color` was a SECOND axis that silently swapped the base class from
|
|
21
|
-
* `tag-control` to `tag tag--{color}` — so passing a
|
|
21
|
+
* `tag-control` to `tag tag--{color}` — so passing a color cost you the
|
|
22
22
|
* interaction state, invisibly. That is what shipped a solid blue pill you
|
|
23
23
|
* could not hover.
|
|
24
24
|
* - `variant="solid"` and a `solid` boolean did the same job.
|
|
@@ -28,8 +28,8 @@ const ICON_SIZES = { xs: 8, sm: 10, md: 12, lg: 14 }
|
|
|
28
28
|
*
|
|
29
29
|
* Now it is Pill's vocabulary — `primary` (filled) · `secondary` (outlined) ·
|
|
30
30
|
* `inverse` — one size scale, ONE class scheme (`kol-tag--*`), and every
|
|
31
|
-
* variant carries hover + active.
|
|
32
|
-
* variant, exactly as it is on Pill and Button. Tag
|
|
31
|
+
* variant carries hover + active. Color is not a prop: a chip's look is its
|
|
32
|
+
* variant, exactly as it is on Pill and Button. Tag color BY TAXONOMY returns
|
|
33
33
|
* later as its own decision, on top of the variants rather than instead of them.
|
|
34
34
|
*
|
|
35
35
|
* @param {ReactNode} children label content
|
package/src/hooks/colorMath.js
CHANGED
|
@@ -116,7 +116,7 @@ export function harmonyColors(hue, harmony, { saturation = 100, lightness = 50 }
|
|
|
116
116
|
* package's `colors` payload was half-ignorable).
|
|
117
117
|
*
|
|
118
118
|
* A slot that is `locked`, empty, or has no hex is passed through untouched —
|
|
119
|
-
* locking a
|
|
119
|
+
* locking a color is the one instruction a re-hue must not overrule.
|
|
120
120
|
*
|
|
121
121
|
* @param {number} hue base hue, 0–360
|
|
122
122
|
* @param {string|object} harmony harmony id or object
|
package/src/index.js
CHANGED
|
@@ -22,14 +22,14 @@ 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 './
|
|
25
|
+
export { default as CloseButton } from './atoms/CloseButton.jsx'
|
|
26
26
|
export { default as ActionButton } from './atoms/ActionButton.jsx'
|
|
27
27
|
export { default as FileIcon } from './atoms/FileIcon.jsx'
|
|
28
28
|
export { default as SizeOrDownload } from './atoms/SizeOrDownload.jsx'
|
|
29
29
|
export { default as SortHeader } from './atoms/SortHeader.jsx'
|
|
30
30
|
export { default as SortControls } from './molecules/SortControls.jsx'
|
|
31
31
|
export { default as CopyButton } from './molecules/CopyButton.jsx'
|
|
32
|
-
export { default as CurveOverlay } from './
|
|
32
|
+
export { default as CurveOverlay } from './utilities/CurveOverlay.jsx'
|
|
33
33
|
export { default as Divider } from './atoms/Divider.jsx'
|
|
34
34
|
export { default as DocsToc } from './molecules/DocsToc.jsx'
|
|
35
35
|
export { default as DropdownTagFilter } from './molecules/DropdownTagFilter.jsx'
|
|
@@ -49,7 +49,7 @@ export { default as LabeledControl } from './molecules/LabeledControl.jsx'
|
|
|
49
49
|
export { default as OverlayGlassPanel } from './utilities/OverlayGlassPanel.jsx'
|
|
50
50
|
export { default as Pill } from './atoms/Pill.jsx'
|
|
51
51
|
export { usePopover, PopoverPanel, Tooltip } from './utilities/Popover.jsx'
|
|
52
|
-
export { default as ContextMenu, useContextMenu } from './
|
|
52
|
+
export { default as ContextMenu, useContextMenu } from './molecules/ContextMenu.jsx'
|
|
53
53
|
export { default as ProsePreview } from './utilities/ProsePreview.jsx'
|
|
54
54
|
export { default as QuantityInput } from './molecules/QuantityInput.jsx'
|
|
55
55
|
export { default as RotaryDial } from './atoms/RotaryDial.jsx'
|
|
@@ -59,6 +59,7 @@ export { default as SectionText } from './molecules/SectionText.jsx'
|
|
|
59
59
|
export { default as SectionLabel } from './atoms/SectionLabel.jsx'
|
|
60
60
|
export { default as SegmentedToggle } from './atoms/SegmentedToggle.jsx'
|
|
61
61
|
export { default as Stepper } from './molecules/Stepper.jsx'
|
|
62
|
+
export { default as Kbd } from './atoms/Kbd.jsx'
|
|
62
63
|
export { default as Tag } from './atoms/Tag.jsx'
|
|
63
64
|
export { default as Textarea } from './atoms/Textarea.jsx'
|
|
64
65
|
export { default as ToggleBracket } from './atoms/ToggleBracket.jsx'
|
|
@@ -73,8 +74,8 @@ export { default as InspectorRail } from './molecules/InspectorRail.jsx'
|
|
|
73
74
|
/* SelectionOverlay's two siblings — same 1080-virtual contract, same zoom
|
|
74
75
|
* division (editor-panels-the-held-specs B2). `pathMath` is their geometry,
|
|
75
76
|
* exported because the editor engine re-exports it rather than keep a copy. */
|
|
76
|
-
export { default as PathNodeOverlay } from './
|
|
77
|
-
export { default as CropOverlay } from './
|
|
77
|
+
export { default as PathNodeOverlay } from './utilities/PathNodeOverlay.jsx'
|
|
78
|
+
export { default as CropOverlay } from './utilities/CropOverlay.jsx'
|
|
78
79
|
export { default as LayerStack, AddLayerButton, BLEND_MODES } from './organisms/LayerStack.jsx'
|
|
79
80
|
export { TYPE_LABELS, BOOL_OP_LABELS, SHAPE_KIND_LABELS, labelForLayer, rowLabelForLayer, findLayerDeep } from './hooks/layerTree.js'
|
|
80
81
|
export { default as TimelineDock, sampleTrack, TIMELINE_EASINGS } from './organisms/TimelineDock.jsx'
|
|
@@ -88,7 +89,7 @@ export { default as ButtonGroup } from './utilities/ButtonGroup.jsx'
|
|
|
88
89
|
/* monorepo sets (P6–P10) — molecule members */
|
|
89
90
|
export { default as AlignmentGrid } from './molecules/AlignmentGrid.jsx'
|
|
90
91
|
export { default as ImageBlock } from './molecules/ImageBlock.jsx'
|
|
91
|
-
export { default as SelectionOverlay } from './
|
|
92
|
+
export { default as SelectionOverlay } from './utilities/SelectionOverlay.jsx'
|
|
92
93
|
export { default as VideoBlock, getEmbedUrl } from './molecules/VideoBlock.jsx'
|
|
93
94
|
export { default as SectionCardItem, default as CardFeatureItem } from './molecules/SectionCardItem.jsx'
|
|
94
95
|
export { default as CodeBlock } from './molecules/CodeBlock.jsx'
|
|
@@ -243,7 +243,7 @@ export default function ColorInputRow({
|
|
|
243
243
|
)}
|
|
244
244
|
{/* QUICK STATES. Theme (the auto value — a token that flips with
|
|
245
245
|
light/dark) is offered only where the field HAS one; None is
|
|
246
|
-
always available, because clearing a
|
|
246
|
+
always available, because clearing a color is not a palette
|
|
247
247
|
decision. Both were dropped in the first port, which is what left
|
|
248
248
|
`value == null` renderable but unreachable. */}
|
|
249
249
|
<div className="flex items-center gap-2">
|
|
@@ -47,7 +47,7 @@ const BOX = {
|
|
|
47
47
|
/* SLIDE (slide-variant-and-shelf-preset, kol-client-olina 2026-09-03; user: "they are genuinely
|
|
48
48
|
* different with 16:9 layout and those exposed properties"): file's stack — cover on top, the
|
|
49
49
|
* plate below — on the PAGE'S surface, rest and hover (olina's /slide-deck, read off the render).
|
|
50
|
-
* The plate is not a tone (user: "no just controls"); it is this kind's
|
|
50
|
+
* The plate is not a tone (user: "no just controls"); it is this kind's color. */
|
|
51
51
|
slide: { layout: 'stack', border: null, bg: 'var(--kol-surface-primary)', pad: 'var(--kol-pad-card-sm)' },
|
|
52
52
|
/* THE FRAME READS BACKWARDS (CatalogCardFrameAndZoom, kol-website 2026-08-28 — user, on a 212-tile
|
|
53
53
|
* grid: no frame at rest; the old rest value is the hover): a wall of fg-04 frames is a grid of boxes,
|
|
@@ -94,7 +94,7 @@ const BOX = {
|
|
|
94
94
|
* has no surface of its own to step, so it dims its title instead. */
|
|
95
95
|
const HOVER = {
|
|
96
96
|
file: 'var(--kol-oq-04)',
|
|
97
|
-
slide: 'var(--kol-surface-primary)', /* the plate holds its
|
|
97
|
+
slide: 'var(--kol-surface-primary)', /* the plate holds its color on hover; the drawer control is the affordance */
|
|
98
98
|
catalog: 'var(--kol-surface-tertiary)',
|
|
99
99
|
/* article and work take NO surface hover, and that is a decision not a gap:
|
|
100
100
|
* article has no surface of its own (its media frame, when on, steps its
|
|
@@ -168,7 +168,7 @@ export default function ContentCard({
|
|
|
168
168
|
* print draw it); `false` turns it off without an `!important` in a consumer sheet */
|
|
169
169
|
plateRule,
|
|
170
170
|
/* `bg` — the card's REST fill, overriding the variant's. It sets
|
|
171
|
-
* `--kol-card-bg`, not a background, because the rest
|
|
171
|
+
* `--kol-card-bg`, not a background, because the rest colors are custom
|
|
172
172
|
* properties so the hover class can win; that is also why
|
|
173
173
|
* `className="bg-oq-48"` does nothing here and a consumer reaching around the
|
|
174
174
|
* component had to write `className="[--kol-card-bg:var(--kol-oq-48)]"`
|
|
@@ -437,7 +437,7 @@ export default function ContentCard({
|
|
|
437
437
|
'data-tags': isHero && Array.isArray(text.tags) && text.tags.length ? text.tags.join(' ') : undefined,
|
|
438
438
|
className: `kol-card group flex ${box.layout === 'canvas' && reveal != null ? 'has-reveal' : ''} ${expanded ? 'flex-col md:flex-row-reverse' : 'flex-col'} ${box.layout === 'drawer' ? 'relative overflow-hidden rounded-[var(--kol-radius-sm)]' : ''} ${framed ? 'overflow-hidden rounded-[var(--kol-radius-sm)]' : ''} ${box.border ? 'border' : ''} ${box.layout === 'canvas' ? 'relative' : ''} ${interactive ? 'cursor-pointer select-none' : ''} ${hoverBg && interactive ? 'kol-content-hover' : ''} ${interactive && box.frameHover ? 'kol-content-hover-frame' : ''} ${className}`.trim(),
|
|
439
439
|
style: {
|
|
440
|
-
/* same reason as ContentRow: rest
|
|
440
|
+
/* same reason as ContentRow: rest colors are PROPERTIES, because an
|
|
441
441
|
* inline background/borderColor outranks the hover class and the step
|
|
442
442
|
* would never render. */
|
|
443
443
|
'--kol-card-bg': bg ?? box.bg ?? undefined,
|
|
@@ -464,7 +464,7 @@ export default function ContentCard({
|
|
|
464
464
|
onContextMenu={onContextMenu}
|
|
465
465
|
onDoubleClick={onDoubleClick}
|
|
466
466
|
/* SELECTED HAS TO SHOW (user 2026-09-23: a click in the grid "should select/highlight").
|
|
467
|
-
* `selected` only flipped a border
|
|
467
|
+
* `selected` only flipped a border color on the variants that HAVE a border, and the file
|
|
468
468
|
* wall's cards have none — so the prop was true and the card looked untouched. The
|
|
469
469
|
* attribute is the hook; the theme paints it, once, for every variant. */
|
|
470
470
|
data-selected={selected || undefined}
|
|
@@ -57,7 +57,7 @@ import AssetPlaceholder from '../utilities/AssetPlaceholder.jsx'
|
|
|
57
57
|
* @param {boolean} frame tinted box + border UNDER the media
|
|
58
58
|
* @param {boolean} border border only, no tint
|
|
59
59
|
* @param {string} bg tint only, no border — a raw token value
|
|
60
|
-
* @param {string} borderHover border
|
|
60
|
+
* @param {string} borderHover border color on hover (article's fg-16 step)
|
|
61
61
|
* @param {boolean} ring hairline border OVER the media, inset
|
|
62
62
|
* @param {boolean|'hero'} zoom the artwork creeps up inside its frame on the
|
|
63
63
|
* card's hover — 1.06; `'hero'` = the hero rung, 1.02
|
|
@@ -130,7 +130,7 @@ const BOX = {
|
|
|
130
130
|
* its background and read as a different interaction from every other listing.
|
|
131
131
|
*
|
|
132
132
|
* The three passes are the lesson: the ask was "make it look like /work" and
|
|
133
|
-
* each ticket named only the state someone had looked at — rest
|
|
133
|
+
* each ticket named only the state someone had looked at — rest colors, then
|
|
134
134
|
* the derive's collateral, then this. When a user says make X look like Y,
|
|
135
135
|
* diff EVERY state: rest, hover, selected, focus. */
|
|
136
136
|
bg: 'var(--kol-surface-secondary)', frame: 'transparent', frameHover: 'var(--kol-fg-08)' },
|
|
@@ -171,7 +171,7 @@ export default function ContentRow({
|
|
|
171
171
|
* card, `contentcard-bg-and-text-props` 2026-09-03; the pair ships together
|
|
172
172
|
* and a consumer that re-grounds one hits the same wall on the other in the
|
|
173
173
|
* same grid). It sets `--kol-row-bg`, not a background, because the rest
|
|
174
|
-
*
|
|
174
|
+
* colors are custom properties so the hover class can win — which is why
|
|
175
175
|
* `className="bg-oq-48"` does nothing here either. `selected` still wins:
|
|
176
176
|
* a selected row is the list's state, not the consumer's ground. For the
|
|
177
177
|
* row's INK, pass `text` — it falls through to ContentText. */
|
|
@@ -128,7 +128,7 @@ export default function DocsToc({
|
|
|
128
128
|
* `block transition-colors focus-visible:ring-focus
|
|
129
129
|
* hover:text-emphasis text-body` as utilities, so the same rung
|
|
130
130
|
* rendered a different className here than in the left tree.
|
|
131
|
-
* Layout,
|
|
131
|
+
* Layout, color, hover and focus live in `.shell-nav-item` now;
|
|
132
132
|
* active is the shared `is-active` marker, not `text-emphasis`
|
|
133
133
|
* typed at one call site. Matches RailRow exactly. */
|
|
134
134
|
className={`shell-nav-item kol-mono-14${activeId === item.id ? ' is-active' : ''}`}
|
|
@@ -43,11 +43,11 @@ import { HARMONIES, harmonyById, harmonyColors, normHue, reHueSlots } from '../h
|
|
|
43
43
|
* @param {Array} harmonies injectable scheme table (default HARMONIES)
|
|
44
44
|
* @param {Array} slots the CURRENT palette in role order (`{hex, locked}` objects or plain hex strings). Given, `colors` re-hues these — each slot keeps its own S/L, locked and empty entries pass through — instead of generating flat ones
|
|
45
45
|
* @param {Function} onChange ({ hue, colors }) => void
|
|
46
|
-
* @param {Function} onHueChange (hue) => void — the payload-free seam, for a caller that derives its own
|
|
46
|
+
* @param {Function} onHueChange (hue) => void — the payload-free seam, for a caller that derives its own colors
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
/* Marker outline — white for contrast against the fully-saturated ring hues
|
|
50
|
-
* (theme-independent: the wheel's
|
|
50
|
+
* (theme-independent: the wheel's colors, not the surface, sit behind it). */
|
|
51
51
|
const MARKER_STROKE = '#FFFFFF'
|
|
52
52
|
|
|
53
53
|
export default function PaletteHarmonyWheel({
|
|
@@ -74,9 +74,9 @@ export default function PaletteHarmonyWheel({
|
|
|
74
74
|
/* Emit next hue + its harmony colors. Held in a ref so the pointer/key
|
|
75
75
|
* handlers stay stable while always seeing the latest props.
|
|
76
76
|
*
|
|
77
|
-
* With `slots`, the
|
|
77
|
+
* With `slots`, the colors are the CALLER'S palette re-hued — each slot
|
|
78
78
|
* keeping its own saturation and lightness — rather than a fresh flat set.
|
|
79
|
-
* Both fire, so a caller can take the hue and ignore the
|
|
79
|
+
* Both fire, so a caller can take the hue and ignore the colors. */
|
|
80
80
|
emitRef.current = (nextHue) => {
|
|
81
81
|
const h = normHue(nextHue)
|
|
82
82
|
const colors = slots?.length
|
|
@@ -27,7 +27,7 @@ import { surfaceClass } from '../utilities/sectionSurface.js'
|
|
|
27
27
|
* `inverse` with inverse ink, exactly as shipped. `controlVariant` goes straight
|
|
28
28
|
* to the disclosure's `IconFrame`; `pad` is `ContentCard`'s — one step on
|
|
29
29
|
* `--kol-pad-card-*`, overriding the size ramp's padding. The logo is a slot
|
|
30
|
-
* and stays one: its ink follows the shelf, a
|
|
30
|
+
* and stays one: its ink follows the shelf, a colored mark is the asset's.
|
|
31
31
|
*
|
|
32
32
|
* THE SHELF SIZES TO ITS CONTENT. The source held it in a fixed box
|
|
33
33
|
* (`h-60`/`h-44`/`h-32`/`h-24`, `overflow-hidden`) and every vertical size
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useRef, useState } from 'react'
|
|
2
2
|
import Button from '../atoms/Button.jsx'
|
|
3
|
-
import CloseButton from '../
|
|
3
|
+
import CloseButton from '../atoms/CloseButton.jsx'
|
|
4
4
|
|
|
5
5
|
/* taxonomy-ok: molecule — nests Button (atom) + CloseButton (utility). */
|
|
6
6
|
|
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
import { toneClass } from '../utilities/tone.js'
|
|
3
3
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
4
4
|
import { glyphSize } from '../hooks/glyphLadders.js'
|
|
5
|
+
import Kbd from '../atoms/Kbd.jsx'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* SearchInput — controlled search field on the .kol-control shell. The
|
|
@@ -200,8 +201,10 @@ export default function SearchInput({
|
|
|
200
201
|
const shellCls = [
|
|
201
202
|
bare
|
|
202
203
|
/* kol-control--bare: zero-chrome marker so the theme's coarse-pointer
|
|
203
|
-
* 16px floor covers this body plan too (OverlaySearchFieldZoomsIOS)
|
|
204
|
-
|
|
204
|
+
* 16px floor covers this body plan too (OverlaySearchFieldZoomsIOS).
|
|
205
|
+
* Chrome off, geometry on: the size's padding + a transparent 1px ring
|
|
206
|
+
* land it on the 22 · 26 · 32 · 40 ladder like the shell (09-sizes) */
|
|
207
|
+
? `kol-control--bare kol-control-${size} flex w-full border border-transparent gap-2`
|
|
205
208
|
: `kol-control${variant ? ` kol-control--${variant}` : ''} kol-control-${size} gap-2${toneClass(tone) ? ` ${toneClass(tone)}` : ''}`,
|
|
206
209
|
'items-center cursor-text',
|
|
207
210
|
SIZE_TYPE[size],
|
|
@@ -225,7 +228,7 @@ export default function SearchInput({
|
|
|
225
228
|
<label className={shellCls}>
|
|
226
229
|
<span
|
|
227
230
|
aria-hidden="true"
|
|
228
|
-
className={`flex items-center shrink-0 ${bare ? 'text-
|
|
231
|
+
className={`flex items-center shrink-0 ${bare ? 'text-oq-48' : 'text-auto opacity-50'}`}
|
|
229
232
|
>
|
|
230
233
|
{/* ADJACENT — this glyph sits in the field's line box beside the query */}
|
|
231
234
|
<Icon name="search" size={iconSize ?? glyphSize(size)} />
|
|
@@ -254,12 +257,10 @@ export default function SearchInput({
|
|
|
254
257
|
</button>
|
|
255
258
|
) : shortcutHint ? (
|
|
256
259
|
/* aria-hidden — affordance, not a label (same stance as Input's prefix/suffix) */
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{shortcutHint}
|
|
262
|
-
</kbd>
|
|
260
|
+
/* ⌘ is drawn, not typed — the mono face's ⌘ is the "drawn by a child" cap */
|
|
261
|
+
shortcutHint.startsWith('⌘')
|
|
262
|
+
? <Kbd size="sm" icon="command">{shortcutHint.slice(1)}</Kbd>
|
|
263
|
+
: <Kbd size="sm">{shortcutHint}</Kbd>
|
|
263
264
|
) : null}
|
|
264
265
|
</label>
|
|
265
266
|
)
|
|
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
import { pushLayer, popLayer, isTopLayer } from '../utilities/layerStack.js'
|
|
3
3
|
import { createPortal } from 'react-dom'
|
|
4
4
|
import { Icon } from '@kolkrabbi/kol-icons'
|
|
5
|
-
import CloseButton from '../
|
|
5
|
+
import CloseButton from '../atoms/CloseButton.jsx'
|
|
6
6
|
import usePrefersReducedMotion from '../hooks/usePrefersReducedMotion.js'
|
|
7
7
|
|
|
8
8
|
/* taxonomy-ok: nests kol-icons's Icon (a package import the relative-import
|
|
@@ -4,7 +4,7 @@ import ColorSwatch from '../atoms/ColorSwatch.jsx'
|
|
|
4
4
|
import { Tooltip } from '../utilities/Popover.jsx'
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
* SwatchControls — the Photoshop-style top row of the
|
|
7
|
+
* SwatchControls — the Photoshop-style top row of the color panel, in two
|
|
8
8
|
* hand-tuned pieces plus the composed row. Hand-tuned visuals (fixed pixel
|
|
9
9
|
* slots, the overlap-by-DOM-order trick, the double-ring halo, the red-slash
|
|
10
10
|
* "none" marker). DO NOT refactor pieces to atoms — the look is intentional
|
|
@@ -20,7 +20,7 @@ import { Tooltip } from '../utilities/Popover.jsx'
|
|
|
20
20
|
* No z-index, no transform — DOM order alone stacks them.
|
|
21
21
|
*
|
|
22
22
|
* <EyedropPick sampleColor onPick disabled />
|
|
23
|
-
* — eyedropper icon button + a small sample chip of the sampled
|
|
23
|
+
* — eyedropper icon button + a small sample chip of the sampled color.
|
|
24
24
|
* The button is feature-gated: it is hidden entirely when the browser
|
|
25
25
|
* EyeDropper API is unavailable, and `disabled` dims it when supported
|
|
26
26
|
* but unusable. The actual EyeDropper call + canvas sampling live at the
|
|
@@ -92,7 +92,7 @@ export function SwatchStack({
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* EyedropPick — eyedropper icon button + a sample chip of the sampled
|
|
95
|
+
* EyedropPick — eyedropper icon button + a sample chip of the sampled color.
|
|
96
96
|
* The button is hidden when the browser EyeDropper API is unavailable (no
|
|
97
97
|
* affordance for an action that can't run); when supported but unusable pass
|
|
98
98
|
* `disabled` to dim it. `onPick` is the app seam where EyeDropper + canvas
|
package/src/organisms/Canvas.jsx
CHANGED
|
@@ -93,7 +93,7 @@ export function CanvasFrame({
|
|
|
93
93
|
const { ratio, label } = resolveAspect(aspect, customRatio, aspects)
|
|
94
94
|
/* NO guideColor → THEME INK (editor DS sync 2026-09-27, from the design editor's copy, which had
|
|
95
95
|
* it and this lift had not): the border and label follow the theme, so the frame reads on a light
|
|
96
|
-
* page and a dark one. A guideColor (a type frame's own
|
|
96
|
+
* page and a dark one. A guideColor (a type frame's own color) still tints both. */
|
|
97
97
|
const borderColor = guideColor ? `color-mix(in srgb, ${guideColor} 24%, transparent)` : 'var(--kol-oq-24)'
|
|
98
98
|
const labelColor = guideColor ? `color-mix(in srgb, ${guideColor} 70%, transparent)` : 'var(--kol-fg-64)'
|
|
99
99
|
const virtualH = CANVAS_VIRTUAL_W / ratio
|
|
@@ -395,7 +395,7 @@ const ContentFilters = ({
|
|
|
395
395
|
* 05-control-chrome.md:109 — "any icon-only control in chrome is
|
|
396
396
|
* IconFrame; nothing hand-writes the square". This wore
|
|
397
397
|
* `kol-btn-md kol-btn-icon` and then removed the background, the
|
|
398
|
-
* border and the
|
|
398
|
+
* border and the color by inline style, which is the whole button
|
|
399
399
|
* paid for and thrown away — and it left the control with no
|
|
400
400
|
* states at all while the search beside it had hover.
|
|
401
401
|
*
|
|
@@ -22,7 +22,7 @@ import { filterMedia, rankMedia } from '../utilities/mediaSearch.js'
|
|
|
22
22
|
import { useMasthead, mastheadTitleClass } from '../utilities/masthead.js'
|
|
23
23
|
import { MenuItem, MenuDropdownItem, MenuDropdownDivider } from '../molecules/MenuItem.jsx'
|
|
24
24
|
import { Tooltip } from '../utilities/Popover.jsx'
|
|
25
|
-
import ContextMenu, { useContextMenu } from '../
|
|
25
|
+
import ContextMenu, { useContextMenu } from '../molecules/ContextMenu.jsx'
|
|
26
26
|
import useMarquee from '../hooks/useMarquee.js'
|
|
27
27
|
import useLongPress from '../hooks/useLongPress.js'
|
|
28
28
|
import useMediaQuery from '../hooks/useMediaQuery.js'
|
|
@@ -36,7 +36,7 @@ import { minHeightClass } from './sectionHeights.js'
|
|
|
36
36
|
* @param {boolean} [fullBleed=false] the FILL breaks the page gutter while the content keeps it —
|
|
37
37
|
* the family's shared breakout (`sectionBleed.js`, SectionFamilyFullBleed, kol-website 2026-08-31).
|
|
38
38
|
* Any member of this family can be a filled surface, and a filled surface inside `.kol-page` has its
|
|
39
|
-
*
|
|
39
|
+
* color clipped by the gutter on mobile. Viewport-relative, so unlike `.kol-full-bleed` it does not
|
|
40
40
|
* over-bleed in a parent with no gutter of its own. The section's horizontal padding re-insets the
|
|
41
41
|
* CONTENT, so only the fill moves. Default false — nothing renders differently until it is passed.
|
|
42
42
|
* @param {string} sectionClassName · wrapperClassName · cardsWrapperClassName · actionsClassName · headerClassName · headerTextWidthClass layout seams
|
|
@@ -30,7 +30,7 @@ import { minHeightClass } from './sectionHeights.js'
|
|
|
30
30
|
* @param {boolean} [fullBleed=false] the FILL breaks the page gutter while the content keeps it —
|
|
31
31
|
* the family's shared breakout (`sectionBleed.js`, SectionFamilyFullBleed, kol-website 2026-08-31).
|
|
32
32
|
* Any member of this family can be a filled surface, and a filled surface inside `.kol-page` has its
|
|
33
|
-
*
|
|
33
|
+
* color clipped by the gutter on mobile. Viewport-relative, so unlike `.kol-full-bleed` it does not
|
|
34
34
|
* over-bleed in a parent with no gutter of its own. The section's horizontal padding re-insets the
|
|
35
35
|
* CONTENT, so only the fill moves. Default false — nothing renders differently until it is passed.
|
|
36
36
|
* @param {string} className extra classes on the section
|
|
@@ -21,7 +21,7 @@ import { minHeightClass } from './sectionHeights.js'
|
|
|
21
21
|
* @param {boolean} [fullBleed=false] the FILL breaks the page gutter while the content keeps it —
|
|
22
22
|
* the family's shared breakout (`sectionBleed.js`, SectionFamilyFullBleed, kol-website 2026-08-31).
|
|
23
23
|
* Any member of this family can be a filled surface, and a filled surface inside `.kol-page` has its
|
|
24
|
-
*
|
|
24
|
+
* color clipped by the gutter on mobile. Viewport-relative, so unlike `.kol-full-bleed` it does not
|
|
25
25
|
* over-bleed in a parent with no gutter of its own. The section's horizontal padding re-insets the
|
|
26
26
|
* CONTENT, so only the fill moves. Default false — nothing renders differently until it is passed.
|
|
27
27
|
* @param {string} className · innerClassName layout seams
|
|
@@ -60,7 +60,7 @@ import { minHeightClass } from './sectionHeights.js'
|
|
|
60
60
|
* @param {boolean} [fullBleed=false] the FILL breaks the page gutter while the content keeps it
|
|
61
61
|
* (SectionNewsletterFullBleed, kol-website 2026-08-31). This card is a filled surface inside
|
|
62
62
|
* `.kol-page`, so the gutter clipped its background and left strips of page down both sides of the
|
|
63
|
-
*
|
|
63
|
+
* color. Fill and content padding are the same box, so a consumer could not bleed one without
|
|
64
64
|
* dragging the other out with it. The breakout literal is SectionHero's, character for character —
|
|
65
65
|
* two organisms in one family must not invent two ways to leave a gutter. The section's own
|
|
66
66
|
* `px-5 sm:px-8` then re-insets the content, so only the fill moves.
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { useEffect, useId, useRef, useState } from 'react'
|
|
2
|
+
import { Icon } from '@kolkrabbi/kol-icons'
|
|
2
3
|
import SearchInput from '../molecules/SearchInput.jsx'
|
|
3
4
|
import Tag from '../atoms/Tag.jsx'
|
|
5
|
+
import Kbd from '../atoms/Kbd.jsx'
|
|
6
|
+
|
|
7
|
+
/* ONE COLUMN (2026-09-30): rows, headings and footer sit on the field's own
|
|
8
|
+
* geometry — the p-2 inset, then the md control's 1px ring + 16px pad — so
|
|
9
|
+
* every glyph and word lines up under the field's icon and caret. */
|
|
10
|
+
import { glyphSize } from '../hooks/glyphLadders.js'
|
|
11
|
+
|
|
12
|
+
/* Rows bucketed by `group` in first-seen order, rank kept inside a group —
|
|
13
|
+
* one heading per group; arrows rove this order, not the engine's. */
|
|
14
|
+
function groupRows(rows) {
|
|
15
|
+
const order = []
|
|
16
|
+
const byGroup = new Map()
|
|
17
|
+
rows.forEach((r) => {
|
|
18
|
+
const g = r.group ?? ''
|
|
19
|
+
if (!byGroup.has(g)) { byGroup.set(g, []); order.push(g) }
|
|
20
|
+
byGroup.get(g).push(r)
|
|
21
|
+
})
|
|
22
|
+
return order.flatMap((g) => byGroup.get(g))
|
|
23
|
+
}
|
|
4
24
|
|
|
5
25
|
/**
|
|
6
26
|
* HighlightMatch — default row renderer: underlines the first
|
|
@@ -56,7 +76,9 @@ export function HighlightMatch({ label, query, ranges }) {
|
|
|
56
76
|
*
|
|
57
77
|
* @param {boolean} open mount/unmount the overlay
|
|
58
78
|
* @param {Function} onClose () => void — backdrop click, Escape, post-select
|
|
59
|
-
* @param {Array} results pre-filtered rows: { id, label, group?, hint? }
|
|
79
|
+
* @param {Array} results pre-filtered rows: { id, label, group?, hint?, icon? }
|
|
80
|
+
* @param {Array} [suggestions] rows shown while the query is empty (same shape) —
|
|
81
|
+
* the palette opens on somewhere to go, not a blank box
|
|
60
82
|
* @param {string} query controlled query (drives the highlight slice)
|
|
61
83
|
* @param {Function} onQueryChange (string) => void — input change
|
|
62
84
|
* @param {Function} onSelect (item) => void — row click / Enter; consumer navigates
|
|
@@ -69,7 +91,8 @@ export function HighlightMatch({ label, query, ranges }) {
|
|
|
69
91
|
export default function ShellSearchOverlay({
|
|
70
92
|
open,
|
|
71
93
|
onClose,
|
|
72
|
-
results = [],
|
|
94
|
+
results: rawResults = [],
|
|
95
|
+
suggestions = [],
|
|
73
96
|
/* EXPANDED — the palette's second state (user ruling 2026-08-01). Enter
|
|
74
97
|
* commits the query and opens `children` as the results body; the palette
|
|
75
98
|
* and the old tag overlay are one surface with two states, not two
|
|
@@ -92,6 +115,7 @@ export default function ShellSearchOverlay({
|
|
|
92
115
|
/* Has the user actually chosen a row? See the Enter branch — without this,
|
|
93
116
|
* index 0 counts as a selection and Enter navigates somewhere unasked. */
|
|
94
117
|
const [navigated, setNavigated] = useState(false)
|
|
118
|
+
const results = groupRows(query ? rawResults : suggestions)
|
|
95
119
|
const active = results.length > 0 ? Math.min(activeIndex, results.length - 1) : -1
|
|
96
120
|
|
|
97
121
|
/* Focus in on open, restore the opener on close. querySelector instead of
|
|
@@ -110,7 +134,7 @@ export default function ShellSearchOverlay({
|
|
|
110
134
|
/* Keep the active row visible inside the scrolling list. */
|
|
111
135
|
useEffect(() => {
|
|
112
136
|
if (active < 0) return
|
|
113
|
-
listRef.current?.
|
|
137
|
+
listRef.current?.querySelectorAll('[role="option"]')[active]?.scrollIntoView({ block: 'nearest' })
|
|
114
138
|
}, [active])
|
|
115
139
|
|
|
116
140
|
if (!open) return null
|
|
@@ -141,7 +165,8 @@ export default function ShellSearchOverlay({
|
|
|
141
165
|
* highlighted" is true from the first keystroke and testing `active >= 0`
|
|
142
166
|
* made Enter navigate to whatever happened to be first. Committing a
|
|
143
167
|
* query must never be a navigation you didn't choose. */
|
|
144
|
-
|
|
168
|
+
/* An empty query has nothing to commit — Enter goes to the top suggestion. */
|
|
169
|
+
if (active >= 0 && (navigated || !query)) select(results[active])
|
|
145
170
|
else onExpand?.()
|
|
146
171
|
} else if (e.key === 'Tab') {
|
|
147
172
|
/* Focus trap — the input is the palette's only tab stop. */
|
|
@@ -182,8 +207,11 @@ export default function ShellSearchOverlay({
|
|
|
182
207
|
))}
|
|
183
208
|
</div>
|
|
184
209
|
)}
|
|
210
|
+
{/* A REAL FIELD, inset in the panel (2026-09-30, shadcn's palette as
|
|
211
|
+
* the aim) — the flush `bare` strip read as a hole, not a control. */}
|
|
212
|
+
<div className="kol-tone-grey p-2">
|
|
185
213
|
<SearchInput
|
|
186
|
-
|
|
214
|
+
className="w-full"
|
|
187
215
|
value={query}
|
|
188
216
|
onChange={(e) => onQueryChange?.(e.target.value)}
|
|
189
217
|
placeholder={chips.length > 0 ? 'Narrow these results…' : placeholder}
|
|
@@ -193,6 +221,7 @@ export default function ShellSearchOverlay({
|
|
|
193
221
|
aria-controls={listId}
|
|
194
222
|
aria-activedescendant={active >= 0 ? optionId(results[active]) : undefined}
|
|
195
223
|
/>
|
|
224
|
+
</div>
|
|
196
225
|
|
|
197
226
|
{/* WHY THIS IS NOT `molecules/Dropdown` (asked 2026-08-01). Dropdown is
|
|
198
227
|
* a SELECT: a trigger, a `value`, `onChange(value)`, and rows that are
|
|
@@ -204,7 +233,8 @@ export default function ShellSearchOverlay({
|
|
|
204
233
|
*
|
|
205
234
|
* THE ROW CONTRACT (was documented nowhere):
|
|
206
235
|
* label the row's text, match-highlighted against the query
|
|
207
|
-
* group
|
|
236
|
+
* group section heading the row files under — 'Atoms', 'Documentation', 'Tags'
|
|
237
|
+
* icon optional leading glyph (kol-icons name)
|
|
208
238
|
* hint subtext shown when the LABEL was not what matched
|
|
209
239
|
* href a destination; dismisses the palette
|
|
210
240
|
* action a closure; runs and KEEPS the palette open (tag rows)
|
|
@@ -212,46 +242,60 @@ export default function ShellSearchOverlay({
|
|
|
212
242
|
{expanded ? (
|
|
213
243
|
<div className="border-t border-fg-08 max-h-[70vh] overflow-y-auto">{children}</div>
|
|
214
244
|
) : results.length > 0 && (
|
|
245
|
+
/* FIXED BODY HEIGHT — the panel holds still while the list narrows. */
|
|
215
246
|
<ul
|
|
216
247
|
ref={listRef}
|
|
217
248
|
id={listId}
|
|
218
249
|
role="listbox"
|
|
219
|
-
className="
|
|
250
|
+
className="kol-tone-grey h-80 overflow-y-auto px-2 pb-2"
|
|
220
251
|
>
|
|
221
|
-
{results.map((item, i) =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
/* preventDefault keeps focus in the input through the click */
|
|
228
|
-
onMouseDown={(e) => e.preventDefault()}
|
|
229
|
-
onClick={() => select(item)}
|
|
230
|
-
onMouseEnter={() => { setActiveIndex(i); setNavigated(true) }}
|
|
231
|
-
className={`flex items-center gap-2 px-4 py-1.5 cursor-pointer kol-mono-14 transition-colors ${
|
|
232
|
-
i === active ? 'bg-fg-08 text-fg' : 'text-fg-80'
|
|
233
|
-
}`}
|
|
234
|
-
>
|
|
235
|
-
<span className="flex flex-col min-w-0">
|
|
236
|
-
<span className="truncate">
|
|
237
|
-
<HighlightMatch label={item.label} query={query} ranges={item.highlights} />
|
|
238
|
-
</span>
|
|
239
|
-
{item.hint && (
|
|
240
|
-
<span className="kol-mono-12 text-fg-48 truncate">{item.hint}</span>
|
|
252
|
+
{results.map((item, i) => {
|
|
253
|
+
const heading = (item.group ?? '') !== (results[i - 1]?.group ?? '') || i === 0
|
|
254
|
+
return (
|
|
255
|
+
<li key={item.id} role="presentation">
|
|
256
|
+
{heading && item.group && (
|
|
257
|
+
<p className="kol-helper-12 text-fg-48 px-4 border-x border-transparent pt-3 pb-2">{item.group}</p>
|
|
241
258
|
)}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
259
|
+
<div
|
|
260
|
+
id={optionId(item)}
|
|
261
|
+
role="option"
|
|
262
|
+
aria-selected={i === active}
|
|
263
|
+
/* preventDefault keeps focus in the input through the click */
|
|
264
|
+
onMouseDown={(e) => e.preventDefault()}
|
|
265
|
+
onClick={() => select(item)}
|
|
266
|
+
onMouseEnter={() => { setActiveIndex(i); setNavigated(true) }}
|
|
267
|
+
/* the md control's box: 1px ring + 6/16 pad + gap-2 → 32px, same as the field */
|
|
268
|
+
className={`flex items-center gap-2 px-4 py-1.5 border border-transparent min-h-[var(--kol-ctl-md)] rounded-[var(--kol-radius-sm)] cursor-pointer kol-mono-14 transition-colors ${
|
|
269
|
+
i === active ? 'bg-[var(--kol-tone-bg,var(--kol-surface-secondary))] text-fg' : 'text-fg-80'
|
|
270
|
+
}`}
|
|
271
|
+
>
|
|
272
|
+
{item.icon && (
|
|
273
|
+
<span aria-hidden="true" className="flex shrink-0 text-oq-48">
|
|
274
|
+
<Icon name={item.icon} size={glyphSize('md')} />
|
|
275
|
+
</span>
|
|
276
|
+
)}
|
|
277
|
+
<span className="flex flex-col min-w-0">
|
|
278
|
+
<span className="truncate">
|
|
279
|
+
<HighlightMatch label={item.label} query={query} ranges={item.highlights} />
|
|
280
|
+
</span>
|
|
281
|
+
{item.hint && (
|
|
282
|
+
<span className="kol-mono-12 text-fg-48 truncate">{item.hint}</span>
|
|
283
|
+
)}
|
|
284
|
+
</span>
|
|
285
|
+
</div>
|
|
286
|
+
</li>
|
|
287
|
+
)
|
|
288
|
+
})}
|
|
248
289
|
</ul>
|
|
249
290
|
)}
|
|
250
|
-
{
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
291
|
+
{/* THE FOOTER SAYS WHAT ENTER DOES — always, not only once typing. */}
|
|
292
|
+
{!expanded && ((!query && results.length > 0) || (query && enterLabel)) && (
|
|
293
|
+
<div className="border-t border-fg-08 py-2">
|
|
294
|
+
<p className="flex items-center gap-2 kol-helper-12 text-fg-48 mx-2 px-4 border-x border-transparent">
|
|
295
|
+
<Kbd icon="corner-down-left" />
|
|
296
|
+
{query ? enterLabel : 'Go to page'}
|
|
297
|
+
</p>
|
|
298
|
+
</div>
|
|
255
299
|
)}
|
|
256
300
|
</div>
|
|
257
301
|
</div>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* WHY IT IS A FILLED-SECTION PROBLEM, not a newsletter one. Any member of the
|
|
9
9
|
* family can be a filled surface, and a filled surface inside `.kol-page` has its
|
|
10
|
-
*
|
|
10
|
+
* color clipped by the page gutter on mobile — strips of page down both sides of
|
|
11
11
|
* the fill. Reported once per organism until the prop is shared.
|
|
12
12
|
*
|
|
13
13
|
* NOT `.kol-full-bleed`: that escape is CONTAINER-relative, so on an organism
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
2
|
import { pushLayer, popLayer, isTopLayer } from './layerStack.js'
|
|
3
|
-
import CloseButton from '
|
|
3
|
+
import CloseButton from '../atoms/CloseButton.jsx'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* FullscreenOverlay — the scrim + centred sheet every overlay in the repo
|
|
@@ -8,7 +8,7 @@ import FullscreenOverlay from './FullscreenOverlay.jsx'
|
|
|
8
8
|
* `loader` slot — as the overlay content. Being mounted IS being visible;
|
|
9
9
|
* the parent removes this element to dismiss.
|
|
10
10
|
*
|
|
11
|
-
* The loader is a SLOT, not a built-in: inject a curtain (e.g. `<
|
|
11
|
+
* The loader is a SLOT, not a built-in: inject a curtain (e.g. `<IntroLoader/>`
|
|
12
12
|
* from `@kolkrabbi/kol-foundry`) via `loader` and wire its completion callback
|
|
13
13
|
* yourself. The slot is mounted in a `fixed inset-0` box that escapes
|
|
14
14
|
* FullscreenOverlay's centered, `--kol-container-max`-width sheet so the loader
|
|
@@ -16,7 +16,7 @@ import FullscreenOverlay from './FullscreenOverlay.jsx'
|
|
|
16
16
|
* With neither, the loader slot renders nothing (the overlay still works).
|
|
17
17
|
*
|
|
18
18
|
* @param {ReactNode} children overlay content, centered (takes precedence)
|
|
19
|
-
* @param {ReactNode} loader full-screen loading curtain, e.g. foundry's
|
|
19
|
+
* @param {ReactNode} loader full-screen loading curtain, e.g. foundry's IntroLoader
|
|
20
20
|
*/
|
|
21
21
|
export default function LoaderOverlay({ children, loader }) {
|
|
22
22
|
return (
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
flip as flipMw,
|
|
8
8
|
shift as shiftMw,
|
|
9
9
|
size as sizeMw,
|
|
10
|
+
hide as hideMw,
|
|
10
11
|
FloatingPortal,
|
|
11
12
|
FloatingFocusManager,
|
|
12
13
|
useClick,
|
|
@@ -101,6 +102,7 @@ export function usePopover({
|
|
|
101
102
|
* external DOM node (e.g. a parent container ref) instead of wiring
|
|
102
103
|
* `setReference` onto the trigger. Used by TypeBlockToolbar to anchor
|
|
103
104
|
* to its TypeFrame parent. */
|
|
105
|
+
middleware.push(hideMw({ strategy: 'referenceHidden' })) // last — it reads the final position
|
|
104
106
|
const data = useFloating({
|
|
105
107
|
open,
|
|
106
108
|
onOpenChange,
|
|
@@ -110,6 +112,13 @@ export function usePopover({
|
|
|
110
112
|
elements: referenceElement ? { reference: referenceElement } : undefined,
|
|
111
113
|
})
|
|
112
114
|
|
|
115
|
+
/* A PANEL FOLLOWS ITS TRIGGER OUT OF SIGHT (2026-09-30 — docs/menus: an open menu painted over
|
|
116
|
+
* the sticky header when its trigger scrolled under it, and floated at the page's foot when it
|
|
117
|
+
* scrolled away). The panel is portalled to <body>, so no scroll container can clip it; `hide`
|
|
118
|
+
* reports when the trigger is clipped, and the panel hides with it. */
|
|
119
|
+
const hidden = data.middlewareData.hide?.referenceHidden
|
|
120
|
+
const floatingStyles = hidden ? { ...data.floatingStyles, visibility: 'hidden' } : data.floatingStyles
|
|
121
|
+
|
|
113
122
|
const interactions = useInteractions([
|
|
114
123
|
useClick(data.context, { enabled: click }),
|
|
115
124
|
useHover(data.context, { enabled: hover, delay: hoverDelay, move: false }),
|
|
@@ -118,7 +127,7 @@ export function usePopover({
|
|
|
118
127
|
useRole(data.context, { role }),
|
|
119
128
|
])
|
|
120
129
|
|
|
121
|
-
return { ...data, ...interactions, open }
|
|
130
|
+
return { ...data, floatingStyles, ...interactions, open }
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
/**
|
package/src/utilities/tone.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* primary · secondary · inverted · outline · ghost · grey · sunken
|
|
6
6
|
*
|
|
7
|
-
* `secondary` paints the PAGE SURFACE and `inverted` the text
|
|
7
|
+
* `secondary` paints the PAGE SURFACE and `inverted` the text color as fill
|
|
8
8
|
* (tone-secondary-is-inverse, 2026-09-03; user: "that tone should be called
|
|
9
9
|
* secondary. What is currently secondary should be called inverted") — 0.134.0
|
|
10
10
|
* had lifted `secondary` from Button's variant, which was already an inverse.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|