@kolkrabbi/kol-component 0.228.0 → 0.230.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 -3
- package/src/atoms/ActionButton.jsx +1 -1
- package/src/atoms/Button.jsx +8 -2
- package/src/atoms/IconFrame.jsx +1 -1
- package/src/atoms/Input.jsx +3 -2
- 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/LabeledControl.jsx +7 -4
- package/src/molecules/MenuItem.jsx +9 -1
- package/src/molecules/Modal.jsx +1 -1
- package/src/molecules/PageHeader.jsx +14 -7
- 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 +7 -4
- 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 +4 -11
- package/src/organisms/DocumentEditor.jsx +8 -7
- package/src/organisms/MediaLibrary.jsx +3 -5
- package/src/organisms/MediaLibraryExplorer.jsx +5 -1
- package/src/organisms/MediaLibraryPages.jsx +40 -17
- 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/SettingsPanel.jsx +5 -2
- package/src/organisms/ShortcutsOverlay.jsx +8 -4
- package/src/organisms/sectionBleed.js +1 -1
- package/src/utilities/ErrorBoundary.jsx +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/masthead.js +39 -0
- package/src/utilities/mediaSearch.js +41 -0
- package/src/utilities/searchItems.js +30 -0
- 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.230.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,7 +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-
|
|
28
|
+
"@kolkrabbi/kol-search": "^0.2.0",
|
|
29
|
+
"@kolkrabbi/kol-markdown": "^0.1.2"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
32
|
"@kolkrabbi/kol-icons": ">=0.22.0",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@kolkrabbi/kol-icons": "^0.29.
|
|
40
|
+
"@kolkrabbi/kol-icons": "^0.29.1"
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
42
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
|
|
@@ -81,8 +81,14 @@ const Button = ({
|
|
|
81
81
|
* grey) or its own bundle (nav · danger · accent); `nav` is ghost's chrome
|
|
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
|
+
/* An UNKNOWN variant is a typo, not a request for the inverted fill — it fell back to
|
|
85
|
+
* `kol-btn-secondary` (the text color as fill) until 2026-09-29, so a misspelt variant
|
|
86
|
+
* shipped inverted. It now stamps nothing, like an unset variant: the wrapper's tone,
|
|
87
|
+
* else primary — and says so in dev. */
|
|
84
88
|
const KNOWN = ['primary', 'secondary', 'accent', 'outline', 'ghost', 'nav', 'danger', 'grey']
|
|
85
|
-
const
|
|
89
|
+
const known = !resolvedVariant || KNOWN.includes(resolvedVariant)
|
|
90
|
+
if (!known && import.meta.env.DEV) console.warn(`Button: unknown variant "${resolvedVariant}" — rendering the inherited tone (primary)`)
|
|
91
|
+
const variantClass = resolvedVariant && known ? `kol-btn-${resolvedVariant}` : ''
|
|
86
92
|
|
|
87
93
|
// Add size class — pairs the padding rule with its mono type class.
|
|
88
94
|
const sizeClass = size === 'sm'
|
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.
|
package/src/atoms/Input.jsx
CHANGED
|
@@ -158,8 +158,9 @@ export default function Input({
|
|
|
158
158
|
* or <label> at the same kol-mono-N — Chromium computes input height
|
|
159
159
|
* from the font's ascender+descender (font-metric), not strictly from
|
|
160
160
|
* CSS line-height. Result: kol-control-sm ends up 26.5px instead of 26.
|
|
161
|
-
* h-4 / h-[18px] / h-[22px] match the kol-mono-12 / -14 / -16 line-heights
|
|
162
|
-
|
|
161
|
+
* h-4 / h-[18px] / h-[22px] match the kol-mono-12 / -14 / -16 line-heights; on touch every size is
|
|
162
|
+
* the 16 / 22 rung (D5, 2026-09-29 — see SearchInput) */
|
|
163
|
+
const heightCls = `${size === 'xs' ? 'h-3' : size === 'sm' ? 'h-4' : size === 'md' ? 'h-[18px]' : 'h-[22px]'} pointer-coarse:h-[22px]`
|
|
163
164
|
|
|
164
165
|
const inputCls = [
|
|
165
166
|
'min-w-0 bg-transparent border-none outline-none text-auto',
|
|
@@ -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'
|
|
@@ -73,8 +73,8 @@ export { default as InspectorRail } from './molecules/InspectorRail.jsx'
|
|
|
73
73
|
/* SelectionOverlay's two siblings — same 1080-virtual contract, same zoom
|
|
74
74
|
* division (editor-panels-the-held-specs B2). `pathMath` is their geometry,
|
|
75
75
|
* 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 './
|
|
76
|
+
export { default as PathNodeOverlay } from './utilities/PathNodeOverlay.jsx'
|
|
77
|
+
export { default as CropOverlay } from './utilities/CropOverlay.jsx'
|
|
78
78
|
export { default as LayerStack, AddLayerButton, BLEND_MODES } from './organisms/LayerStack.jsx'
|
|
79
79
|
export { TYPE_LABELS, BOOL_OP_LABELS, SHAPE_KIND_LABELS, labelForLayer, rowLabelForLayer, findLayerDeep } from './hooks/layerTree.js'
|
|
80
80
|
export { default as TimelineDock, sampleTrack, TIMELINE_EASINGS } from './organisms/TimelineDock.jsx'
|
|
@@ -88,7 +88,7 @@ export { default as ButtonGroup } from './utilities/ButtonGroup.jsx'
|
|
|
88
88
|
/* monorepo sets (P6–P10) — molecule members */
|
|
89
89
|
export { default as AlignmentGrid } from './molecules/AlignmentGrid.jsx'
|
|
90
90
|
export { default as ImageBlock } from './molecules/ImageBlock.jsx'
|
|
91
|
-
export { default as SelectionOverlay } from './
|
|
91
|
+
export { default as SelectionOverlay } from './utilities/SelectionOverlay.jsx'
|
|
92
92
|
export { default as VideoBlock, getEmbedUrl } from './molecules/VideoBlock.jsx'
|
|
93
93
|
export { default as SectionCardItem, default as CardFeatureItem } from './molecules/SectionCardItem.jsx'
|
|
94
94
|
export { default as CodeBlock } from './molecules/CodeBlock.jsx'
|
|
@@ -152,6 +152,7 @@ export { default as EmblaNav } from './molecules/EmblaNav.jsx'
|
|
|
152
152
|
export { default as MobileTabBar, TABBAR_H } from './molecules/MobileTabBar.jsx'
|
|
153
153
|
export { default as ContentFilters } from './organisms/ContentFilters.jsx'
|
|
154
154
|
export { default as PageHeader } from './molecules/PageHeader.jsx'
|
|
155
|
+
export { MastheadContext, MASTHEADS, useMasthead, mastheadTitleClass } from './utilities/masthead.js'
|
|
155
156
|
export { default as ErrorBoundary } from './utilities/ErrorBoundary.jsx'
|
|
156
157
|
export { default as FeatureSplit } from './organisms/FeatureSplit.jsx'
|
|
157
158
|
export { default as FeaturedCarousel } from './organisms/FeaturedCarousel.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' : ''}`}
|
|
@@ -48,19 +48,22 @@ export default function LabeledControl({
|
|
|
48
48
|
/* `'auto'`: the label is the yielding cell — flex-1, truncating — and the
|
|
49
49
|
* control hugs. The control still carries min-w-0 + overflow-hidden so a
|
|
50
50
|
* combo wider than the whole column CLIPS at the column edge rather than
|
|
51
|
-
* painting over the neighbour (the invariant: no cell outside its column).
|
|
51
|
+
* painting over the neighbour (the invariant: no cell outside its column).
|
|
52
|
+
* BELOW `sm` IT STACKS (apps review, 2026-09-29): the yielding label yielded to
|
|
53
|
+
* nothing on a phone — a sentence-long combo took the whole row and the label
|
|
54
|
+
* showed as "C" or not at all. Label on its line, the value under it. */
|
|
52
55
|
const labelAuto = labelWidth === 'auto'
|
|
53
56
|
return (
|
|
54
|
-
<div className={`flex items-center gap-3 ${className}`}>
|
|
57
|
+
<div className={`flex ${labelAuto ? 'flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-3' : 'items-center gap-3'} ${className}`}>
|
|
55
58
|
{showLabel && (
|
|
56
59
|
<span
|
|
57
|
-
className={`kol-helper-10 tracking-widest text-meta ${labelAuto ? '
|
|
60
|
+
className={`kol-helper-10 tracking-widest text-meta ${labelAuto ? 'max-w-full min-w-0 truncate sm:flex-1' : 'shrink-0'}`}
|
|
58
61
|
style={labelAuto ? undefined : { width: labelWidth }}
|
|
59
62
|
>
|
|
60
63
|
{labelInner}
|
|
61
64
|
</span>
|
|
62
65
|
)}
|
|
63
|
-
<div className={labelAuto ? 'min-w-0 overflow-hidden' : 'flex-1 min-w-0'}>{children}</div>
|
|
66
|
+
<div className={labelAuto ? 'min-w-0 max-w-full sm:overflow-hidden' : 'flex-1 min-w-0'}>{children}</div>
|
|
64
67
|
</div>
|
|
65
68
|
)
|
|
66
69
|
}
|
|
@@ -19,6 +19,9 @@ import { PopoverPanel, usePopover } from '../utilities/Popover.jsx'
|
|
|
19
19
|
* `Dropdown` molecule instead — MenuItem is for action menus / popovers
|
|
20
20
|
* that hold arbitrary children.
|
|
21
21
|
*/
|
|
22
|
+
/* the ladder's tokens, so the touch rung (D5, 2026-09-29) grows the trigger with its row */
|
|
23
|
+
const TRIGGER_H = { xs: 'var(--kol-ctl-xs)', sm: 'var(--kol-ctl-sm)', md: 'var(--kol-ctl-md)', lg: 'var(--kol-ctl-lg)' }
|
|
24
|
+
|
|
22
25
|
export function MenuItem({
|
|
23
26
|
label,
|
|
24
27
|
children,
|
|
@@ -33,6 +36,10 @@ export function MenuItem({
|
|
|
33
36
|
* an affordance; neither reference draws one (ColumnBrowserMobileViews item
|
|
34
37
|
* 15, kol-r2b2 2026-09-04). Default keeps every existing call-site. */
|
|
35
38
|
caret = true,
|
|
39
|
+
/* THE TRIGGER IS ON THE LADDER (apps review 2026-09-29). It was a fixed `h-8` — 32px whatever sat
|
|
40
|
+
* beside it, so media's phone `···` stood 32 next to a 26 `sm` search field. `size` pins it to the
|
|
41
|
+
* control heights (01-foundations/09-sizes: 22 · 26 · 32 · 40); `md` is the 32 it always was. */
|
|
42
|
+
size = 'md',
|
|
36
43
|
}) {
|
|
37
44
|
const [open, setOpen] = useState(defaultOpen)
|
|
38
45
|
const popover = usePopover({
|
|
@@ -51,7 +58,8 @@ export function MenuItem({
|
|
|
51
58
|
ref={popover.refs.setReference}
|
|
52
59
|
{...popover.getReferenceProps()}
|
|
53
60
|
type="button"
|
|
54
|
-
className={`kol-menu-btn kol-helper-12 px-3
|
|
61
|
+
className={`kol-menu-btn kol-helper-12 px-3 inline-flex items-center gap-2 rounded text-body hover:text-emphasis transition-colors ${buttonClassName}`}
|
|
62
|
+
style={{ height: TRIGGER_H[size] ?? TRIGGER_H.md }}
|
|
55
63
|
>
|
|
56
64
|
<span>{label}</span>
|
|
57
65
|
{caret && (
|
package/src/molecules/Modal.jsx
CHANGED
|
@@ -119,7 +119,7 @@ function ModalView({ state, closeWith }) {
|
|
|
119
119
|
/>
|
|
120
120
|
)}
|
|
121
121
|
<div className="flex gap-2 justify-end">
|
|
122
|
-
{state.kind !== 'alert' && <Button
|
|
122
|
+
{state.kind !== 'alert' && <Button size="sm" onClick={cancel}>{state.cancelLabel ?? 'Cancel'}</Button>}
|
|
123
123
|
<Button variant="primary" size="sm" onClick={submit}>{state.okLabel ?? 'OK'}</Button>
|
|
124
124
|
</div>
|
|
125
125
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import SectionText from './SectionText.jsx'
|
|
2
|
+
import { TITLE_ROLES, useMasthead } from '../utilities/masthead.js'
|
|
2
3
|
/**
|
|
3
4
|
* PageHeader — the page's masthead: an optional eyebrow, the title, and a
|
|
4
5
|
* sub-line.
|
|
@@ -62,19 +63,25 @@ import SectionText from './SectionText.jsx'
|
|
|
62
63
|
* with an `!important` on the margin — a DS text role re-implemented outside.
|
|
63
64
|
* @param {string} subtitleMaxWidth the lede's measure (e.g. '800px' or '60ch'), a prop
|
|
64
65
|
* instead of a consumer selector reaching inside
|
|
66
|
+
* @param {'display'|'mono'} masthead the APP's masthead (utilities/masthead.js, 2026-09-29): inside
|
|
67
|
+
* a Shell it comes from `AppShell masthead` and WINS over `voice`/`size`, and
|
|
68
|
+
* `display` drops the subtitle — one app, one header. Outside a Shell, unset =
|
|
69
|
+
* the props as given, so no site page moves.
|
|
65
70
|
* The bottom rhythm is `--kol-page-header-mb` (default 40px): inline, as before, but through a
|
|
66
71
|
* variable a consumer can re-point where an inline literal could only be `!important`-ed.
|
|
67
72
|
*/
|
|
68
73
|
/* the title role per size and voice — the one thing PageHeader knows that the
|
|
69
|
-
* base does not, because the base's ladder is the SECTION scale
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
mono: { sm: 'kol-mono-heading-03', md: 'kol-mono-display-03', lg: 'kol-mono-display-02' },
|
|
73
|
-
}
|
|
74
|
+
* base does not, because the base's ladder is the SECTION scale. Lives in
|
|
75
|
+
* utilities/masthead.js since 2026-09-29 so a tool's own title reads the same table. */
|
|
76
|
+
const TITLE = TITLE_ROLES
|
|
74
77
|
/* the ONE thing the registers disagree about */
|
|
75
78
|
const LEDE = { app: 'kol-mono-14', site: 'kol-sans-body-01' }
|
|
76
79
|
|
|
77
|
-
export default function PageHeader({ eyebrow, title, subtitle, actions, subtitleMaxWidth, size = 'md', voice = 'sans', register = 'app', titleClass, className = '' }) {
|
|
80
|
+
export default function PageHeader({ eyebrow, title, subtitle: ownSubtitle, actions, subtitleMaxWidth, size: ownSize = 'md', voice: ownVoice = 'sans', register = 'app', titleClass, masthead, className = '' }) {
|
|
81
|
+
const m = useMasthead(masthead)
|
|
82
|
+
const voice = m ? m.voice : ownVoice
|
|
83
|
+
const size = m ? m.size : ownSize
|
|
84
|
+
const subtitle = m && !m.subtitle ? undefined : ownSubtitle
|
|
78
85
|
const roles = TITLE[voice] ?? TITLE.sans
|
|
79
86
|
return (
|
|
80
87
|
/* the block owns its own rhythm — margins inline, never in a shared type
|
|
@@ -83,7 +90,7 @@ export default function PageHeader({ eyebrow, title, subtitle, actions, subtitle
|
|
|
83
90
|
eyebrow={eyebrow}
|
|
84
91
|
headline={title}
|
|
85
92
|
headlineAs="h1"
|
|
86
|
-
headlineClass={`text-fg-96 ${titleClass ?? roles[size] ?? roles.md}`}
|
|
93
|
+
headlineClass={`text-fg-96 ${titleClass ?? roles[size] ?? roles.md}${m?.upper ? ' uppercase' : ''}`}
|
|
87
94
|
body={subtitle}
|
|
88
95
|
bodyClass={`text-oq-64 ${LEDE[register] ?? LEDE.app} min-w-0`}
|
|
89
96
|
actions={actions}
|
|
@@ -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
|
|
|
@@ -103,8 +103,9 @@ export default function SearchInput({
|
|
|
103
103
|
if (expanding && isOpen) inputRef.current?.focus()
|
|
104
104
|
}, [expanding, isOpen])
|
|
105
105
|
|
|
106
|
-
/* the pinned squares, 01-foundations/09-sizes.md — 22 · 26 · 32 · 40
|
|
107
|
-
|
|
106
|
+
/* the pinned squares, 01-foundations/09-sizes.md — 22 · 26 · 32 · 40, read from the theme's
|
|
107
|
+
* `--kol-ctl-*` so the touch rung (D5, 2026-09-29) grows them with every other control */
|
|
108
|
+
const square = `var(--kol-ctl-${['xs', 'sm', 'md', 'lg'].includes(size) ? size : 'md'})`
|
|
108
109
|
/* THE OPEN FIELD'S HEIGHT IS ITS OWN KNOB, defaulting to the square.
|
|
109
110
|
*
|
|
110
111
|
* /work's pill is `h-9` open AND closed, sitting level with a 36px toggle —
|
|
@@ -209,8 +210,10 @@ export default function SearchInput({
|
|
|
209
210
|
|
|
210
211
|
/* Same height pin as Input: Chromium sizes an <input> from font metrics,
|
|
211
212
|
* not CSS line-height, so without this the shell lands ~0.5px tall.
|
|
212
|
-
* h-4 / h-[18px] match the kol-mono-12 / -14 line-heights.
|
|
213
|
-
|
|
213
|
+
* h-4 / h-[18px] match the kol-mono-12 / -14 line-heights. On TOUCH the input types 16 / 22 (the
|
|
214
|
+
* touch rung, D5 2026-09-29), and this utility out-ranked the theme's coarse rule — 16px text in a
|
|
215
|
+
* 16px line in a 26px box; `pointer-coarse:` carries the 22 so the box grows with its row. */
|
|
216
|
+
const heightCls = `${size === 'sm' ? 'h-4' : 'h-[18px]'} pointer-coarse:h-[22px]`
|
|
214
217
|
|
|
215
218
|
const inputCls = [
|
|
216
219
|
'min-w-0 flex-1 bg-transparent border-none outline-none text-auto',
|
|
@@ -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
|
|
@@ -7,6 +7,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
7
7
|
import SearchInput from '../molecules/SearchInput.jsx'
|
|
8
8
|
import IconFrame from '../atoms/IconFrame.jsx'
|
|
9
9
|
import { Tooltip } from '../utilities/Popover.jsx'
|
|
10
|
+
import { filterItems } from '../utilities/searchItems.js'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* ContentFilters — universal filter component for content grids.
|
|
@@ -214,16 +215,8 @@ const ContentFilters = ({
|
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
const filteredItems = useMemo(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const q = searchText.toLowerCase()
|
|
220
|
-
result = result.filter((item) =>
|
|
221
|
-
searchKeys.some((key) => {
|
|
222
|
-
const val = item[key]
|
|
223
|
-
return val && String(val).toLowerCase().includes(q)
|
|
224
|
-
}),
|
|
225
|
-
)
|
|
226
|
-
}
|
|
218
|
+
/* the KOL engine, not a private substring match (apps review 2026-09-29) — utilities/searchItems */
|
|
219
|
+
let result = filterItems(items, searchText, { keys: searchKeys })
|
|
227
220
|
if (activeFilters.size === 0) return result
|
|
228
221
|
return result.filter((item) => {
|
|
229
222
|
let matches = true
|
|
@@ -402,7 +395,7 @@ const ContentFilters = ({
|
|
|
402
395
|
* 05-control-chrome.md:109 — "any icon-only control in chrome is
|
|
403
396
|
* IconFrame; nothing hand-writes the square". This wore
|
|
404
397
|
* `kol-btn-md kol-btn-icon` and then removed the background, the
|
|
405
|
-
* border and the
|
|
398
|
+
* border and the color by inline style, which is the whole button
|
|
406
399
|
* paid for and thrown away — and it left the control with no
|
|
407
400
|
* states at all while the search beside it had hover.
|
|
408
401
|
*
|