mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +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,
|
||||
type WiggleEaseConfig,
|
||||
} 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 { EaseBezierField, SpringBounceField, WiggleField } from "./EaseParamFields";
|
||||
import { EASE_CURVES, EASE_LABELS, resolveEaseCurveTuple } from "./gsapAnimationConstants";
|
||||
@@ -14,51 +22,6 @@ import type { AnimationKeyframeTarget } from "../../hooks/gsapTweenSynth";
|
||||
|
||||
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;
|
||||
|
||||
// ── Graph geometry (Figma-style easing box) ─────────────────────────────────
|
||||
@@ -81,50 +44,6 @@ const DRAG_VMAX = 2;
|
||||
const DRAG_VMIN = -1;
|
||||
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
|
||||
// that opens the preset grid in a popover. This is where a preset is selected —
|
||||
// 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