("input");
+ if (!input) throw new Error("expected an input");
+ act(() => {
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ "value",
+ )?.set;
+ nativeInputValueSetter?.call(input, "24px");
+ input.dispatchEvent(new Event("input", { bubbles: true }));
+ });
+ act(() => {
+ input.dispatchEvent(new Event("focusout", { bubbles: true }));
+ });
+ expect(onCommit).toHaveBeenCalledWith("24px");
+ act(() => root.unmount());
+ });
+});
+
+describe("FlatSegmentedRow", () => {
+ it("underlines the active option in mint and leaves others muted", () => {
+ const onChange = vi.fn();
+ const { host, root } = renderInto(
+ ,
+ );
+ const options = host.querySelectorAll('[data-flat-segment="true"]');
+ expect(options).toHaveLength(2);
+ expect((options[0] as HTMLElement).className).toContain("text-panel-text-4");
+ expect((options[1] as HTMLElement).className).toContain("border-panel-accent");
+ act(() =>
+ (options[0] as HTMLElement).dispatchEvent(new MouseEvent("click", { bubbles: true })),
+ );
+ expect(onChange).toHaveBeenCalledWith("left");
+ act(() => root.unmount());
+ });
+});
diff --git a/packages/studio/src/components/editor/propertyPanelFlatPrimitives.tsx b/packages/studio/src/components/editor/propertyPanelFlatPrimitives.tsx
new file mode 100644
index 000000000..5acd863ff
--- /dev/null
+++ b/packages/studio/src/components/editor/propertyPanelFlatPrimitives.tsx
@@ -0,0 +1,133 @@
+import { type ReactNode } from "react";
+import { RotateCcw } from "../../icons/SystemIcons";
+import { CommitField } from "./propertyPanelPrimitives";
+import {
+ VALUE_TIER_LABEL_CLASS,
+ VALUE_TIER_VALUE_CLASS,
+ type PropertyValueTier,
+} from "./propertyPanelValueTier";
+
+/* ------------------------------------------------------------------ */
+/* FlatRow — single-column label/value property row */
+/* ------------------------------------------------------------------ */
+
+export function FlatRow({
+ label,
+ value,
+ tier,
+ disabled,
+ liveCommit,
+ suffix,
+ dropdown,
+ onCommit,
+ onReset,
+}: {
+ label: string;
+ value: string;
+ tier: PropertyValueTier;
+ disabled?: boolean;
+ liveCommit?: boolean;
+ suffix?: ReactNode;
+ /** Renders a trailing 10px caret-down, for select-backed rows. */
+ dropdown?: boolean;
+ onCommit: (nextValue: string) => void;
+ onReset?: () => void;
+}) {
+ return (
+
+
{label}
+
+
+
+
+ {suffix}
+ {tier === "explicitCustom" && onReset && (
+
+ )}
+ {dropdown && (
+
+ )}
+
+
+ );
+}
+
+/* ------------------------------------------------------------------ */
+/* FlatSegmentedRow — inline glyph runs, no container background */
+/* ------------------------------------------------------------------ */
+
+export interface FlatSegmentOption {
+ key: string;
+ node: ReactNode;
+ active: boolean;
+}
+
+export function FlatSegmentedRow({
+ label,
+ options,
+ disabled,
+ /** Index (0-based) after which to render a 12px spacer — for combined rows
+ * like Text's "Case · Style", which pack two independent option groups. */
+ spacerAfterIndex,
+ onChange,
+}: {
+ label: string;
+ options: FlatSegmentOption[];
+ disabled?: boolean;
+ spacerAfterIndex?: number;
+ onChange: (nextKey: string) => void;
+}) {
+ return (
+
+ {label}
+
+ {options.map((option, index) => (
+
+
+ {spacerAfterIndex === index && }
+
+ ))}
+
+
+ );
+}
diff --git a/packages/studio/src/components/editor/propertyPanelPrimitives.tsx b/packages/studio/src/components/editor/propertyPanelPrimitives.tsx
index ae1243560..d8a36eeef 100644
--- a/packages/studio/src/components/editor/propertyPanelPrimitives.tsx
+++ b/packages/studio/src/components/editor/propertyPanelPrimitives.tsx
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { adjustNumericToken, FIELD, LABEL, parseNumericToken } from "./propertyPanelHelpers";
-function CommitField({
+export function CommitField({
value,
disabled,
liveCommit,