feat(studio): add centerTick and reset-slot to FlatSlider for bipolar grade rows

This commit is contained in:
Vance Ingalls
2026-07-14 15:50:49 -07:00
parent 22b3f07c56
commit afafeccf4c
2 changed files with 115 additions and 0 deletions
@@ -229,6 +229,96 @@ describe("FlatSlider", () => {
});
});
describe("FlatSlider — Grade extensions", () => {
it("renders a center tick when centerTick is true, and omits it by default", () => {
const { host: withTick, root: rootA } = renderInto(
<FlatSlider
label="Exposure"
value={0}
min={-100}
max={100}
tier="default"
displayValue="+0.00"
centerTick
onCommit={vi.fn()}
/>,
);
expect(withTick.querySelector('[data-flat-slider-center-tick="true"]')).not.toBeNull();
act(() => rootA.unmount());
const { host: withoutTick, root: rootB } = renderInto(
<FlatSlider
label="Layer blur"
value={0}
min={0}
max={100}
tier="default"
displayValue="0px"
onCommit={vi.fn()}
/>,
);
expect(withoutTick.querySelector('[data-flat-slider-center-tick="true"]')).toBeNull();
act(() => rootB.unmount());
});
it("always reserves a 14px reset slot, showing the icon only when set and onReset is provided", () => {
const onReset = vi.fn();
const { host, root } = renderInto(
<FlatSlider
label="Contrast"
value={12}
min={-100}
max={100}
tier="explicitCustom"
displayValue="+12%"
centerTick
onReset={onReset}
onCommit={vi.fn()}
/>,
);
const slot = host.querySelector('[data-flat-slider-reset-slot="true"]');
expect(slot).not.toBeNull();
const resetButton = host.querySelector<HTMLButtonElement>('[data-flat-slider-reset="true"]');
expect(resetButton).not.toBeNull();
act(() => resetButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
expect(onReset).toHaveBeenCalledTimes(1);
act(() => root.unmount());
const { host: unsetHost, root: rootB } = renderInto(
<FlatSlider
label="Contrast"
value={0}
min={-100}
max={100}
tier="default"
displayValue="0%"
centerTick
onCommit={vi.fn()}
/>,
);
expect(unsetHost.querySelector('[data-flat-slider-reset-slot="true"]')).not.toBeNull();
expect(unsetHost.querySelector('[data-flat-slider-reset="true"]')).toBeNull();
act(() => rootB.unmount());
});
it("renders no reset slot at all when centerTick is omitted, matching existing Style/Media callers", () => {
const { host, root } = renderInto(
<FlatSlider
label="Opacity"
value={100}
min={0}
max={100}
tier="explicitCustom"
displayValue="100%"
onCommit={vi.fn()}
/>,
);
expect(host.querySelector('[data-flat-slider-reset-slot="true"]')).toBeNull();
expect(host.querySelector('[data-flat-slider-reset="true"]')).toBeNull();
act(() => root.unmount());
});
});
describe("FlatSelectRow", () => {
it("renders the default tier with no reset button", () => {
const { host, root } = renderInto(
@@ -247,6 +247,8 @@ export function FlatSlider({
tier,
displayValue,
disabled,
centerTick,
onReset,
onCommit,
}: {
label: string;
@@ -257,6 +259,8 @@ export function FlatSlider({
tier: "default" | "explicitCustom";
displayValue: string;
disabled?: boolean;
centerTick?: boolean;
onReset?: () => void;
onCommit: (nextValue: number) => void;
}) {
const clampedPct = Math.max(0, Math.min(100, ((value - min) / Math.max(max - min, 1e-6)) * 100));
@@ -285,6 +289,12 @@ export function FlatSlider({
commitFromClientX(e.clientX, e.currentTarget.getBoundingClientRect());
}}
>
{centerTick && (
<div
data-flat-slider-center-tick="true"
className="absolute left-1/2 top-[-1px] h-1 w-px -translate-x-1/2 bg-panel-text-5"
/>
)}
{tier === "explicitCustom" && (
<div
data-flat-slider-fill="true"
@@ -308,6 +318,21 @@ export function FlatSlider({
>
{displayValue}
</span>
{centerTick && (
<span data-flat-slider-reset-slot="true" className="w-3.5 flex-shrink-0">
{tier === "explicitCustom" && onReset && (
<button
type="button"
data-flat-slider-reset="true"
title="Remove — fall back to default"
onClick={onReset}
className="text-panel-text-3 hover:text-panel-text-1"
>
<RotateCcw size={11} />
</button>
)}
</span>
)}
</div>
);
}