@kolkrabbi/kol-component 0.28.1 → 0.30.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.28.1",
3
+ "version": "0.30.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",
@@ -24,7 +24,7 @@
24
24
  "@floating-ui/react": "^0.27.19",
25
25
  "embla-carousel-react": "^8.6.0",
26
26
  "react-syntax-highlighter": "^16.1.1",
27
- "@kolkrabbi/kol-icons": "0.10.0"
27
+ "@kolkrabbi/kol-icons": "0.11.1"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "framer-motion": "^12.0.0",
@@ -68,11 +68,15 @@ export function usePopover({
68
68
  middleware.push(
69
69
  sizeMw({
70
70
  apply({ rects, elements }) {
71
- /* EXACT width, not a floor (2026-08-09) "one piece means one
72
- * width": with minWidth alone, a wide row let the panel outgrow the
73
- * trigger it claims to continue. Sole consumer is Dropdown. */
71
+ /* FLOOR again, not exact (2026-08-09 evening, user: "if you have
72
+ * to truncate 'centered' then its too narrow" overrides the
73
+ * morning's exact-width reading of the one-piece ruling for the
74
+ * narrow case). The panel is never NARROWER than the trigger, and
75
+ * grows when its rows carry chrome the trigger doesn't (the
76
+ * selected row's check). Sole consumer is Dropdown. */
74
77
  Object.assign(elements.floating.style, {
75
- width: `${rects.reference.width}px`,
78
+ minWidth: `${rects.reference.width}px`,
79
+ width: '',
76
80
  })
77
81
  },
78
82
  })
@@ -61,12 +61,15 @@ export function StatusChip({ value, options = [], onChange, size = 'sm' }) {
61
61
  >
62
62
  {activeOpt?.label ?? value}
63
63
  {interactive && (
64
- <Icon
65
- name="chevron-down"
66
- size={INDICATOR[size] ?? INDICATOR.sm}
67
- className={`transition-transform duration-150${open ? ' rotate-180' : ''}`}
64
+ /* plain inline-flex wrapper — Icon's own element can render
65
+ * inline-block and ride the baseline (the misaligned caret,
66
+ * 2026-08-09); the flex span centres it against the label. */
67
+ <span
68
+ className={`inline-flex shrink-0 transition-transform duration-150${open ? ' rotate-180' : ''}`}
68
69
  aria-hidden="true"
69
- />
70
+ >
71
+ <Icon name="chevron-down" size={INDICATOR[size] ?? INDICATOR.sm} />
72
+ </span>
70
73
  )}
71
74
  </Tag>
72
75
  )
@@ -168,7 +171,9 @@ export default function FieldRow({
168
171
  ) : empty
169
172
  } else if (type === 'file') {
170
173
  control = value ? (
171
- <Tag variant="secondary" size="sm" hash={false} onRemove={onChange ? () => onChange(null) : undefined}>
174
+ /* kol-tag--data: a filename renders VERBATIM the chip uppercase is
175
+ * for label strings, and transforming data misquotes it (2026-08-09). */
176
+ <Tag variant="secondary" size="sm" hash={false} className="kol-tag--data" onRemove={onChange ? () => onChange(null) : undefined}>
172
177
  {value.name ?? value}
173
178
  </Tag>
174
179
  ) : empty
@@ -7,9 +7,31 @@ import ToggleCheckbox from '../atoms/ToggleCheckbox'
7
7
  import ShellDrawer from '../molecules/ShellDrawer'
8
8
  import Dropdown from '../molecules/Dropdown'
9
9
  import FieldRow, { StatusChip } from '../molecules/FieldRow'
10
+ import { Tooltip } from '../atoms/Popover'
10
11
  import Table from './Table'
11
12
  import MediaLibrary from './MediaLibrary'
12
13
 
14
+ /* TOOLBAR ICONS ARE BARE GLYPHS (user ruling 2026-08-09, verbatim: "did I
15
+ * ask for a button?? … the only thing that should hover is full opacity and
16
+ * popover"): no Button chrome, no fill, no box — body ink at rest, emphasis
17
+ * on hover, a Tooltip as the name. */
18
+ const ToolbarIcon = ({ name, label, onClick, active = false, triggerClassName }) => (
19
+ <Tooltip label={label} placement="bottom" triggerClassName={triggerClassName}>
20
+ <button
21
+ type="button"
22
+ aria-label={label}
23
+ aria-pressed={active}
24
+ onClick={onClick}
25
+ /* PRESSED gets the box (user ruling, reference frame 2026-08-09): a
26
+ * snug rounded fill hugging the glyph — "container relative to the
27
+ * icon, and only when pressed". Rest is bare; hover is ink only. */
28
+ className={`inline-flex cursor-pointer rounded-sm border-0 p-1 transition-colors duration-150 ${active ? 'bg-fg-08 text-emphasis' : 'bg-transparent text-body hover:text-emphasis'}`}
29
+ >
30
+ <Icon name={name} size={16} />
31
+ </button>
32
+ </Tooltip>
33
+ )
34
+
13
35
  /**
14
36
  * RecordManager — full-screen CMS record surface (lobby: RecordManager,
15
37
  * reference: Framer CMS). A reorderable record table plus a slide-over detail
@@ -61,9 +83,14 @@ export default function RecordManager({
61
83
  onOverflow,
62
84
  mediaClient,
63
85
  reorderLabel = (n) => `Reorder row ${n}`,
86
+ /* pressed states for the toolbar's sort/filter controls — the consumer owns
87
+ * those panels, so it tells the toolbar which control is open */
88
+ sortActive = false,
89
+ filterActive = false,
64
90
  className = '',
65
91
  }) {
66
92
  const wrapRef = useRef(null)
93
+ const [searchOpen, setSearchOpen] = useState(false)
67
94
  const dragRef = useRef(null)
68
95
  const [drag, setDrag] = useState(null) // { from, over, x, y } during a drag
69
96
  const [active, setActive] = useState(null)
@@ -193,7 +220,7 @@ export default function RecordManager({
193
220
  className="cursor-grab touch-none bg-transparent border-0 p-0 text-meta hover:text-emphasis"
194
221
  onPointerDown={(e) => startDrag(e, i)}
195
222
  >
196
- <Icon name="resize-grip" size={14} />
223
+ <Icon name="drag-handle" size={14} />
197
224
  </button>
198
225
  )
199
226
  },
@@ -233,23 +260,28 @@ export default function RecordManager({
233
260
  return (
234
261
  <div ref={wrapRef} className={`flex flex-col min-w-0 ${className}`}>
235
262
  {hasToolbar && (
236
- <div className="flex items-center gap-1 pb-3">
237
- {onAdd && <Button variant="nav" size="md" iconOnly="plus" iconSize={14} onClick={onAdd} aria-label="Add" />}
238
- {onSortToggle && <Button variant="nav" size="md" iconOnly="swap" iconSize={14} onClick={onSortToggle} aria-label="Sort" />}
239
- {onFilterToggle && <Button variant="nav" size="md" iconOnly="filter" iconSize={14} onClick={onFilterToggle} aria-label="Filter" />}
240
- {onQueryChange && (
263
+ /* the reference cluster (user screenshots 2026-08-09): bare + · ↑↓ ·
264
+ * · 🔍, search expanding into a COMPACT field on press, … far right */
265
+ <div className="flex items-center gap-2 pb-3">
266
+ {onAdd && <ToolbarIcon name="plus" label="Add" onClick={onAdd} />}
267
+ {onSortToggle && <ToolbarIcon name="arrows-vertical" label="Sort" active={sortActive} onClick={onSortToggle} />}
268
+ {onFilterToggle && <ToolbarIcon name="filter-lines" label="Filter" active={filterActive} onClick={onFilterToggle} />}
269
+ {onQueryChange && (searchOpen || query ? (
241
270
  <SearchInput
271
+ autoFocus
272
+ size="sm"
273
+ className="w-56"
242
274
  value={query ?? ''}
243
275
  onChange={(e) => onQueryChange(e?.target ? e.target.value : e)}
244
276
  placeholder={searchPlaceholder}
245
- size="sm"
246
- className="ml-2"
277
+ onClear={() => { onQueryChange(''); setSearchOpen(false) }}
278
+ onBlur={() => { if (!query) setSearchOpen(false) }}
247
279
  />
248
- )}
249
- {/* trailing overflow the reference toolbar's far-right … (surface
250
- * scope: called with null, vs the drawer header's per-record call) */}
280
+ ) : (
281
+ <ToolbarIcon name="search" label="Search" onClick={() => setSearchOpen(true)} />
282
+ ))}
251
283
  {onOverflow && (
252
- <Button variant="nav" size="md" iconOnly="more" iconSize={14} className="ml-auto" onClick={() => onOverflow(null)} aria-label="More" />
284
+ <ToolbarIcon name="more" label="More" triggerClassName="ml-auto" onClick={() => onOverflow(null)} />
253
285
  )}
254
286
  </div>
255
287
  )}
@@ -286,10 +318,10 @@ export default function RecordManager({
286
318
  header={
287
319
  <div className="flex items-center gap-2 min-w-0">
288
320
  {onOverflow && (
289
- <Button variant="nav" size="md" iconOnly="more" iconSize={14} onClick={() => onOverflow(record)} aria-label="More" />
321
+ <ToolbarIcon name="more" label="More" onClick={() => onOverflow(record)} />
290
322
  )}
291
323
  {onPreview && (
292
- <Button variant="nav" size="md" iconOnly="play" iconSize={14} onClick={() => onPreview(record)} aria-label="Preview" />
324
+ <ToolbarIcon name="play" label="Preview" onClick={() => onPreview(record)} />
293
325
  )}
294
326
  {saveState != null && <span className="kol-helper-10 text-meta ml-auto truncate">{saveState}</span>}
295
327
  {onPublish && (