mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): add FlatSelectRow primitive for the flat inspector
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
FlatGroup,
|
FlatGroup,
|
||||||
FlatRow,
|
FlatRow,
|
||||||
FlatSegmentedRow,
|
FlatSegmentedRow,
|
||||||
|
FlatSelectRow,
|
||||||
FlatSlider,
|
FlatSlider,
|
||||||
PinnedZoneDivider,
|
PinnedZoneDivider,
|
||||||
} from "./propertyPanelFlatPrimitives";
|
} from "./propertyPanelFlatPrimitives";
|
||||||
@@ -226,3 +227,62 @@ describe("FlatSlider", () => {
|
|||||||
act(() => root.unmount());
|
act(() => root.unmount());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FlatSelectRow", () => {
|
||||||
|
it("renders the default tier with no reset button", () => {
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatSelectRow
|
||||||
|
label="Blend"
|
||||||
|
value="normal"
|
||||||
|
options={["normal", "multiply", "screen"]}
|
||||||
|
tier="default"
|
||||||
|
onChange={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const select = host.querySelector("select");
|
||||||
|
expect(select?.value).toBe("normal");
|
||||||
|
expect(host.querySelector('[data-flat-select-reset="true"]')).toBeNull();
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the explicitCustom tier with a reset button and fires onReset", () => {
|
||||||
|
const onReset = vi.fn();
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatSelectRow
|
||||||
|
label="Shadow"
|
||||||
|
value="soft"
|
||||||
|
options={["none", "soft", "lift", "glow"]}
|
||||||
|
tier="explicitCustom"
|
||||||
|
onChange={vi.fn()}
|
||||||
|
onReset={onReset}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const select = host.querySelector<HTMLSelectElement>("select");
|
||||||
|
expect(select?.className).toContain("text-panel-accent");
|
||||||
|
const reset = host.querySelector<HTMLButtonElement>('[data-flat-select-reset="true"]');
|
||||||
|
act(() => reset?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
||||||
|
expect(onReset).toHaveBeenCalledTimes(1);
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fires onChange when the select value changes", () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
const { host, root } = renderInto(
|
||||||
|
<FlatSelectRow
|
||||||
|
label="Overflow"
|
||||||
|
value="visible"
|
||||||
|
options={["visible", "hidden", "clip", "auto", "scroll"]}
|
||||||
|
tier="default"
|
||||||
|
onChange={onChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const select = host.querySelector<HTMLSelectElement>("select");
|
||||||
|
if (!select) throw new Error("expected a select");
|
||||||
|
act(() => {
|
||||||
|
select.value = "hidden";
|
||||||
|
select.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
});
|
||||||
|
expect(onChange).toHaveBeenCalledWith("hidden");
|
||||||
|
act(() => root.unmount());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -311,3 +311,67 @@ export function FlatSlider({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* FlatSelectRow — label/value row backed by a native <select> */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
export function FlatSelectRow({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
tier,
|
||||||
|
disabled,
|
||||||
|
onChange,
|
||||||
|
onReset,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
options: string[];
|
||||||
|
tier: PropertyValueTier;
|
||||||
|
disabled?: boolean;
|
||||||
|
onChange: (nextValue: string) => void;
|
||||||
|
onReset?: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="group flex min-h-[30px] items-center justify-between">
|
||||||
|
<span className={`text-[11px] ${VALUE_TIER_LABEL_CLASS[tier]}`}>{label}</span>
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<label className="flex items-center gap-1.5">
|
||||||
|
<select
|
||||||
|
value={value}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
className={`appearance-none bg-transparent text-right font-mono text-[11px] outline-none disabled:cursor-not-allowed ${VALUE_TIER_VALUE_CLASS[tier]}`}
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
fill="currentColor"
|
||||||
|
className="flex-shrink-0 text-panel-text-5"
|
||||||
|
>
|
||||||
|
<path d="M2 3l3 4 3-4z" />
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
{tier === "explicitCustom" && onReset && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-flat-select-reset="true"
|
||||||
|
title="Remove — fall back to default"
|
||||||
|
onClick={onReset}
|
||||||
|
className="flex-shrink-0 text-panel-text-3 opacity-0 transition-opacity hover:text-panel-text-1 group-hover:opacity-100"
|
||||||
|
>
|
||||||
|
<RotateCcw size={11} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user