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,
|
||||
FlatRow,
|
||||
FlatSegmentedRow,
|
||||
FlatSelectRow,
|
||||
FlatSlider,
|
||||
PinnedZoneDivider,
|
||||
} from "./propertyPanelFlatPrimitives";
|
||||
@@ -226,3 +227,62 @@ describe("FlatSlider", () => {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 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