@kolkrabbi/kol-component 0.30.0 → 0.31.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.30.0",
3
+ "version": "0.31.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.11.1"
27
+ "@kolkrabbi/kol-icons": "0.12.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "framer-motion": "^12.0.0",
@@ -128,11 +128,32 @@ export default function FieldRow({
128
128
  placeholder,
129
129
  disabled = false,
130
130
  }) {
131
- const empty = onPick ? (
132
- <Button variant="nav" size="sm" iconOnly="plus" iconSize={12} onClick={onPick} aria-label={label} disabled={disabled} />
133
- ) : (
134
- <span className="kol-helper-10 text-meta">—</span>
135
- )
131
+ const dash = <span className="kol-helper-10 text-meta">—</span>
132
+ /* Empty MEDIA is a framed + slot, empty FILE is a Choose-file control
133
+ * the reference panel's grammar (user frames 2026-08-09), not a bare
134
+ * icon button. */
135
+ const mediaEmpty = onPick ? (
136
+ <button
137
+ type="button"
138
+ aria-label={label}
139
+ disabled={disabled}
140
+ onClick={onPick}
141
+ className="flex h-14 w-20 cursor-pointer items-center justify-center rounded border border-fg-16 bg-fg-02 text-body transition-colors duration-150 hover:text-emphasis"
142
+ >
143
+ <Icon name="plus" size={14} />
144
+ </button>
145
+ ) : dash
146
+ const fileEmpty = onPick ? (
147
+ <button
148
+ type="button"
149
+ aria-label={label}
150
+ disabled={disabled}
151
+ onClick={onPick}
152
+ className="kol-control kol-control--filled kol-control-sm kol-mono-12 w-full cursor-pointer text-left text-meta"
153
+ >
154
+ Choose file…
155
+ </button>
156
+ ) : dash
136
157
 
137
158
  let control = null
138
159
  if (type === 'text') {
@@ -168,7 +189,7 @@ export default function FieldRow({
168
189
  />
169
190
  )}
170
191
  </span>
171
- ) : empty
192
+ ) : mediaEmpty
172
193
  } else if (type === 'file') {
173
194
  control = value ? (
174
195
  /* kol-tag--data: a filename renders VERBATIM — the chip uppercase is
@@ -176,7 +197,7 @@ export default function FieldRow({
176
197
  <Tag variant="secondary" size="sm" hash={false} className="kol-tag--data" onRemove={onChange ? () => onChange(null) : undefined}>
177
198
  {value.name ?? value}
178
199
  </Tag>
179
- ) : empty
200
+ ) : fileEmpty
180
201
  }
181
202
 
182
203
  return (
@@ -147,17 +147,19 @@ export default function RecordManager({
147
147
  return {
148
148
  ...col,
149
149
  render: (row) => (
150
- <span className="inline-flex items-center gap-2 min-w-0 max-w-full">
150
+ /* the open affordance is the reference's BOXED toggle-overlay
151
+ * button, pinned at the cell's end — not an inline icon riding
152
+ * the text (user frames 2026-08-09). Hover-revealed per row. */
153
+ <span className="flex items-center justify-between gap-2 min-w-0 max-w-full">
151
154
  <span className="truncate text-emphasis">{col.render ? col.render(row) : row[col.accessor]}</span>
152
- <Button
153
- variant="nav"
154
- size="sm"
155
- iconOnly="maximize"
156
- iconSize={12}
157
- aria-label={typeof col.header === 'string' ? col.header : 'Open'}
158
- className="shrink-0 opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
155
+ <button
156
+ type="button"
157
+ aria-label="Toggle overlay"
158
+ className="shrink-0 inline-flex cursor-pointer rounded-sm border border-fg-16 bg-transparent p-1 text-body opacity-0 transition-opacity duration-150 group-hover:opacity-100 focus-visible:opacity-100 hover:text-emphasis"
159
159
  onClick={() => openRecord(row)}
160
- />
160
+ >
161
+ <Icon name="toggle-overlay" size={14} />
162
+ </button>
161
163
  </span>
162
164
  ),
163
165
  }
@@ -202,55 +204,52 @@ export default function RecordManager({
202
204
  return col
203
205
  })
204
206
 
205
- const prefix = []
206
- if (onReorder) {
207
- prefix.push({
208
- accessor: '__handle',
209
- header: '',
210
- className: 'kol-table-cell-text w-8',
211
- render: (row) => {
212
- const i = rows.indexOf(row)
213
- return (
214
- /* REST-VISIBLE (2026-08-09 — "Im looking for this handle"): the
215
- * reference draws the grip dim at rest; hover-only hid the whole
216
- * reorder affordance. */
217
- <button
218
- type="button"
219
- aria-label={reorderLabel(i + 1)}
220
- className="cursor-grab touch-none bg-transparent border-0 p-0 text-meta hover:text-emphasis"
221
- onPointerDown={(e) => startDrag(e, i)}
222
- >
223
- <Icon name="drag-handle" size={14} />
224
- </button>
225
- )
226
- },
227
- })
228
- }
229
- prefix.push({
230
- accessor: '__select',
207
+ /* ONE LEADING FIELD (reference row anatomy, user frames 2026-08-09:
208
+ * "reorder and the toggle is the same field — look at the sizes"): the
209
+ * grip and the row checkbox share a single cell at matched sizes. The
210
+ * grip is rest-visible, dim; the header keeps only the select-all box,
211
+ * with the grip column held as space so the checkboxes align. */
212
+ const prefix = [{
213
+ accessor: '__lead',
231
214
  header: (
232
- <ToggleCheckbox
233
- checked={rows.length > 0 && selected.size === rows.length}
234
- onChange={() =>
235
- updateSelection(selected.size === rows.length ? new Set() : new Set(rows.map(rowId)))
236
- }
237
- />
215
+ <span className="inline-flex items-center gap-2">
216
+ {onReorder && <span className="w-4" aria-hidden="true" />}
217
+ <ToggleCheckbox
218
+ checked={rows.length > 0 && selected.size === rows.length}
219
+ onChange={() =>
220
+ updateSelection(selected.size === rows.length ? new Set() : new Set(rows.map(rowId)))
221
+ }
222
+ />
223
+ </span>
238
224
  ),
239
- className: 'kol-table-cell-text w-8',
225
+ className: 'kol-table-cell-text w-14',
240
226
  render: (row) => {
241
- const id = rowId(row, rows.indexOf(row))
227
+ const i = rows.indexOf(row)
228
+ const id = rowId(row, i)
242
229
  return (
243
- <ToggleCheckbox
244
- checked={selected.has(id)}
245
- onChange={() => {
246
- const next = new Set(selected)
247
- next.has(id) ? next.delete(id) : next.add(id)
248
- updateSelection(next)
249
- }}
250
- />
230
+ <span className="inline-flex items-center gap-2">
231
+ {onReorder && (
232
+ <button
233
+ type="button"
234
+ aria-label={reorderLabel(i + 1)}
235
+ className="inline-flex w-4 cursor-grab touch-none justify-center border-0 bg-transparent p-0 text-meta hover:text-emphasis"
236
+ onPointerDown={(e) => startDrag(e, i)}
237
+ >
238
+ <Icon name="drag-handle" size={16} />
239
+ </button>
240
+ )}
241
+ <ToggleCheckbox
242
+ checked={selected.has(id)}
243
+ onChange={() => {
244
+ const next = new Set(selected)
245
+ next.has(id) ? next.delete(id) : next.add(id)
246
+ updateSelection(next)
247
+ }}
248
+ />
249
+ </span>
251
250
  )
252
251
  },
253
- })
252
+ }]
254
253
  return [...prefix, ...resolved]
255
254
  }, [columns, rows, selected, onReorder, drag]) // eslint-disable-line react-hooks/exhaustive-deps
256
255