mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
refactor(studio): split ease mode controls out of EaseCurveSection
EaseCurveSection.tsx was 635 lines against CI's 600-line cap, which has been failing the File size check on main for four consecutive runs and blocks release cuts. Moves the two self-contained presentational pieces into a sibling EaseModeControls.tsx, following the pattern the directory already uses (easeCurveSvg, easePresetLibrary, EaseParamFields): the mode radio group (EaseModeToggle) and the preset grid (EasePresetGrid), plus the mode vocabulary they own — EASE_MODES, the EaseMode type, MODE_LABELS, DEFAULT_EASE_BY_MODE, and the DEFAULT_CURVE/Pts pair those defaults are built from. Both components are stateless: they take the current selection and emit a committed ease string, so nothing had to be rewired. Only the symbols the parent still references are exported — EASE_MODES and DEFAULT_EASE_BY_MODE became file-internal, since the components that consume them moved too. No behaviour change. EaseCurveSection is now 556 lines, the new file 110. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
941167dd3c
commit
e0b1909475
@@ -5,7 +5,15 @@ import {
|
|||||||
parseWiggleEase,
|
parseWiggleEase,
|
||||||
type WiggleEaseConfig,
|
type WiggleEaseConfig,
|
||||||
} from "@hyperframes/core/wiggle-ease";
|
} from "@hyperframes/core/wiggle-ease";
|
||||||
import { EASE_PRESETS, easePresetLabel } from "./easePresetLibrary";
|
import { easePresetLabel } from "./easePresetLibrary";
|
||||||
|
import {
|
||||||
|
DEFAULT_CURVE,
|
||||||
|
MODE_LABELS,
|
||||||
|
EaseModeToggle,
|
||||||
|
EasePresetGrid,
|
||||||
|
type EaseMode,
|
||||||
|
type Pts,
|
||||||
|
} from "./EaseModeControls";
|
||||||
import { holdCurvePath, MiniCurveSvg, sampledPath } from "./easeCurveSvg";
|
import { holdCurvePath, MiniCurveSvg, sampledPath } from "./easeCurveSvg";
|
||||||
import { EaseBezierField, SpringBounceField, WiggleField } from "./EaseParamFields";
|
import { EaseBezierField, SpringBounceField, WiggleField } from "./EaseParamFields";
|
||||||
import { EASE_CURVES, EASE_LABELS, resolveEaseCurveTuple } from "./gsapAnimationConstants";
|
import { EASE_CURVES, EASE_LABELS, resolveEaseCurveTuple } from "./gsapAnimationConstants";
|
||||||
@@ -14,51 +22,6 @@ import type { AnimationKeyframeTarget } from "../../hooks/gsapTweenSynth";
|
|||||||
|
|
||||||
export { MiniCurveSvg } from "./easeCurveSvg";
|
export { MiniCurveSvg } from "./easeCurveSvg";
|
||||||
|
|
||||||
const EASE_MODES = ["curve", "spring", "wiggle"] as const;
|
|
||||||
type EaseMode = (typeof EASE_MODES)[number];
|
|
||||||
|
|
||||||
const EasePresetGrid = function EasePresetGrid({
|
|
||||||
kind,
|
|
||||||
currentEase,
|
|
||||||
onSelect,
|
|
||||||
}: {
|
|
||||||
kind: EaseMode;
|
|
||||||
currentEase: string;
|
|
||||||
onSelect: (ease: string) => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="mb-2 grid max-h-56 grid-cols-4 gap-1 overflow-y-auto pr-0.5">
|
|
||||||
{EASE_PRESETS.filter((preset) => preset.kind === kind).map((preset) => {
|
|
||||||
const isActive = currentEase === preset.ease;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={preset.id}
|
|
||||||
type="button"
|
|
||||||
data-ease-preset-id={preset.id}
|
|
||||||
role="menuitemradio"
|
|
||||||
aria-checked={isActive}
|
|
||||||
tabIndex={isActive ? 0 : -1}
|
|
||||||
onClick={() => onSelect(preset.ease)}
|
|
||||||
className={`flex flex-col items-center gap-0.5 rounded-md p-1 transition-colors ${
|
|
||||||
isActive ? "bg-panel-accent/10 ring-1 ring-panel-accent/30" : "hover:bg-neutral-800"
|
|
||||||
}`}
|
|
||||||
title={preset.label}
|
|
||||||
>
|
|
||||||
<MiniCurveSvg ease={preset.ease} active={isActive} />
|
|
||||||
<span
|
|
||||||
className={`text-center text-[8px] leading-none ${
|
|
||||||
isActive ? "text-panel-accent" : "text-neutral-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{preset.label}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const round2 = roundToCenti;
|
const round2 = roundToCenti;
|
||||||
|
|
||||||
// ── Graph geometry (Figma-style easing box) ─────────────────────────────────
|
// ── Graph geometry (Figma-style easing box) ─────────────────────────────────
|
||||||
@@ -81,50 +44,6 @@ const DRAG_VMAX = 2;
|
|||||||
const DRAG_VMIN = -1;
|
const DRAG_VMIN = -1;
|
||||||
const ACCENT = "#3CE6AC";
|
const ACCENT = "#3CE6AC";
|
||||||
|
|
||||||
type Pts = [number, number, number, number];
|
|
||||||
const DEFAULT_CURVE: Pts = EASE_CURVES["power2.out"];
|
|
||||||
const MODE_LABELS = { curve: "Curve", spring: "Spring", wiggle: "Wiggle" } satisfies Record<
|
|
||||||
EaseMode,
|
|
||||||
string
|
|
||||||
>;
|
|
||||||
const DEFAULT_EASE_BY_MODE = {
|
|
||||||
curve: `custom(M0,0 C${DEFAULT_CURVE[0]},${DEFAULT_CURVE[1]} ${DEFAULT_CURVE[2]},${DEFAULT_CURVE[3]} 1,1)`,
|
|
||||||
spring: "spring(0.42)",
|
|
||||||
wiggle: "wiggle(3,easeInOut,0.12)",
|
|
||||||
} satisfies Record<EaseMode, string>;
|
|
||||||
|
|
||||||
function EaseModeToggle({ mode, onCommit }: { mode: EaseMode; onCommit: (ease: string) => void }) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="mb-2 grid grid-cols-3 rounded-md bg-black/20 p-0.5"
|
|
||||||
role="radiogroup"
|
|
||||||
aria-label="Ease editor mode"
|
|
||||||
>
|
|
||||||
{EASE_MODES.map((candidateMode) => {
|
|
||||||
const active = candidateMode === mode;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={candidateMode}
|
|
||||||
type="button"
|
|
||||||
data-ease-mode={candidateMode}
|
|
||||||
role="radio"
|
|
||||||
aria-checked={active}
|
|
||||||
onClick={() => {
|
|
||||||
if (active) return;
|
|
||||||
onCommit(DEFAULT_EASE_BY_MODE[candidateMode]);
|
|
||||||
}}
|
|
||||||
className={`rounded px-2 py-1 text-[10px] font-medium transition-colors ${
|
|
||||||
active ? "bg-neutral-700 text-neutral-100" : "text-neutral-500 hover:text-neutral-300"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{MODE_LABELS[candidateMode]}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figma-style ease-type dropdown: the current ease (glyph + name) as a button
|
// Figma-style ease-type dropdown: the current ease (glyph + name) as a button
|
||||||
// that opens the preset grid in a popover. This is where a preset is selected —
|
// that opens the preset grid in a popover. This is where a preset is selected —
|
||||||
// the grid is no longer shown inline.
|
// the grid is no longer shown inline.
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Ease editor mode primitives: the mode radio group and the preset grid.
|
||||||
|
*
|
||||||
|
* Split out of `EaseCurveSection.tsx` to keep it under the 600-line cap
|
||||||
|
* (CI's file size check). Both components are presentational and stateless —
|
||||||
|
* they take the current selection and emit a committed ease string — so they
|
||||||
|
* carry the mode vocabulary (`EASE_MODES`, labels, per-mode defaults) with
|
||||||
|
* them rather than importing it back from the parent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EASE_PRESETS } from "./easePresetLibrary";
|
||||||
|
import { MiniCurveSvg } from "./easeCurveSvg";
|
||||||
|
import { EASE_CURVES } from "./gsapAnimationConstants";
|
||||||
|
|
||||||
|
const EASE_MODES = ["curve", "spring", "wiggle"] as const;
|
||||||
|
export type EaseMode = (typeof EASE_MODES)[number];
|
||||||
|
|
||||||
|
export type Pts = [number, number, number, number];
|
||||||
|
export const DEFAULT_CURVE: Pts = EASE_CURVES["power2.out"];
|
||||||
|
|
||||||
|
export const MODE_LABELS = { curve: "Curve", spring: "Spring", wiggle: "Wiggle" } satisfies Record<
|
||||||
|
EaseMode,
|
||||||
|
string
|
||||||
|
>;
|
||||||
|
|
||||||
|
const DEFAULT_EASE_BY_MODE = {
|
||||||
|
curve: `custom(M0,0 C${DEFAULT_CURVE[0]},${DEFAULT_CURVE[1]} ${DEFAULT_CURVE[2]},${DEFAULT_CURVE[3]} 1,1)`,
|
||||||
|
spring: "spring(0.42)",
|
||||||
|
wiggle: "wiggle(3,easeInOut,0.12)",
|
||||||
|
} satisfies Record<EaseMode, string>;
|
||||||
|
|
||||||
|
export const EasePresetGrid = function EasePresetGrid({
|
||||||
|
kind,
|
||||||
|
currentEase,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
kind: EaseMode;
|
||||||
|
currentEase: string;
|
||||||
|
onSelect: (ease: string) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="mb-2 grid max-h-56 grid-cols-4 gap-1 overflow-y-auto pr-0.5">
|
||||||
|
{EASE_PRESETS.filter((preset) => preset.kind === kind).map((preset) => {
|
||||||
|
const isActive = currentEase === preset.ease;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={preset.id}
|
||||||
|
type="button"
|
||||||
|
data-ease-preset-id={preset.id}
|
||||||
|
role="menuitemradio"
|
||||||
|
aria-checked={isActive}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
|
onClick={() => onSelect(preset.ease)}
|
||||||
|
className={`flex flex-col items-center gap-0.5 rounded-md p-1 transition-colors ${
|
||||||
|
isActive ? "bg-panel-accent/10 ring-1 ring-panel-accent/30" : "hover:bg-neutral-800"
|
||||||
|
}`}
|
||||||
|
title={preset.label}
|
||||||
|
>
|
||||||
|
<MiniCurveSvg ease={preset.ease} active={isActive} />
|
||||||
|
<span
|
||||||
|
className={`text-center text-[8px] leading-none ${
|
||||||
|
isActive ? "text-panel-accent" : "text-neutral-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{preset.label}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EaseModeToggle({
|
||||||
|
mode,
|
||||||
|
onCommit,
|
||||||
|
}: {
|
||||||
|
mode: EaseMode;
|
||||||
|
onCommit: (ease: string) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="mb-2 grid grid-cols-3 rounded-md bg-black/20 p-0.5"
|
||||||
|
role="radiogroup"
|
||||||
|
aria-label="Ease editor mode"
|
||||||
|
>
|
||||||
|
{EASE_MODES.map((candidateMode) => {
|
||||||
|
const active = candidateMode === mode;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={candidateMode}
|
||||||
|
type="button"
|
||||||
|
data-ease-mode={candidateMode}
|
||||||
|
role="radio"
|
||||||
|
aria-checked={active}
|
||||||
|
onClick={() => {
|
||||||
|
if (active) return;
|
||||||
|
onCommit(DEFAULT_EASE_BY_MODE[candidateMode]);
|
||||||
|
}}
|
||||||
|
className={`rounded px-2 py-1 text-[10px] font-medium transition-colors ${
|
||||||
|
active ? "bg-neutral-700 text-neutral-100" : "text-neutral-500 hover:text-neutral-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{MODE_LABELS[candidateMode]}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user