@kolkrabbi/kol-component 0.204.0 → 0.205.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.204.0",
3
+ "version": "0.205.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",
@@ -1,27 +1,28 @@
1
- import Button from '../atoms/Button.jsx'
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
+ import SegmentedToggle from '../atoms/SegmentedToggle.jsx'
3
+ import { glyphSize } from '../hooks/glyphLadders.js'
2
4
 
3
5
  /**
4
- * AlignmentGrid — a six-cell alignment row: horizontal start/center/end then
5
- * vertical start/center/end, each a quiet icon Button that emits an
6
- * `(axis, mode)` alignment intent. Driven off a static config array (the
7
- * default 6, overridable via `items`). Presentation only — the consumer wires
8
- * `onAlign` to whatever "align these" means in its context (align a box to the
9
- * canvas bounds, align a multi-selection's common bbox, …).
6
+ * AlignmentGrid — the align control: TWO three-way strips, X and Y.
10
7
  *
11
- * Ported from the brand editor's AlignmentPanel with the store coupling
12
- * dropped (per lobby spec): `useComposeState().alignSelected` an `onAlign`
13
- * prop; the hand-rolled `kol-btn-quiet` buttons DS `Button` (quiet +
14
- * iconOnly) so the DS owns the button atom; `EditorIcon` the DS Icon
15
- * (through Button).
8
+ * Rebuilt on `SegmentedToggle` 2026-09-03 (`editor-chrome-review`, the user's
9
+ * own pass over the running editor: *"alignment isnt using segmentedtoggle?"*).
10
+ * It was six bare quiet icon Buttons in one `grid-cols-6` six loose controls
11
+ * where the thing itself is two three-way choices, which is what a segmented
12
+ * strip is for. The shape had been ruled on `editor-set-is-behind-its-source`
13
+ * and the PRESS TREATMENT was the open question; his reaction answered it.
16
14
  *
17
- * [icon-gap] The source's `align-h-{start,center,end}` / `align-v-{start,
18
- * center,end}` glyph names are NOT in the loader. Remapped to the closest
19
- * existing loader glyphs (stroke/layout): `align-horizontal-{left,center,
20
- * right}` and `align-vertical-{top,center,bottom}`. Same six align marks,
21
- * different names no visual gap.
15
+ * MOMENTARY, NOT A SELECTION. `value={null}` puts the strip in its stateless
16
+ * mode: no cell is ever lit, because "align left" is an action you fire, not a
17
+ * state the object is in — the object's alignment is not a property this reads
18
+ * back. The press is the cell's own `:active`, drawn in the theme
19
+ * (`.kol-seg-cell:active`), which is why no `tone` or `pressed` prop was minted
20
+ * for it: `tone` is the GROUND axis and says nothing about a momentary press.
22
21
  *
23
- * @param {(axis:'h'|'v', mode:'start'|'center'|'end') => void} onAlign fired on cell click
24
- * @param {Array} items [{ axis, mode, icon, title }] cells (default the standard 6)
22
+ * @param {(axis:'h'|'v', mode:'start'|'center'|'end') => void} onAlign - Fired on press
23
+ * @param {Array} items - `[{ axis, mode, icon, title }]` cells (default the standard 6, three per axis)
24
+ * @param {'xs'|'sm'|'md'|'lg'} size - The strips' rung (default 'sm')
25
+ * @param {string} className - Extra classes on the wrapper
25
26
  */
26
27
  const ALIGN_BUTTONS = [
27
28
  { axis: 'h', mode: 'start', icon: 'align-horizontal-left', title: 'Align left' },
@@ -32,22 +33,37 @@ const ALIGN_BUTTONS = [
32
33
  { axis: 'v', mode: 'end', icon: 'align-vertical-bottom', title: 'Align bottom' },
33
34
  ]
34
35
 
35
- export default function AlignmentGrid({ onAlign, items = ALIGN_BUTTONS }) {
36
+ export default function AlignmentGrid({ onAlign, items = ALIGN_BUTTONS, size = 'sm', className = '' }) {
37
+ const strip = (axis) => items.filter((b) => b.axis === axis)
38
+
36
39
  return (
37
- <div className="grid grid-cols-6 gap-1">
38
- {items.map((b) => (
39
- <Button
40
- key={`${b.axis}-${b.mode}`}
41
- quiet
42
- iconOnly={b.icon}
43
- iconSize={16}
44
- onClick={() => onAlign?.(b.axis, b.mode)}
45
- title={b.title}
46
- aria-label={b.title}
47
- className="w-full"
48
- style={{ height: 28, padding: 6 }}
49
- />
50
- ))}
40
+ <div className={`kol-alignment-grid flex flex-col gap-1 ${className}`.trim()}>
41
+ {['h', 'v'].map((axis) => {
42
+ const cells = strip(axis)
43
+ if (!cells.length) return null
44
+ return (
45
+ <SegmentedToggle
46
+ key={axis}
47
+ variant="filled"
48
+ size={size}
49
+ /* stateless: never lit, the press IS the feedback */
50
+ value={null}
51
+ ariaLabel={axis === 'h' ? 'Align horizontally' : 'Align vertically'}
52
+ /* the cell's `label` takes a NODE, so the glyph needs no new prop
53
+ * on SegmentedToggle — and the glyph size comes from the ADJACENT
54
+ * ladder, never a number typed here */
55
+ options={cells.map((b) => ({
56
+ value: `${b.axis}-${b.mode}`,
57
+ label: <Icon name={b.icon} size={glyphSize(size)} />,
58
+ ariaLabel: b.title,
59
+ }))}
60
+ onChange={(v) => {
61
+ const cell = cells.find((b) => `${b.axis}-${b.mode}` === v)
62
+ if (cell) onAlign?.(cell.axis, cell.mode)
63
+ }}
64
+ />
65
+ )
66
+ })}
51
67
  </div>
52
68
  )
53
69
  }
@@ -1,4 +1,5 @@
1
1
  import { useState } from 'react'
2
+ import { toneClass } from './tone.js'
2
3
  import {
3
4
  useFloating,
4
5
  autoUpdate,
@@ -189,6 +190,14 @@ export function PopoverPanel({
189
190
  panel = true,
190
191
  modal = false,
191
192
  focus = true,
193
+ /* THE FLOATING SURFACE'S GROUND (editor-chrome-review, kol-fxr 2026-09-03 —
194
+ * the user on the Editor / Labs / Randomiser list: *"can we change it border
195
+ * oq-04 and background as tone prop?"*). The panel painted
196
+ * `surface-secondary` unconditionally, which in dark is LIGHTER than the bar
197
+ * it drops from, so the layering read inverted. Unset it still does exactly
198
+ * that, so nothing existing moves; a tone paints that tone's ground instead,
199
+ * through the same `toneClass` every other control uses. */
200
+ tone,
192
201
  className = '',
193
202
  style: extraStyle,
194
203
  }) {
@@ -199,7 +208,7 @@ export function PopoverPanel({
199
208
  * carries the stacking guarantee — floats must top overlay chrome
200
209
  * (.kol-overlay z-100), or a dropdown inside a FullscreenOverlay renders
201
210
  * under the sheet and its options can't be clicked. */
202
- const cls = ['kol-popover-float', panel && 'kol-popover', className].filter(Boolean).join(' ')
211
+ const cls = ['kol-popover-float', panel && 'kol-popover', tone && toneClass(tone), className].filter(Boolean).join(' ')
203
212
 
204
213
  /* `data-editor-keep-selection` mirrors the marker on the EditorShell
205
214
  * root. Popovers render via FloatingPortal (mounted on <body>, outside