Skip to content

Commit 16045e5

Browse files
fix: labels no longer jump (opacity-only transitions, stable variant), unified label editor
1 parent cf051d5 commit 16045e5

5 files changed

Lines changed: 56 additions & 61 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "next-easytour",
3-
"version": "0.3.0-alpha.17",
3+
"version": "0.3.0-alpha.18",
44
"description": "Generalizable interactive tutorial overlay library for React/Next.js. CSS-selector targeting, step actions, waitFor conditions, auto-scroll, highlight effects, animated transitions — all composable and declarative.",
55
"keywords": [
66
"tutorial",

src/editor/EditorPanel.tsx

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -365,39 +365,41 @@ function StepEditor({ step, index, total, editor }: {
365365
{/* ── Text labels ── */}
366366
<Section title="Text labels">
367367
{labels.length > 0 && (
368-
<p className="eto-panel-hint">Drag labels directly on the page to reposition.</p>
368+
<p className="eto-panel-hint">Drag labels on the page to reposition.</p>
369369
)}
370370
{labels.map((lbl, i) => (
371371
<div key={i} className="eto-panel-label-card">
372-
{/* Header: index + text + delete */}
372+
{/* Header row */}
373373
<div className="eto-panel-label-row">
374374
<span className="eto-panel-label-idx">{i + 1}</span>
375375
<input className="eto-panel-input" value={lbl.text}
376376
onChange={(e) => updateLabel(i, { text: e.target.value })}
377377
placeholder="Label text" />
378378
<button type="button" className="eto-panel-action eto-panel-action--danger"
379-
onClick={() => removeLabel(i)} title="Remove label">
380-
<MiniTrash />
381-
</button>
379+
onClick={() => removeLabel(i)} title="Remove"><MiniTrash /></button>
382380
</div>
383381

384-
{/* Style variant */}
385-
<div className="eto-panel-toggles">
386-
{(["callout", "badge", "tag", "code", "plain"] as const).map((v) => (
387-
<Pill key={v} label={v} active={(lbl.variant ?? "callout") === v}
388-
onChange={() => updateLabel(i, { variant: v })} />
389-
))}
382+
{/* Style + size row */}
383+
<div className="eto-panel-label-style-row">
384+
<label className="eto-panel-field-label">Style</label>
385+
<select className="eto-panel-select"
386+
value={lbl.variant ?? "callout"}
387+
onChange={(e) => updateLabel(i, { variant: e.target.value as TextLabel["variant"] })}>
388+
<option value="plain">Plain text</option>
389+
<option value="callout">Callout card</option>
390+
<option value="badge">Badge pill</option>
391+
<option value="tag">Tag</option>
392+
<option value="code">Code</option>
393+
</select>
394+
<Slider label="Size" value={lbl.fontSize ?? 12} min={9} max={24}
395+
onChange={(v) => updateLabel(i, { fontSize: v })} unit="px" />
390396
</div>
391397

392-
{/* Live coordinates + quick presets */}
398+
{/* Position: coordinates + presets */}
393399
<div className="eto-panel-label-position">
394400
<div className="eto-panel-label-coords">
395-
<span className="eto-panel-coord">
396-
X <strong>{Math.round(lbl.position.x)}</strong>%
397-
</span>
398-
<span className="eto-panel-coord">
399-
Y <strong>{Math.round(lbl.position.y)}</strong>%
400-
</span>
401+
<span className="eto-panel-coord">X <strong>{Math.round(lbl.position.x)}</strong>%</span>
402+
<span className="eto-panel-coord">Y <strong>{Math.round(lbl.position.y)}</strong>%</span>
401403
</div>
402404
<div className="eto-panel-label-quick">
403405
{[
@@ -410,16 +412,11 @@ function StepEditor({ step, index, total, editor }: {
410412
<button key={p.tip} type="button"
411413
className={`eto-panel-pos-btn${lbl.position.x === p.x && lbl.position.y === p.y ? " eto-panel-pos-btn--active" : ""}`}
412414
onClick={() => updateLabel(i, { position: targetPoint(p.x, p.y) })}
413-
title={p.tip}>
414-
{p.tip}
415-
</button>
415+
title={p.tip}>{p.tip}</button>
416416
))}
417417
</div>
418418
</div>
419419

420-
<Slider label="Size" value={lbl.fontSize ?? 12} min={9} max={24}
421-
onChange={(v) => updateLabel(i, { fontSize: v })} unit="px" />
422-
423420
{/* Animation */}
424421
<LabelAnimationEditor label={lbl} index={i} updateLabel={updateLabel} />
425422
</div>
@@ -506,7 +503,7 @@ function LabelAnimationEditor({ label, index, updateLabel }: {
506503
updateLabel(index, {
507504
animation: {
508505
frames: [
509-
{ text: label.text, variant: label.variant },
506+
{ text: label.text },
510507
{ text: "Second frame" },
511508
],
512509
frameDuration: 2500,
@@ -549,22 +546,13 @@ function LabelAnimationEditor({ label, index, updateLabel }: {
549546

550547
{hasAnim && anim && (
551548
<div className="eto-panel-anim-body">
552-
{/* Frames */}
549+
{/* Frames — text only, style stays on the label */}
553550
{anim.frames.map((fr, fi) => (
554551
<div key={fi} className="eto-panel-anim-frame">
555552
<span className="eto-panel-anim-frame-num">{fi + 1}</span>
556553
<input className="eto-panel-input" value={fr.text}
557554
onChange={(e) => updateFrame(fi, { text: e.target.value })}
558555
placeholder="Frame text" />
559-
<select className="eto-panel-select"
560-
value={fr.variant ?? label.variant ?? "callout"}
561-
onChange={(e) => updateFrame(fi, { variant: e.target.value as TextLabel["variant"] })}>
562-
<option value="callout">callout</option>
563-
<option value="badge">badge</option>
564-
<option value="tag">tag</option>
565-
<option value="code">code</option>
566-
<option value="plain">plain</option>
567-
</select>
568556
{anim.frames.length > 1 && (
569557
<button type="button" className="eto-panel-action eto-panel-action--danger"
570558
onClick={() => removeFrame(fi)} title="Remove frame">
@@ -577,18 +565,11 @@ function LabelAnimationEditor({ label, index, updateLabel }: {
577565
<Plus /> Frame
578566
</button>
579567

580-
{/* Controls */}
568+
{/* Speed + loop */}
581569
<Slider label="Speed" value={anim.frameDuration ?? 2000} min={500} max={8000} step={250}
582570
onChange={(v) => setAnim({ frameDuration: v })}
583571
unit="ms" formatValue={(v) => `${(v / 1000).toFixed(1)}s`} />
584572

585-
<div className="eto-panel-toggles">
586-
{(["fade", "slide-up", "none"] as const).map((t) => (
587-
<Pill key={t} label={t} active={(anim.transition ?? "fade") === t}
588-
onChange={() => setAnim({ transition: t })} />
589-
))}
590-
</div>
591-
592573
<div className="eto-panel-toggles">
593574
<Pill label="Loop" active={anim.loop ?? false}
594575
onChange={(on) => setAnim({ loop: on })} />

src/overlay/Labels.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,14 @@ function LabelElement({ label, index, stepId, targetRect, isAbsolute, isEditing,
9393
const enterClass = useEnterAnimation(stepId, "eto-label--enter", 300);
9494

9595
// ── Resolve display values ────────────────────────────────────────
96+
// ── Resolve display values ────────────────────────────────────────
97+
// Variant is ALWAYS from the label itself — never from frames.
98+
// Changing variant between frames shifts container size → jumping.
99+
// Frames only override text and color.
96100
const displayText = frame.text ?? label.text;
97-
const displayVariant = frame.variant ?? label.variant ?? "callout";
101+
const displayVariant = label.variant ?? "callout";
98102
const displayColor = frame.color ?? label.color;
99-
const displayFontSize = frame.fontSize ?? label.fontSize ?? 12;
103+
const displayFontSize = label.fontSize ?? 12;
100104

101105
// ── Position ──────────────────────────────────────────────────────
102106
const pos = targetPointToPx(label.position, targetRect);

src/styles.css

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,13 @@
992992
flex-shrink: 0;
993993
}
994994

995+
.eto-panel-label-style-row {
996+
display: flex;
997+
align-items: center;
998+
gap: 6px;
999+
flex-wrap: wrap;
1000+
}
1001+
9951002
.eto-panel-label-position {
9961003
display: flex;
9971004
flex-direction: column;
@@ -1058,38 +1065,41 @@
10581065
color: var(--eto-accent);
10591066
}
10601067

1061-
/* ── Animation engine classes (alpha.17) ──────────────────────────────
1062-
Phase classes applied by useFrameSequence and useEnterAnimation.
1063-
The actual visual transitions are CSS-only. */
1068+
/* ── Animation engine classes (alpha.18) ──────────────────────────────
1069+
Frame transitions are OPACITY ONLY — no transform changes, no jumping.
1070+
Step-enter gets a subtle slide. */
10641071

1065-
/* Enter animation for labels appearing on step change */
1072+
/* Enter animation for labels appearing on step change (one-time slide) */
10661073
.eto-label--enter {
1067-
animation: eto-label-enter 300ms ease-out;
1074+
animation: eto-label-enter 250ms ease-out;
10681075
}
10691076

1070-
/* Frame sequence phases — applied by useFrameSequence */
1077+
/* Frame sequence phases — opacity only, no position shift */
10711078
.eto-anim--hidden {
10721079
opacity: 0;
10731080
pointer-events: none;
10741081
}
10751082
.eto-anim--enter {
1076-
animation: eto-anim-enter 300ms ease-out;
1083+
animation: eto-anim-enter 250ms ease-out;
10771084
}
10781085
.eto-anim--exit {
1079-
animation: eto-anim-exit 300ms ease-in forwards;
1086+
animation: eto-anim-exit 200ms ease-in forwards;
10801087
}
10811088

1089+
/* Step-enter: subtle slide up (first appearance only) */
10821090
@keyframes eto-label-enter {
1083-
from { opacity: 0; transform: translate(-50%, -90%); }
1091+
from { opacity: 0; transform: translate(-50%, calc(-100% + 6px)); }
10841092
to { opacity: 1; transform: translate(-50%, -100%); }
10851093
}
1094+
1095+
/* Frame transitions: OPACITY ONLY — label stays in place */
10861096
@keyframes eto-anim-enter {
1087-
from { opacity: 0; transform: translate(-50%, -90%); }
1088-
to { opacity: 1; transform: translate(-50%, -100%); }
1097+
from { opacity: 0; }
1098+
to { opacity: 1; }
10891099
}
10901100
@keyframes eto-anim-exit {
1091-
from { opacity: 1; transform: translate(-50%, -100%); }
1092-
to { opacity: 0; transform: translate(-50%, -110%); }
1101+
from { opacity: 1; }
1102+
to { opacity: 0; }
10931103
}
10941104

10951105
/* ── Label animation editor controls ── */

0 commit comments

Comments
 (0)