@kolkrabbi/kol-component 0.31.0 → 0.32.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.
|
|
3
|
+
"version": "0.32.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",
|
|
@@ -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.12.
|
|
27
|
+
"@kolkrabbi/kol-icons": "0.12.1"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"framer-motion": "^12.0.0",
|
package/src/atoms/Popover.jsx
CHANGED
|
@@ -74,9 +74,15 @@ export function usePopover({
|
|
|
74
74
|
* narrow case). The panel is never NARROWER than the trigger, and
|
|
75
75
|
* grows when its rows carry chrome the trigger doesn't (the
|
|
76
76
|
* selected row's check). Sole consumer is Dropdown. */
|
|
77
|
+
/* EXACT width — the one-piece fusion restored (2026-08-09 night).
|
|
78
|
+
* The truncation that briefly justified a floor is solved at the
|
|
79
|
+
* TRIGGER instead: the ghost stack reserves the panel row's check
|
|
80
|
+
* column (.kol-dd-ghost slack), so the one shared width fits every
|
|
81
|
+
* row. Letting the panel size itself was the broken middle: an
|
|
82
|
+
* abs-positioned float with auto width sizes against available
|
|
83
|
+
* space, not content. Sole consumer is Dropdown. */
|
|
77
84
|
Object.assign(elements.floating.style, {
|
|
78
|
-
|
|
79
|
-
width: '',
|
|
85
|
+
width: `${rects.reference.width}px`,
|
|
80
86
|
})
|
|
81
87
|
},
|
|
82
88
|
})
|
|
@@ -50,28 +50,29 @@ export function StatusChip({ value, options = [], onChange, size = 'sm' }) {
|
|
|
50
50
|
const norm = options.map((opt) => (typeof opt === 'object' ? opt : { value: opt, label: opt }))
|
|
51
51
|
const activeOpt = norm.find((o) => o.value === value)
|
|
52
52
|
const toneCls = activeOpt?.tone ? ` kol-status-chip--${activeOpt.tone}` : ''
|
|
53
|
+
/* BUILT ON THE COVER-FOCUS SPECS (user ruling 2026-08-09: "same component
|
|
54
|
+
* as cover focus"): the chip wears the Dropdown-sm trigger metrics — the
|
|
55
|
+
* kol-btn-sm box (py-1 px-3) and mono-12 type — as a pill with the tone
|
|
56
|
+
* tint. The caret is a direct child of the flex row, centred by
|
|
57
|
+
* items-center; it cannot sit off the label's middle. */
|
|
53
58
|
const chip = (interactive) => (
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
className={`kol-status-chip${toneCls}`}
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
disabled={!interactive || undefined}
|
|
62
|
+
aria-expanded={interactive ? open : undefined}
|
|
63
|
+
className={`kol-status-chip${toneCls} kol-mono-12 inline-flex items-center gap-1 rounded-full border-0 px-2 py-1 leading-none${interactive ? ' cursor-pointer' : ''}`}
|
|
60
64
|
onClick={interactive ? () => setOpen((o) => !o) : undefined}
|
|
61
65
|
>
|
|
62
66
|
{activeOpt?.label ?? value}
|
|
63
67
|
{interactive && (
|
|
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
68
|
<span
|
|
68
69
|
className={`inline-flex shrink-0 transition-transform duration-150${open ? ' rotate-180' : ''}`}
|
|
69
70
|
aria-hidden="true"
|
|
70
71
|
>
|
|
71
|
-
<Icon name="chevron-down" size={INDICATOR
|
|
72
|
+
<Icon name="chevron-down" size={INDICATOR.sm} />
|
|
72
73
|
</span>
|
|
73
74
|
)}
|
|
74
|
-
</
|
|
75
|
+
</button>
|
|
75
76
|
)
|
|
76
77
|
return (
|
|
77
78
|
<>
|
|
@@ -113,9 +114,9 @@ export function StatusChip({ value, options = [], onChange, size = 'sm' }) {
|
|
|
113
114
|
)
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
/* Media
|
|
117
|
-
*
|
|
118
|
-
const THUMB_CLS = 'h-
|
|
117
|
+
/* Media thumbnail — the reference panel's proportions (frame 39,
|
|
118
|
+
* 2026-08-09): a generous rounded tile, remove × inset at its corner. */
|
|
119
|
+
const THUMB_CLS = 'h-16 w-28 rounded-lg object-cover bg-fg-04'
|
|
119
120
|
|
|
120
121
|
export default function FieldRow({
|
|
121
122
|
label,
|
|
@@ -128,32 +129,33 @@ export default function FieldRow({
|
|
|
128
129
|
placeholder,
|
|
129
130
|
disabled = false,
|
|
130
131
|
}) {
|
|
131
|
-
const dash = <span className="kol-helper-10 text-meta">—</span>
|
|
132
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
|
-
*
|
|
135
|
-
|
|
133
|
+
* the reference panel's grammar (user frames 2026-08-09), not a bare icon
|
|
134
|
+
* button and never a dash: the affordance renders with or without a
|
|
135
|
+
* picker (disabled when no onPick), because the reference shows the slot
|
|
136
|
+
* itself as the field's resting state. */
|
|
137
|
+
const mediaEmpty = (
|
|
136
138
|
<button
|
|
137
139
|
type="button"
|
|
138
140
|
aria-label={label}
|
|
139
|
-
disabled={disabled}
|
|
141
|
+
disabled={disabled || !onPick}
|
|
140
142
|
onClick={onPick}
|
|
141
|
-
className="flex h-
|
|
143
|
+
className="flex h-16 w-28 items-center justify-center rounded-lg border border-fg-16 bg-fg-02 text-body transition-colors duration-150 enabled:cursor-pointer enabled:hover:text-emphasis disabled:opacity-60"
|
|
142
144
|
>
|
|
143
|
-
<Icon name="plus" size={
|
|
145
|
+
<Icon name="plus" size={16} />
|
|
144
146
|
</button>
|
|
145
|
-
)
|
|
146
|
-
const fileEmpty =
|
|
147
|
+
)
|
|
148
|
+
const fileEmpty = (
|
|
147
149
|
<button
|
|
148
150
|
type="button"
|
|
149
151
|
aria-label={label}
|
|
150
|
-
disabled={disabled}
|
|
152
|
+
disabled={disabled || !onPick}
|
|
151
153
|
onClick={onPick}
|
|
152
|
-
className="kol-control kol-control--filled kol-control-sm kol-mono-12 w-full
|
|
154
|
+
className="kol-control kol-control--filled kol-control-sm kol-mono-12 w-full text-left text-meta enabled:cursor-pointer disabled:opacity-60"
|
|
153
155
|
>
|
|
154
156
|
Choose file…
|
|
155
157
|
</button>
|
|
156
|
-
)
|
|
158
|
+
)
|
|
157
159
|
|
|
158
160
|
let control = null
|
|
159
161
|
if (type === 'text') {
|
|
@@ -184,7 +186,7 @@ export default function FieldRow({
|
|
|
184
186
|
iconOnly="x"
|
|
185
187
|
iconSize={10}
|
|
186
188
|
aria-label={label}
|
|
187
|
-
className="absolute
|
|
189
|
+
className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
|
|
188
190
|
onClick={() => onChange(null)}
|
|
189
191
|
/>
|
|
190
192
|
)}
|
|
@@ -201,7 +203,7 @@ export default function FieldRow({
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
return (
|
|
204
|
-
<div className="grid grid-cols-[minmax(0,10rem)_minmax(0,1fr)] items-center gap-x-6 py-
|
|
206
|
+
<div className="grid grid-cols-[minmax(0,10rem)_minmax(0,1fr)] items-center gap-x-6 py-4 border-b border-fg-08">
|
|
205
207
|
<div className="kol-mono-12 text-body">{label}</div>
|
|
206
208
|
<div className="min-w-0">{control}</div>
|
|
207
209
|
{hint != null && (
|
|
@@ -37,6 +37,7 @@ export default function ShellDrawer({
|
|
|
37
37
|
side = 'left',
|
|
38
38
|
width,
|
|
39
39
|
header,
|
|
40
|
+
closeSide = 'end',
|
|
40
41
|
children,
|
|
41
42
|
className = '',
|
|
42
43
|
}) {
|
|
@@ -149,6 +150,11 @@ export default function ShellDrawer({
|
|
|
149
150
|
style={width != null ? { width: typeof width === 'number' ? `${width}px` : width } : undefined}
|
|
150
151
|
>
|
|
151
152
|
<div className="mb-6 flex items-center gap-4">
|
|
153
|
+
{/* closeSide="start" — the record-surface anatomy (reference frame,
|
|
154
|
+
* 2026-08-09): × leads, the header's actions sit at the far end. */}
|
|
155
|
+
{closeSide === 'start' && (
|
|
156
|
+
<Button variant="nav" size="md" iconOnly="x" iconSize={14} onClick={onClose} aria-label="Close" className="shrink-0" />
|
|
157
|
+
)}
|
|
152
158
|
{header != null && <div className="min-w-0 flex-1">{header}</div>}
|
|
153
159
|
{/* The box has an owner (2026-08-01). This hand-wrote the icon-button
|
|
154
160
|
* square and its hover wash; `Button variant="nav"` IS that string.
|
|
@@ -156,15 +162,17 @@ export default function ShellDrawer({
|
|
|
156
162
|
* heavier than a drawer close wants, and Button documents iconSize
|
|
157
163
|
* for exactly the cases the ladder cannot serve. The SQUARE is what
|
|
158
164
|
* needed an owner, and it now has one. */}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
{closeSide !== 'start' && (
|
|
166
|
+
<Button
|
|
167
|
+
variant="nav"
|
|
168
|
+
size="md"
|
|
169
|
+
iconOnly="x"
|
|
170
|
+
iconSize={14}
|
|
171
|
+
onClick={onClose}
|
|
172
|
+
aria-label="Close"
|
|
173
|
+
className="ml-auto shrink-0"
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
168
176
|
</div>
|
|
169
177
|
<div className="flex-1 overflow-y-auto pr-1" style={{ overflowAnchor: 'none' }}>
|
|
170
178
|
{children}
|
|
@@ -25,9 +25,11 @@ const ToolbarIcon = ({ name, label, onClick, active = false, triggerClassName })
|
|
|
25
25
|
/* PRESSED gets the box (user ruling, reference frame 2026-08-09): a
|
|
26
26
|
* snug rounded fill hugging the glyph — "container relative to the
|
|
27
27
|
* icon, and only when pressed". Rest is bare; hover is ink only. */
|
|
28
|
-
|
|
28
|
+
/* 20px glyph, half-step pad — "TOO SMALL, TOO MUCH PADDING" (user
|
|
29
|
+
* frames 2026-08-09): the reference icons nearly fill their box. */
|
|
30
|
+
className={`inline-flex cursor-pointer rounded-sm border-0 p-0.5 transition-colors duration-150 ${active ? 'bg-fg-08 text-emphasis' : 'bg-transparent text-body hover:text-emphasis'}`}
|
|
29
31
|
>
|
|
30
|
-
<Icon name={name} size={
|
|
32
|
+
<Icon name={name} size={20} />
|
|
31
33
|
</button>
|
|
32
34
|
</Tooltip>
|
|
33
35
|
)
|
|
@@ -155,10 +157,10 @@ export default function RecordManager({
|
|
|
155
157
|
<button
|
|
156
158
|
type="button"
|
|
157
159
|
aria-label="Toggle overlay"
|
|
158
|
-
className="shrink-0 inline-flex cursor-pointer rounded-
|
|
160
|
+
className="shrink-0 inline-flex cursor-pointer rounded-md 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
161
|
onClick={() => openRecord(row)}
|
|
160
162
|
>
|
|
161
|
-
<Icon name="toggle-overlay" size={
|
|
163
|
+
<Icon name="toggle-overlay" size={18} />
|
|
162
164
|
</button>
|
|
163
165
|
</span>
|
|
164
166
|
),
|
|
@@ -289,6 +291,7 @@ export default function RecordManager({
|
|
|
289
291
|
columns={builtColumns}
|
|
290
292
|
rows={rows}
|
|
291
293
|
width="column"
|
|
294
|
+
compact
|
|
292
295
|
rowClassName={(row, i) =>
|
|
293
296
|
`group${drag && i === drag.from ? ' opacity-40' : ''}${
|
|
294
297
|
drag && i === drag.over && drag.over !== drag.from ? ' bg-fg-04' : ''
|
|
@@ -314,17 +317,21 @@ export default function RecordManager({
|
|
|
314
317
|
onClose={() => setActive(null)}
|
|
315
318
|
side="right"
|
|
316
319
|
width="min(37.5rem, 92vw)"
|
|
320
|
+
closeSide="start"
|
|
317
321
|
header={
|
|
318
|
-
|
|
322
|
+
/* reference header anatomy (frame 45, 2026-08-09): × leads the row
|
|
323
|
+
* (closeSide above); every action clusters at the far end —
|
|
324
|
+
* … ▶ save-state Publish. */
|
|
325
|
+
<div className="flex items-center justify-end gap-2 min-w-0">
|
|
319
326
|
{onOverflow && (
|
|
320
327
|
<ToolbarIcon name="more" label="More" onClick={() => onOverflow(record)} />
|
|
321
328
|
)}
|
|
322
329
|
{onPreview && (
|
|
323
330
|
<ToolbarIcon name="play" label="Preview" onClick={() => onPreview(record)} />
|
|
324
331
|
)}
|
|
325
|
-
{saveState != null && <span className="kol-helper-10 text-meta
|
|
332
|
+
{saveState != null && <span className="kol-helper-10 text-meta truncate">{saveState}</span>}
|
|
326
333
|
{onPublish && (
|
|
327
|
-
<Button variant="primary" size="sm"
|
|
334
|
+
<Button variant="primary" size="sm" onClick={() => onPublish(record)}>
|
|
328
335
|
{publishLabel}
|
|
329
336
|
</Button>
|
|
330
337
|
)}
|
package/src/organisms/Table.jsx
CHANGED
|
@@ -33,7 +33,7 @@ const WIDTHS = {
|
|
|
33
33
|
column: '',
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const Table = ({ caption, columns, rows, variant = 'default', className = '', width = 'panel', rowClassName }) => {
|
|
36
|
+
const Table = ({ caption, columns, rows, variant = 'default', className = '', width = 'panel', rowClassName, compact = false }) => {
|
|
37
37
|
const [sort, setSort] = useState({ key: null, dir: null })
|
|
38
38
|
|
|
39
39
|
const cycle = (key) =>
|
|
@@ -56,7 +56,9 @@ const Table = ({ caption, columns, rows, variant = 'default', className = '', wi
|
|
|
56
56
|
}, [rows, columns, sort])
|
|
57
57
|
|
|
58
58
|
const variantClass = variant === 'simple' ? 'kol-table--simple' : ''
|
|
59
|
-
|
|
59
|
+
/* compact — record-surface density (RecordManager, 2026-08-09): the default
|
|
60
|
+
* 12/16 cell box read as bloated rows against the reference table. */
|
|
61
|
+
const wrapperClass = ['kol-table-wrapper', variantClass, compact && 'kol-table--compact', WIDTHS[width] ?? WIDTHS.panel, className].filter(Boolean).join(' ')
|
|
60
62
|
return (
|
|
61
63
|
<div className={wrapperClass}>
|
|
62
64
|
<table className="kol-table">
|