@kolkrabbi/kol-component 0.141.0 → 0.143.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.141.0",
3
+ "version": "0.143.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
@@ -152,6 +152,9 @@ export { parseFrontmatter } from './utilities/frontmatter.js'
152
152
  export { readCover } from './utilities/id3.js'
153
153
  export { kindOf, extOf, isSystemFile, KINDS, KIND_LABEL, DEFAULT_KINDS, isSegment, groupSegments, groupVariants, posterFor, partition } from './utilities/mediaKinds.js'
154
154
  export { nearestRatio, RATIOS } from './utilities/ratios.js'
155
+ /* the grab gesture — shared so kol-shell's NavRail and kol-framework's
156
+ * useDragResize wear ONE implementation (OneGrabGestureBothRails, 2026-08-30) */
157
+ export { default as useGrabEdge } from './utilities/useGrabEdge.js'
155
158
  export { default as markdownToHtml, inlineToHtml } from './utilities/markdownToHtml.js'
156
159
  export { default as RecordManager } from './organisms/RecordManager.jsx'
157
160
  export { default as SpectrumGrid } from './organisms/SpectrumGrid.jsx'
@@ -58,9 +58,19 @@ const BOX = {
58
58
  /* typeface's card is a FIXED 500px tall specimen board, not a ratio — the
59
59
  * shipped item is `h-[500px]`, and a ratio re-crops the glyph at every
60
60
  * column width, which is the one thing a specimen must not do. */
61
- /* REST COLOURS ONLY (TypefaceCardRestSkin, kol-website 2026-08-30): `border`
62
- * and `bg` are showcase's, so a typeface card sits on the same rest skin as a
63
- * work card. `layout`, `pad`, `height`, the RATIO, REVEAL_BG, MEDIA and every
61
+ /* REST COLOURS ONLY, TAKEN FROM THE ROW (TypefaceCardRestSkinFix, kol-website
62
+ * 2026-08-30). Filled surface, no outline `ContentRow`'s showcase pair.
63
+ *
64
+ * 0.140.0 copied `BOX.showcase` from THIS map instead, because it is the
65
+ * entry sitting next to this one. That was wrong: `showcase`'s CARD is a
66
+ * `drawer` — outlined and unfilled — and the ask was to match the ROW, which
67
+ * is filled with no outline. Every /foundry grid card shipped as an outlined
68
+ * empty box, the opposite of the rows beside it.
69
+ *
70
+ * "Make the card look like the row" means READ THE ROW'S BOX. The two maps
71
+ * share variant names and not treatments.
72
+ *
73
+ * `layout`, `pad`, `height`, the RATIO, REVEAL_BG, MEDIA and every
64
74
  * hover treatment are untouched and deliberately different.
65
75
  *
66
76
  * NOT derived with a spread. The row's version of this ask was resolved that
@@ -68,7 +78,7 @@ const BOX = {
68
78
  * it is keyed by variant NAME — a second ticket to undo. `RATIO`, `REVEAL_BG`
69
79
  * and `MEDIA` here are separate name-keyed maps too; a spread cannot reach
70
80
  * them either. Two values, set by hand. */
71
- showcaseCanvas: { layout: 'canvas', border: 'var(--kol-fg-04)', bg: null, pad: 'var(--kol-pad-card-lg)', height: 500 },
81
+ showcaseCanvas: { layout: 'canvas', border: 'transparent', bg: 'var(--kol-surface-secondary)', pad: 'var(--kol-pad-card-lg)', height: 500 },
72
82
  }
73
83
 
74
84
  /* HOVER is a bg STEP on the opaque tier, per 05-control-chrome.md's state model
@@ -0,0 +1,61 @@
1
+ import { useEffect } from 'react'
2
+ import { GRAB } from './motion.js'
3
+ import gsap from 'gsap'
4
+
5
+ /**
6
+ * useGrabEdge — the proximity-wake grab pill, for ANY resizable edge.
7
+ *
8
+ * Lifted out of kol-shell's `NavRail` 2026-08-30 (OneGrabGestureBothRails,
9
+ * kol-fxr — user: *"why dont we use the grab animation and other sidenav
10
+ * settings to be consistent?"*). fxr's `/labs` renders two rails on one screen,
11
+ * the shell's on the left and a params rail on the right, and only the left one
12
+ * came alive as the cursor neared it. Two implementations of one gesture in two
13
+ * packages, and neither was consumer code, so no consumer could converge them.
14
+ *
15
+ * It lives in kol-component because that is the only package BOTH rails can
16
+ * reach: kol-shell dropped its kol-framework peer in 0.16.0, so shell cannot
17
+ * import framework's hook and framework cannot import shell's. `gsap` and the
18
+ * `GRAB` tuning constants were already here.
19
+ *
20
+ * WHAT IT DOES. Watches the pointer; within `GRAB.near` of the handle's centre
21
+ * line it adds `is-near` (hysteresis out at `GRAB.sleep`, so the pill does not
22
+ * flicker on the boundary) and travels the pill along the edge to meet the
23
+ * cursor — bipolar from the middle across `GRAB.range` of the height, so it
24
+ * never rides up under a logomark. `GRAB.stick` is the dwell: a move smaller
25
+ * than that leaves the pill where it is, which is what stops it twitching.
26
+ *
27
+ * The element needs the `.kol-rail-grab` class for the drawing (kol-animation.css);
28
+ * this hook only supplies `is-near` and `--kol-rail-grab-y`.
29
+ *
30
+ * @param {React.RefObject<HTMLElement>} ref the grab handle
31
+ */
32
+ export default function useGrabEdge(ref) {
33
+ useEffect(() => {
34
+ let raf = 0
35
+ const onMove = ({ clientX, clientY }) => {
36
+ if (raf) return
37
+ raf = requestAnimationFrame(() => {
38
+ raf = 0
39
+ const h = ref.current
40
+ if (!h) return
41
+ const r = h.getBoundingClientRect()
42
+ const dist = Math.abs(clientX - (r.left + r.width / 2))
43
+ const near = dist <= GRAB.near || (h.classList.contains('is-near') && dist <= GRAB.sleep)
44
+ h.classList.toggle('is-near', near)
45
+ if (!near) return
46
+ /* the travel band: bipolar from the middle, GRAB.range of the height */
47
+ const edge = (r.height * (1 - GRAB.range)) / 2
48
+ const along = Math.min(Math.max(clientY - r.top, edge), r.height - edge)
49
+ if (h.dataset.grabSeeded && Math.abs(along - Number(h.dataset.grabTarget)) < GRAB.stick) return
50
+ h.dataset.grabTarget = String(along)
51
+ const vars = { '--kol-rail-grab-y': `${along}px` }
52
+ /* the CSS fallback is 50%, a percentage — nothing to tween from, so the
53
+ * first sighting sets and every move after tweens */
54
+ if (h.dataset.grabSeeded) gsap.to(h, { ...vars, ...GRAB.travel, overwrite: 'auto' })
55
+ else { gsap.set(h, vars); h.dataset.grabSeeded = '1' }
56
+ })
57
+ }
58
+ window.addEventListener('pointermove', onMove)
59
+ return () => { window.removeEventListener('pointermove', onMove); cancelAnimationFrame(raf) }
60
+ }, [ref])
61
+ }