@kolkrabbi/kol-component 0.225.0 → 0.227.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 +2 -2
- package/src/atoms/Input.jsx +7 -0
- package/src/atoms/SegmentedToggle.jsx +5 -2
- package/src/atoms/XYPad.jsx +4 -4
- package/src/molecules/FieldRow.jsx +2 -2
- package/src/molecules/InspectorSection.jsx +33 -3
- package/src/molecules/MenuItem.jsx +5 -5
- package/src/molecules/SearchInput.jsx +1 -1
- package/src/molecules/SectionText.jsx +16 -3
- package/src/molecules/SplitToolButton.jsx +1 -1
- package/src/molecules/Stepper.jsx +2 -2
- package/src/molecules/TabsRow.jsx +1 -1
- package/src/organisms/Canvas.jsx +10 -5
- package/src/organisms/KeyframeEditor.jsx +2 -2
- package/src/organisms/MediaLibraryPages.jsx +1 -1
- package/src/organisms/RecordManager.jsx +2 -2
- package/src/organisms/TimelineDock.jsx +1 -1
- package/src/organisms/ToolPalette.jsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.227.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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@kolkrabbi/kol-icons": "^0.
|
|
38
|
+
"@kolkrabbi/kol-icons": "^0.29.0"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
41
|
"src",
|
package/src/atoms/Input.jsx
CHANGED
|
@@ -56,6 +56,9 @@ import { glyphSize } from '../hooks/glyphLadders.js'
|
|
|
56
56
|
* iconLeft/prefix — the paint-bar anatomy ([swatch] FFFFFF is ONE
|
|
57
57
|
* container, not two boxes; ColorSwatchFieldSizing 2026-08-12). The
|
|
58
58
|
* consumer owns the node's sizing; the shell's padding frames it.
|
|
59
|
+
* slotRight — its trailing mirror: an interactive node inside the shell, after the unit /
|
|
60
|
+
* suffix — a combo field's chevron (the design editor's type size, 2026-09-27: the chevron
|
|
61
|
+
* sat OUTSIDE the field as a second control). Not aria-hidden: it is a control.
|
|
59
62
|
*
|
|
60
63
|
* Chrome (bg/border/padding/transition/disabled) comes from .kol-control;
|
|
61
64
|
* Input owns prefix/suffix/icon layout + the inner <input> styling.
|
|
@@ -75,6 +78,7 @@ export default function Input({
|
|
|
75
78
|
prefix,
|
|
76
79
|
suffix,
|
|
77
80
|
slotLeft,
|
|
81
|
+
slotRight,
|
|
78
82
|
affordance,
|
|
79
83
|
unit,
|
|
80
84
|
iconLeft,
|
|
@@ -217,6 +221,9 @@ export default function Input({
|
|
|
217
221
|
{suffix !== undefined && (
|
|
218
222
|
<span aria-hidden="true" className="text-meta pl-1 shrink-0">{suffix}</span>
|
|
219
223
|
)}
|
|
224
|
+
{slotRight && (
|
|
225
|
+
<span className="flex items-center shrink-0 pl-1 ml-auto">{slotRight}</span>
|
|
226
|
+
)}
|
|
220
227
|
</label>
|
|
221
228
|
)
|
|
222
229
|
}
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
* strip (canvas alignment, transform cluster); onChange is the
|
|
30
30
|
* action dispatch.
|
|
31
31
|
* onChange — handler (newValue) => void
|
|
32
|
-
* options — [{ value, label, ariaLabel? }]
|
|
32
|
+
* options — [{ value, label, ariaLabel?, tooltip? }] — a cell whose label is not a string
|
|
33
|
+
* (a glyph) shows its `ariaLabel` as a KOL Tooltip; `tooltip` overrides it
|
|
33
34
|
* variant — 'default' (shipped chrome: shared outer stroke + dividers,
|
|
34
35
|
* filled active cell) | 'filled' (the state law's tiles: every
|
|
35
36
|
* cell a surface-secondary tile with 1px transparent gaps, NO
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
* className — additional classes on the outer shell
|
|
54
55
|
*/
|
|
55
56
|
import { toneClass } from '../utilities/tone.js'
|
|
57
|
+
import { Tooltip } from '../utilities/Popover.jsx'
|
|
56
58
|
|
|
57
59
|
export default function SegmentedToggle({ value, onChange, options = [], variant = 'default', size = 'md', tone = 'default', ariaLabel, className = '' }) {
|
|
58
60
|
const cellType = { xs: 'kol-mono-8', sm: 'kol-mono-12', md: 'kol-mono-14', lg: 'kol-mono-16' }[size]
|
|
@@ -86,8 +88,8 @@ export default function SegmentedToggle({ value, onChange, options = [], variant
|
|
|
86
88
|
{options.map((opt, i) => {
|
|
87
89
|
const isActive = !stateless && opt.value === value
|
|
88
90
|
return (
|
|
91
|
+
<Tooltip key={opt.value} asChild label={opt.tooltip ?? (typeof opt.label === 'string' ? null : opt.ariaLabel)}>
|
|
89
92
|
<button
|
|
90
|
-
key={opt.value}
|
|
91
93
|
type="button"
|
|
92
94
|
role={stateless ? undefined : 'radio'}
|
|
93
95
|
aria-checked={stateless ? undefined : isActive}
|
|
@@ -98,6 +100,7 @@ export default function SegmentedToggle({ value, onChange, options = [], variant
|
|
|
98
100
|
>
|
|
99
101
|
{opt.label}
|
|
100
102
|
</button>
|
|
103
|
+
</Tooltip>
|
|
101
104
|
)
|
|
102
105
|
})}
|
|
103
106
|
</div>
|
package/src/atoms/XYPad.jsx
CHANGED
|
@@ -76,14 +76,14 @@ export default function XYPad({
|
|
|
76
76
|
ref={ref}
|
|
77
77
|
onPointerDown={onPointerDown}
|
|
78
78
|
onPointerMove={onPointerMove}
|
|
79
|
-
className="relative w-full aspect-square border border-
|
|
79
|
+
className="relative w-full aspect-square border border-oq-16 bg-fg-04 rounded cursor-crosshair touch-none"
|
|
80
80
|
>
|
|
81
81
|
{/* crosshair guides */}
|
|
82
|
-
<div className="absolute inset-x-0 top-1/2 border-t border-
|
|
83
|
-
<div className="absolute inset-y-0 left-1/2 border-l border-
|
|
82
|
+
<div className="absolute inset-x-0 top-1/2 border-t border-oq-08" />
|
|
83
|
+
<div className="absolute inset-y-0 left-1/2 border-l border-oq-08" />
|
|
84
84
|
{/* puck */}
|
|
85
85
|
<div
|
|
86
|
-
className="absolute w-3 h-3 -ml-1.5 -mt-1.5 rounded-full bg-fg-96 border border-
|
|
86
|
+
className="absolute w-3 h-3 -ml-1.5 -mt-1.5 rounded-full bg-fg-96 border border-oq-04 pointer-events-none"
|
|
87
87
|
style={{ left: `${puckX}%`, top: `${puckY}%` }}
|
|
88
88
|
/>
|
|
89
89
|
</div>
|
|
@@ -148,7 +148,7 @@ export default function FieldRow({
|
|
|
148
148
|
onClick={onPick}
|
|
149
149
|
/* reference empty tile: FILLED, no border — a plain grey slab with the
|
|
150
150
|
* + centred (frame 2026-08-09) */
|
|
151
|
-
className="flex h-18 w-34 items-center justify-center rounded border-0 bg-fg-04 text-
|
|
151
|
+
className="flex h-18 w-34 items-center justify-center rounded border-0 bg-fg-04 text-oq-64 transition-colors duration-150 enabled:cursor-pointer enabled:hover:text-emphasis disabled:opacity-60"
|
|
152
152
|
>
|
|
153
153
|
<Icon name="plus" size={16} />
|
|
154
154
|
</button>
|
|
@@ -217,7 +217,7 @@ export default function FieldRow({
|
|
|
217
217
|
type="button"
|
|
218
218
|
aria-label={label}
|
|
219
219
|
onClick={() => onChange(null)}
|
|
220
|
-
className="inline-flex shrink-0 cursor-pointer border-0 bg-transparent p-0 text-
|
|
220
|
+
className="inline-flex shrink-0 cursor-pointer border-0 bg-transparent p-0 text-oq-64 hover:text-emphasis"
|
|
221
221
|
>
|
|
222
222
|
<Icon name="x" size={12} />
|
|
223
223
|
</button>
|
|
@@ -17,12 +17,42 @@
|
|
|
17
17
|
* ponytail: a `SectionStack` parent could own this instead of each child
|
|
18
18
|
* declaring it — that is the upgrade path if a consumer ever needs the stack
|
|
19
19
|
* to vary the rule per-gap. One prop is a smaller API than a new component.
|
|
20
|
+
*
|
|
21
|
+
* `pane` (editor inspector rebuild, 2026-09-27 — user: "why are we not sectioning off the panes?
|
|
22
|
+
* saying 'this is type' 'this is transform'"): the Affinity panel shape instead of a labelled form
|
|
23
|
+
* group — a titled header strip, a padded body, and a FULL-WIDTH rule between panes. Mount panes
|
|
24
|
+
* in a container with no horizontal padding; each pane pads itself, so its rule runs edge to edge.
|
|
25
|
+
*
|
|
26
|
+
* @param {ReactNode} label the group's name
|
|
27
|
+
* @param {ReactNode} actions controls on the label's row, right-aligned
|
|
28
|
+
* @param {boolean} divided a hairline between divided siblings
|
|
29
|
+
* @param {boolean} pane the panel shape: header strip + body, full-width rules between panes
|
|
30
|
+
* @param {string} className
|
|
20
31
|
*/
|
|
21
|
-
export default function InspectorSection({ label, children, divided = false, className = '' }) {
|
|
32
|
+
export default function InspectorSection({ label, actions = null, children, divided = false, pane = false, className = '' }) {
|
|
33
|
+
if (pane) {
|
|
34
|
+
return (
|
|
35
|
+
<section className={`kol-inspector-pane ${className}`.trim()}>
|
|
36
|
+
{(label || actions) && (
|
|
37
|
+
<header className="kol-inspector-pane-head">
|
|
38
|
+
{label && <h3 className="kol-inspector-pane-title">{label}</h3>}
|
|
39
|
+
{actions}
|
|
40
|
+
</header>
|
|
41
|
+
)}
|
|
42
|
+
<div className="kol-inspector-pane-body">{children}</div>
|
|
43
|
+
</section>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
22
46
|
return (
|
|
23
47
|
<div className={`flex flex-col gap-2${divided ? ' kol-section--divided' : ''} ${className}`}>
|
|
24
|
-
{label
|
|
25
|
-
|
|
48
|
+
{/* `actions` — controls on the label's row, right-aligned (Figma's eye / plus on Fill and
|
|
49
|
+
* Stroke). From the design editor's own Section, retired into this (editor DS sync,
|
|
50
|
+
* 2026-09-27). */}
|
|
51
|
+
{(label || actions) && (
|
|
52
|
+
<div className="flex items-center gap-2 min-h-6">
|
|
53
|
+
{label && <p className="kol-helper-10 tracking-widest text-meta flex-1">{label}</p>}
|
|
54
|
+
{actions}
|
|
55
|
+
</div>
|
|
26
56
|
)}
|
|
27
57
|
{children}
|
|
28
58
|
</div>
|
|
@@ -51,7 +51,7 @@ export function MenuItem({
|
|
|
51
51
|
ref={popover.refs.setReference}
|
|
52
52
|
{...popover.getReferenceProps()}
|
|
53
53
|
type="button"
|
|
54
|
-
className={`kol-helper-12 px-3 h-8 inline-flex items-center gap-2 rounded text-body hover:text-emphasis transition-colors ${buttonClassName}`}
|
|
54
|
+
className={`kol-menu-btn kol-helper-12 px-3 h-8 inline-flex items-center gap-2 rounded text-body hover:text-emphasis transition-colors ${buttonClassName}`}
|
|
55
55
|
>
|
|
56
56
|
<span>{label}</span>
|
|
57
57
|
{caret && (
|
|
@@ -125,7 +125,7 @@ export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disa
|
|
|
125
125
|
disabled={disabled}
|
|
126
126
|
role="menuitem"
|
|
127
127
|
style={height != null ? { height } : undefined}
|
|
128
|
-
className={`w-full ${ROW_BY_SIZE[size] ?? ROW_BY_SIZE.sm} shrink-0 inline-flex items-center gap-2 text-body ${hover ? 'hover:text-emphasis' : ''} disabled:opacity-40 disabled:cursor-not-allowed text-left`}
|
|
128
|
+
className={`kol-menu-btn w-full ${ROW_BY_SIZE[size] ?? ROW_BY_SIZE.sm} shrink-0 inline-flex items-center gap-2 text-body ${hover ? 'hover:text-emphasis' : ''} disabled:opacity-40 disabled:cursor-not-allowed text-left`}
|
|
129
129
|
>
|
|
130
130
|
{prefix && <span className="shrink-0 inline-flex items-center">{prefix}</span>}
|
|
131
131
|
{iconLeft && <span className="shrink-0 w-4 inline-flex items-center justify-center">{iconLeft}</span>}
|
|
@@ -140,7 +140,7 @@ export function MenuDropdownItem({ onClick, onPointerEnter, onPointerLeave, disa
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export function MenuDropdownDivider() {
|
|
143
|
-
return <div className="border-t border-
|
|
143
|
+
return <div className="border-t border-oq-08 my-1" />
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
@@ -161,7 +161,7 @@ export function MenuDropdownNest({ prefix, iconLeft, label, children }) {
|
|
|
161
161
|
type="button"
|
|
162
162
|
onClick={() => setOpen((v) => !v)}
|
|
163
163
|
aria-expanded={open}
|
|
164
|
-
className="w-full kol-helper-12 px-3 h-8 inline-flex items-center gap-2 text-body hover:text-emphasis text-left"
|
|
164
|
+
className="kol-menu-btn w-full kol-helper-12 px-3 h-8 inline-flex items-center gap-2 text-body hover:text-emphasis text-left"
|
|
165
165
|
>
|
|
166
166
|
{prefix && <span className="shrink-0 inline-flex items-center">{prefix}</span>}
|
|
167
167
|
{iconLeft && <span className="shrink-0 w-4 inline-flex items-center justify-center">{iconLeft}</span>}
|
|
@@ -175,7 +175,7 @@ export function MenuDropdownNest({ prefix, iconLeft, label, children }) {
|
|
|
175
175
|
/>
|
|
176
176
|
</button>
|
|
177
177
|
{open && (
|
|
178
|
-
<div className="ml-3 border-l border-
|
|
178
|
+
<div className="ml-3 border-l border-oq-08">
|
|
179
179
|
{children}
|
|
180
180
|
</div>
|
|
181
181
|
)}
|
|
@@ -245,7 +245,7 @@ export default function SearchInput({
|
|
|
245
245
|
/* preventDefault on mousedown keeps focus in the input across the clear */
|
|
246
246
|
onMouseDown={(e) => e.preventDefault()}
|
|
247
247
|
onClick={onClear}
|
|
248
|
-
className="inline-flex items-center justify-center shrink-0 cursor-pointer text-
|
|
248
|
+
className="inline-flex items-center justify-center shrink-0 cursor-pointer text-oq-48 hover:text-oq-96 transition-colors"
|
|
249
249
|
>
|
|
250
250
|
<Icon name="x" size={12} />
|
|
251
251
|
</button>
|
|
@@ -101,6 +101,8 @@ export default function SectionText({
|
|
|
101
101
|
const cls = (base, slot) => `${base} ${slotClass[slot] ?? (slot === 'eyebrow' ? slotClass.label : undefined) ?? ''}`.trim()
|
|
102
102
|
const alignCls = align === 'center' ? 'items-center text-center' : 'items-start text-left'
|
|
103
103
|
const inlineActions = actionsPlacement === 'inline' && !!actions && !!body
|
|
104
|
+
const inlineHead = actionsPlacement === 'inline' && !!actions && !body && !!headline
|
|
105
|
+
const headlineNode = headline && <Headline className={cls(`kol-section-text-headline ${headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}${headlineCase === 'upper' ? ' kol-section-text-caps' : ''}`, 'headline')} style={slotStyle.headline}>{headline}</Headline>
|
|
104
106
|
const bodyNode = body && (typeof body === 'string'
|
|
105
107
|
? <p className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</p>
|
|
106
108
|
: <div className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</div>)
|
|
@@ -112,7 +114,19 @@ export default function SectionText({
|
|
|
112
114
|
{/* `kol-section-text-eyebrow` = uppercase by ROLE (kol-theme ≥0.55.0);
|
|
113
115
|
* `labelClass` is the voice riding beside it */}
|
|
114
116
|
{eb && <span className={cls(`kol-section-text-eyebrow ${ebClass}`, 'eyebrow')} style={slotStyle.eyebrow ?? slotStyle.label}>{eb}</span>}
|
|
115
|
-
{
|
|
117
|
+
{headlineNode && (inlineHead
|
|
118
|
+
? (
|
|
119
|
+
/* INLINE WITH NO BODY → the cluster shares the HEADLINE's row (the tool frame, 2026-09-27 —
|
|
120
|
+
* a tool's masthead is its title with its controls on the right, apps/media's). This
|
|
121
|
+
* comment used to sit on the fallthrough below, which stacked the cluster under the
|
|
122
|
+
* headline instead: the docstring promised a row the code never drew. It WRAPS: on a
|
|
123
|
+
* phone a wide cluster drops under the title rather than pushing the page sideways. */
|
|
124
|
+
<div className="flex flex-wrap self-stretch items-baseline justify-between gap-x-6 gap-y-3">
|
|
125
|
+
{headlineNode}
|
|
126
|
+
<div className={cls(`${actionsClass} shrink-0`, 'actions')} style={slotStyle.actions}>{actions}</div>
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
: headlineNode)}
|
|
116
130
|
{bodyNode && (inlineActions
|
|
117
131
|
? (
|
|
118
132
|
/* SELF-STRETCH (the Hub review, 2026-09-26 — user, on fxr's /settings: "there should
|
|
@@ -126,8 +140,7 @@ export default function SectionText({
|
|
|
126
140
|
)
|
|
127
141
|
: bodyNode)}
|
|
128
142
|
{children}
|
|
129
|
-
{
|
|
130
|
-
{actions && !inlineActions && <div className={cls(actionsClass, 'actions')} style={slotStyle.actions}>{actions}</div>}
|
|
143
|
+
{actions && !inlineActions && !inlineHead && <div className={cls(actionsClass, 'actions')} style={slotStyle.actions}>{actions}</div>}
|
|
131
144
|
</div>
|
|
132
145
|
)
|
|
133
146
|
}
|
|
@@ -140,7 +140,7 @@ const SplitToolButton = ({
|
|
|
140
140
|
popover={popover}
|
|
141
141
|
panel={false}
|
|
142
142
|
focus={false}
|
|
143
|
-
className="w-max bg-surface-secondary border border-
|
|
143
|
+
className="w-max bg-surface-secondary border border-oq-08 rounded shadow-lg"
|
|
144
144
|
>
|
|
145
145
|
{variants.map((variant) => {
|
|
146
146
|
const isActive = active && variant.id === value
|
|
@@ -123,7 +123,7 @@ export default function Stepper({
|
|
|
123
123
|
<button
|
|
124
124
|
type="button"
|
|
125
125
|
onClick={handleIncrement}
|
|
126
|
-
className="flex items-center justify-center text-
|
|
126
|
+
className="flex items-center justify-center text-oq-48 hover:text-emphasis transition-colors"
|
|
127
127
|
aria-label="Increment"
|
|
128
128
|
>
|
|
129
129
|
<Icon name="chevron-up" size={chevronWidth} />
|
|
@@ -131,7 +131,7 @@ export default function Stepper({
|
|
|
131
131
|
<button
|
|
132
132
|
type="button"
|
|
133
133
|
onClick={handleDecrement}
|
|
134
|
-
className="flex items-center justify-center text-
|
|
134
|
+
className="flex items-center justify-center text-oq-48 hover:text-emphasis transition-colors"
|
|
135
135
|
aria-label="Decrement"
|
|
136
136
|
>
|
|
137
137
|
<Icon name="chevron-down" size={chevronWidth} />
|
|
@@ -71,7 +71,7 @@ export default function TabsRow({ tabs = [], value, onChange, onClose, onMinimis
|
|
|
71
71
|
type="button"
|
|
72
72
|
onClick={onMinimise}
|
|
73
73
|
aria-label="Minimise"
|
|
74
|
-
className="ml-auto text-
|
|
74
|
+
className="ml-auto text-oq-48 hover:text-emphasis self-center"
|
|
75
75
|
style={{ lineHeight: 0 }}
|
|
76
76
|
>
|
|
77
77
|
<Icon name="chevron-down" size={12} />
|
package/src/organisms/Canvas.jsx
CHANGED
|
@@ -87,10 +87,15 @@ export function CanvasFrame({
|
|
|
87
87
|
customRatio,
|
|
88
88
|
aspects,
|
|
89
89
|
bgColor,
|
|
90
|
-
guideColor
|
|
90
|
+
guideColor,
|
|
91
91
|
children,
|
|
92
92
|
}) {
|
|
93
93
|
const { ratio, label } = resolveAspect(aspect, customRatio, aspects)
|
|
94
|
+
/* NO guideColor → THEME INK (editor DS sync 2026-09-27, from the design editor's copy, which had
|
|
95
|
+
* it and this lift had not): the border and label follow the theme, so the frame reads on a light
|
|
96
|
+
* page and a dark one. A guideColor (a type frame's own colour) still tints both. */
|
|
97
|
+
const borderColor = guideColor ? `color-mix(in srgb, ${guideColor} 24%, transparent)` : 'var(--kol-oq-24)'
|
|
98
|
+
const labelColor = guideColor ? `color-mix(in srgb, ${guideColor} 70%, transparent)` : 'var(--kol-fg-64)'
|
|
94
99
|
const virtualH = CANVAS_VIRTUAL_W / ratio
|
|
95
100
|
|
|
96
101
|
const rectRef = useRef(null)
|
|
@@ -127,7 +132,7 @@ export function CanvasFrame({
|
|
|
127
132
|
className="absolute inset-0 pointer-events-none z-[2]"
|
|
128
133
|
style={{
|
|
129
134
|
border: '1px solid',
|
|
130
|
-
borderColor
|
|
135
|
+
borderColor,
|
|
131
136
|
}}
|
|
132
137
|
/>
|
|
133
138
|
<span
|
|
@@ -139,7 +144,7 @@ export function CanvasFrame({
|
|
|
139
144
|
fontSize: 10,
|
|
140
145
|
fontFamily: 'var(--kol-font-family-mono)',
|
|
141
146
|
letterSpacing: '0.1em',
|
|
142
|
-
color:
|
|
147
|
+
color: labelColor,
|
|
143
148
|
pointerEvents: 'none',
|
|
144
149
|
}}
|
|
145
150
|
>
|
|
@@ -197,7 +202,7 @@ export default function Canvas({
|
|
|
197
202
|
customRatio,
|
|
198
203
|
aspects,
|
|
199
204
|
bgColor,
|
|
200
|
-
guideColor
|
|
205
|
+
guideColor,
|
|
201
206
|
align = 'center',
|
|
202
207
|
panEnabled = false,
|
|
203
208
|
backdrop,
|
|
@@ -820,7 +825,7 @@ export function CanvasRuler({ containerRef, virtualWidth = CANVAS_VIRTUAL_W, dis
|
|
|
820
825
|
const tickColor = 'var(--kol-fg-48)'
|
|
821
826
|
const textColor = 'var(--kol-fg-64)'
|
|
822
827
|
const barBg = 'var(--kol-fg-08)'
|
|
823
|
-
const borderColor = 'var(--kol-
|
|
828
|
+
const borderColor = 'var(--kol-oq-16)'
|
|
824
829
|
const labelStyle = { fontFamily: 'var(--kol-font-family-mono)', fontSize: 9 }
|
|
825
830
|
|
|
826
831
|
return (
|
|
@@ -42,7 +42,7 @@ export const DEFAULT_KEYFRAMES = [
|
|
|
42
42
|
*
|
|
43
43
|
* @param {Array<Object>} keyframes - The track; empty falls back to `DEFAULT_KEYFRAMES`
|
|
44
44
|
* @param {Function} onChange - `(keyframes) => void` — the whole track, sorted
|
|
45
|
-
* @param {number} t - The clock's current LAYER-LOCAL phase, 0..1 — what "Add @ playhead" stamps
|
|
45
|
+
* @param {number} t - The clock's current LAYER-LOCAL phase, 0..1 — what "Add @ playhead" stamps; or a function returning it, read at the click — a clock that runs without re-rendering (the design editor's transport) would otherwise stamp the phase of the last render
|
|
46
46
|
* @param {Function} onSeek - `(t) => void` — a key was selected; seek the clock to its local `t`
|
|
47
47
|
* @param {Function} [onPause] - Called before the seek, so the render holds on the pose
|
|
48
48
|
* @param {Array<{value: string, label: string}>} [easeOptions] - The Ease menu (default `KEYFRAME_EASES`)
|
|
@@ -72,7 +72,7 @@ export default function KeyframeEditor({
|
|
|
72
72
|
const onAdd = () => {
|
|
73
73
|
const base = kfs[sel] || { rot: [0, 0, 0], pos: [0, 0, 0], scale: 1, ease: 'inout' }
|
|
74
74
|
const nk = {
|
|
75
|
-
t: Math.max(0, Math.min(1, t)),
|
|
75
|
+
t: Math.max(0, Math.min(1, typeof t === 'function' ? t() : t)),
|
|
76
76
|
rot: [...(base.rot || [0, 0, 0])],
|
|
77
77
|
pos: [...(base.pos || [0, 0, 0])],
|
|
78
78
|
scale: base.scale ?? 1,
|
|
@@ -334,7 +334,7 @@ function FolderRow({ name, icon = 'folder', onClick, onDoubleClick, depth = 0, e
|
|
|
334
334
|
{onToggle ? (
|
|
335
335
|
<button type="button" aria-label={expanded ? `Collapse ${name}` : `Expand ${name}`} aria-expanded={!!expanded}
|
|
336
336
|
onClick={(e) => { e.stopPropagation(); onToggle() }}
|
|
337
|
-
className="w-4 h-4 shrink-0 flex items-center justify-center text-
|
|
337
|
+
className="w-4 h-4 shrink-0 flex items-center justify-center text-oq-32 hover:text-fg-default transition-transform"
|
|
338
338
|
style={{ transform: expanded ? 'rotate(90deg)' : 'none' }}>
|
|
339
339
|
<Icon name="chevron-right" size={12} />
|
|
340
340
|
</button>
|
|
@@ -27,7 +27,7 @@ const ToolbarIcon = ({ name, label, onClick, active = false, triggerClassName })
|
|
|
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
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-
|
|
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-oq-64 hover:text-emphasis'}`}
|
|
31
31
|
>
|
|
32
32
|
<Icon name={name} size={20} />
|
|
33
33
|
</button>
|
|
@@ -255,7 +255,7 @@ export default function RecordManager({
|
|
|
255
255
|
<button
|
|
256
256
|
type="button"
|
|
257
257
|
aria-label={reorderLabel(i + 1)}
|
|
258
|
-
className="inline-flex w-4 cursor-grab touch-none justify-center border-0 bg-transparent p-0 text-
|
|
258
|
+
className="inline-flex w-4 cursor-grab touch-none justify-center border-0 bg-transparent p-0 text-oq-48 hover:text-emphasis"
|
|
259
259
|
onPointerDown={(e) => startDrag(e, i)}
|
|
260
260
|
>
|
|
261
261
|
<Icon name="drag-handle" size={16} />
|
|
@@ -250,7 +250,7 @@ export default function TimelineDock({ tracks = [], t = 0, onSeek, onChange, eas
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
return (
|
|
253
|
-
<div className={`kol-timeline-dock border-t border-
|
|
253
|
+
<div className={`kol-timeline-dock border-t border-oq-08 px-4 py-2 flex flex-col gap-1 select-none ${className}`.trim()} style={{ background: 'var(--kol-surface-primary)' }}>
|
|
254
254
|
<ScrubRuler t={t} onSeek={onSeek} />
|
|
255
255
|
{tracks.map((track) => (
|
|
256
256
|
<TrackRow key={track.id} track={track} t={t} selected={selected} setSelected={setSelected} writeKeys={writeKeys} />
|
|
@@ -43,7 +43,7 @@ const ToolPalette = ({
|
|
|
43
43
|
iconComponent = Icon,
|
|
44
44
|
className = '',
|
|
45
45
|
}) => (
|
|
46
|
-
<div className={`flex items-center gap-1 min-w-0 overflow-x-auto ${className}`.trim()}>
|
|
46
|
+
<div className={`kol-tool-palette flex items-center gap-1 min-w-0 overflow-x-auto ${className}`.trim()}>
|
|
47
47
|
{items.map((it, i) => {
|
|
48
48
|
if (it.kind === 'divider') return <Divider key={`d${i}`} variant="vertical" height={20} className="mx-1.5 shrink-0" />
|
|
49
49
|
if (it.kind === 'split') {
|