@kolkrabbi/kol-component 0.27.0 → 0.28.1

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.27.0",
3
+ "version": "0.28.1",
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,8 +1,10 @@
1
1
  import { useState } from 'react'
2
+ import { Icon } from '@kolkrabbi/kol-icons'
2
3
  import Input from '../atoms/Input'
3
4
  import Tag from '../atoms/Tag'
4
5
  import Button from '../atoms/Button'
5
6
  import { usePopover, PopoverPanel } from '../atoms/Popover'
7
+ import { INDICATOR } from '../hooks/glyphLadders.js'
6
8
  import Dropdown from './Dropdown'
7
9
 
8
10
  /**
@@ -23,11 +25,19 @@ import Dropdown from './Dropdown'
23
25
  * object back through onChange untouched.
24
26
  */
25
27
 
26
- /* StatusChip — the interactive status control ("Live ▾" in the reference).
28
+ /* StatusChip — the interactive status control ("Live ▾" in the reference,
29
+ * rebuilt to it 2026-08-09 after the user's screenshots: the chip carries a
30
+ * TONE tint + a trailing caret at the INDICATOR rung, and the open panel is
31
+ * the reference's grammar — current chip on top, hairline, then option rows
32
+ * with a leading check column).
27
33
  * Chip taxonomy (Tag source, 2026-07-30): interactive → Tag, never Pill. The
28
34
  * menu is usePopover/PopoverPanel — the same primitive Dropdown builds on —
29
35
  * with click handled by the Tag itself (click: false here) so the chip stays
30
- * the only trigger. Shared by FieldRow and RecordManager's status column. */
36
+ * the only trigger. Shared by FieldRow and RecordManager's status column.
37
+ *
38
+ * Options: strings, or { value, label, tone } — tone ∈ success|warning|error|
39
+ * info maps onto the --ui-* ladder (.kol-status-chip--* in kol-theme); the
40
+ * chip wears the ACTIVE option's tone. No tone → the plain Tag look. */
31
41
  export function StatusChip({ value, options = [], onChange, size = 'sm' }) {
32
42
  const [open, setOpen] = useState(false)
33
43
  const popover = usePopover({
@@ -37,30 +47,58 @@ export function StatusChip({ value, options = [], onChange, size = 'sm' }) {
37
47
  click: false,
38
48
  role: 'listbox',
39
49
  })
50
+ const norm = options.map((opt) => (typeof opt === 'object' ? opt : { value: opt, label: opt }))
51
+ const activeOpt = norm.find((o) => o.value === value)
52
+ const toneCls = activeOpt?.tone ? ` kol-status-chip--${activeOpt.tone}` : ''
53
+ const chip = (interactive) => (
54
+ <Tag
55
+ variant="primary"
56
+ size={size}
57
+ hash={false}
58
+ active={interactive && open}
59
+ className={`kol-status-chip${toneCls}`}
60
+ onClick={interactive ? () => setOpen((o) => !o) : undefined}
61
+ >
62
+ {activeOpt?.label ?? value}
63
+ {interactive && (
64
+ <Icon
65
+ name="chevron-down"
66
+ size={INDICATOR[size] ?? INDICATOR.sm}
67
+ className={`transition-transform duration-150${open ? ' rotate-180' : ''}`}
68
+ aria-hidden="true"
69
+ />
70
+ )}
71
+ </Tag>
72
+ )
40
73
  return (
41
74
  <>
42
75
  <span ref={popover.refs.setReference} {...popover.getReferenceProps()} className="inline-flex">
43
- <Tag variant="primary" size={size} hash={false} active={open} onClick={() => setOpen((o) => !o)}>
44
- {value}
45
- </Tag>
76
+ {chip(true)}
46
77
  </span>
47
78
  <PopoverPanel popover={popover}>
79
+ {/* the reference panel: current value as its chip, a hairline, then
80
+ * the option rows — check column always reserved so labels align */}
81
+ <div className="px-2 pt-2">{chip(false)}</div>
82
+ <div className="mt-2 border-t border-fg-08" />
48
83
  <ul className="flex flex-col p-1 m-0 list-none">
49
- {options.map((opt) => {
50
- const label = typeof opt === 'object' ? opt.label : opt
51
- const val = typeof opt === 'object' ? opt.value : opt
84
+ {norm.map(({ value: val, label }) => {
52
85
  return (
53
86
  <li key={val}>
54
87
  <button
55
88
  type="button"
56
89
  role="option"
57
90
  aria-selected={val === value}
58
- className={`kol-helper-12 w-full text-left px-3 py-2 bg-transparent border-0 cursor-pointer rounded-sm hover:bg-fg-08 ${val === value ? 'text-emphasis' : 'text-body'}`}
91
+ className={`kol-helper-12 w-full text-left pr-3 py-2 bg-transparent border-0 cursor-pointer rounded-sm hover:bg-fg-08 inline-flex items-center ${val === value ? 'text-emphasis' : 'text-body'}`}
59
92
  onClick={() => {
60
93
  onChange?.(val)
61
94
  setOpen(false)
62
95
  }}
63
96
  >
97
+ {/* check column always reserved — labels align whether or
98
+ * not a row carries the mark (reference grammar) */}
99
+ <span className="inline-flex w-7 shrink-0 justify-center" aria-hidden="true">
100
+ {val === value && <Icon name="check" size={12} />}
101
+ </span>
64
102
  {label}
65
103
  </button>
66
104
  </li>
@@ -96,7 +134,10 @@ export default function FieldRow({
96
134
  let control = null
97
135
  if (type === 'text') {
98
136
  control = (
137
+ /* sm — record rows are compact chrome (the reference's density); md
138
+ * read as a form field ("input is large? why?", 2026-08-09). */
99
139
  <Input
140
+ size="sm"
100
141
  value={value ?? ''}
101
142
  onChange={(e) => onChange?.(e?.target ? e.target.value : e)}
102
143
  placeholder={placeholder}
@@ -184,10 +184,13 @@ export default function RecordManager({
184
184
  render: (row) => {
185
185
  const i = rows.indexOf(row)
186
186
  return (
187
+ /* REST-VISIBLE (2026-08-09 — "Im looking for this handle"): the
188
+ * reference draws the grip dim at rest; hover-only hid the whole
189
+ * reorder affordance. */
187
190
  <button
188
191
  type="button"
189
192
  aria-label={reorderLabel(i + 1)}
190
- className="cursor-grab touch-none bg-transparent border-0 p-0 text-meta hover:text-emphasis opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
193
+ className="cursor-grab touch-none bg-transparent border-0 p-0 text-meta hover:text-emphasis"
191
194
  onPointerDown={(e) => startDrag(e, i)}
192
195
  >
193
196
  <Icon name="resize-grip" size={14} />
@@ -243,6 +246,11 @@ export default function RecordManager({
243
246
  className="ml-2"
244
247
  />
245
248
  )}
249
+ {/* trailing overflow — the reference toolbar's far-right … (surface
250
+ * scope: called with null, vs the drawer header's per-record call) */}
251
+ {onOverflow && (
252
+ <Button variant="nav" size="md" iconOnly="more" iconSize={14} className="ml-auto" onClick={() => onOverflow(null)} aria-label="More" />
253
+ )}
246
254
  </div>
247
255
  )}
248
256