mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): add FlatColorGradingAccessory (compare, status dot, reset)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { FlatColorGradingAccessory } from "./propertyPanelFlatColorGradingSection";
|
||||
import { normalizeHfColorGrading } from "@hyperframes/core/color-grading";
|
||||
|
||||
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
function renderInto(node: React.ReactElement) {
|
||||
const host = document.createElement("div");
|
||||
document.body.append(host);
|
||||
const root = createRoot(host);
|
||||
act(() => {
|
||||
root.render(node);
|
||||
});
|
||||
return { host, root };
|
||||
}
|
||||
|
||||
function neutralGrading() {
|
||||
const grading = normalizeHfColorGrading("neutral");
|
||||
if (!grading) throw new Error("expected a neutral grading");
|
||||
return grading;
|
||||
}
|
||||
|
||||
describe("FlatColorGradingAccessory", () => {
|
||||
it("shows a 5px status dot colored by runtime status, with the message as its title", () => {
|
||||
const { host, root } = renderInto(
|
||||
<FlatColorGradingAccessory
|
||||
state={{
|
||||
grading: neutralGrading(),
|
||||
compareEnabled: false,
|
||||
runtimeStatus: { state: "active", message: "Shader active" },
|
||||
commitCompare: vi.fn(),
|
||||
resetGrading: vi.fn(),
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const dot = host.querySelector('[data-flat-grade-status-dot="true"]');
|
||||
expect(dot).not.toBeNull();
|
||||
expect(dot?.getAttribute("title")).toBe("Shader active");
|
||||
expect(dot?.className).toContain("bg-emerald-400");
|
||||
act(() => root.unmount());
|
||||
});
|
||||
|
||||
it("disables the compare hold button when grading is inactive, and fires resetGrading on click", () => {
|
||||
const resetGrading = vi.fn();
|
||||
const { host, root } = renderInto(
|
||||
<FlatColorGradingAccessory
|
||||
state={{
|
||||
grading: neutralGrading(),
|
||||
compareEnabled: false,
|
||||
runtimeStatus: { state: "inactive", message: "No grading applied" },
|
||||
commitCompare: vi.fn(),
|
||||
resetGrading,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const compareButton = host.querySelector<HTMLButtonElement>(
|
||||
'[aria-label="Hold to show original"]',
|
||||
);
|
||||
expect(compareButton?.disabled).toBe(true);
|
||||
const resetButton = host.querySelector<HTMLButtonElement>('[data-flat-grade-reset="true"]');
|
||||
act(() => resetButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
||||
expect(resetGrading).toHaveBeenCalledTimes(1);
|
||||
act(() => root.unmount());
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { isHfColorGradingActive } from "@hyperframes/core/color-grading";
|
||||
import { Compare, RotateCcw } from "../../icons/SystemIcons";
|
||||
import type { ColorGradingControllerState } from "./useColorGradingController";
|
||||
|
||||
const STATUS_DOT_CLASS: Record<ColorGradingControllerState["runtimeStatus"]["state"], string> = {
|
||||
active: "bg-emerald-400",
|
||||
pending: "bg-amber-300",
|
||||
unavailable: "bg-red-400",
|
||||
missing: "bg-panel-text-5",
|
||||
inactive: "bg-panel-text-5",
|
||||
};
|
||||
|
||||
export function FlatColorGradingAccessory({
|
||||
state,
|
||||
}: {
|
||||
state: Pick<
|
||||
ColorGradingControllerState,
|
||||
"grading" | "compareEnabled" | "runtimeStatus" | "commitCompare" | "resetGrading"
|
||||
>;
|
||||
}) {
|
||||
const { grading, compareEnabled, runtimeStatus, commitCompare, resetGrading } = state;
|
||||
const gradingActive = isHfColorGradingActive(grading);
|
||||
|
||||
return (
|
||||
<span className="flex items-center gap-2.5">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={compareEnabled}
|
||||
aria-label="Hold to show original"
|
||||
disabled={!gradingActive}
|
||||
onPointerDown={(e) => {
|
||||
if (!gradingActive) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
commitCompare(true);
|
||||
const release = () => {
|
||||
commitCompare(false);
|
||||
window.removeEventListener("pointerup", release);
|
||||
window.removeEventListener("pointercancel", release);
|
||||
};
|
||||
window.addEventListener("pointerup", release);
|
||||
window.addEventListener("pointercancel", release);
|
||||
}}
|
||||
title="Hold to show original"
|
||||
className="flex-shrink-0 text-panel-text-3 hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
<Compare size={12} />
|
||||
</button>
|
||||
<span
|
||||
data-flat-grade-status-dot="true"
|
||||
title={runtimeStatus.message}
|
||||
className={`h-[5px] w-[5px] flex-shrink-0 rounded-full ${STATUS_DOT_CLASS[runtimeStatus.state]}`}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
data-flat-grade-reset="true"
|
||||
title="Reset color grading"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
resetGrading();
|
||||
}}
|
||||
className="flex-shrink-0 text-panel-text-3 hover:text-panel-text-1"
|
||||
>
|
||||
<RotateCcw size={12} />
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user