feat(studio): add PinnedGroupRow always-open pinned-group primitive

This commit is contained in:
Vance Ingalls
2026-07-14 15:51:54 -07:00
parent 4bff97090b
commit 31849d62e2
2 changed files with 63 additions and 0 deletions
@@ -10,6 +10,7 @@ import {
FlatSelectRow,
FlatSlider,
FlatToggle,
PinnedGroupRow,
PinnedZoneDivider,
} from "./propertyPanelFlatPrimitives";
@@ -456,3 +457,21 @@ describe("FlatToggle", () => {
act(() => root.unmount());
});
});
describe("PinnedGroupRow", () => {
it("renders a 'Pinned' badge, filled pin icon, and always shows children", () => {
const onUnpin = vi.fn();
const { host, root } = renderInto(
<PinnedGroupRow title="Motion" onUnpin={onUnpin}>
<div data-testid="body">body</div>
</PinnedGroupRow>,
);
expect(host.textContent).toContain("Pinned");
expect(host.textContent).toContain("Motion");
expect(host.querySelector('[data-testid="body"]')).not.toBeNull();
const unpin = host.querySelector<HTMLButtonElement>('[data-pinned-group-unpin="true"]');
act(() => unpin?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
expect(onUnpin).toHaveBeenCalledTimes(1);
act(() => root.unmount());
});
});
@@ -449,3 +449,47 @@ export function FlatToggle({
</div>
);
}
/* ------------------------------------------------------------------ */
/* PinnedGroupRow — always-open pinned group (design_handoff #8a) */
/* ------------------------------------------------------------------ */
export function PinnedGroupRow({
title,
accessory,
onUnpin,
children,
}: {
title: string;
accessory?: ReactNode;
onUnpin: () => void;
children: ReactNode;
}) {
return (
<div className="border-b border-panel-hairline px-4 py-3" data-pinned-group="true">
<div className="mb-2.5 flex items-center justify-between">
<span className="flex items-center gap-1.5">
<span className="text-[9px] font-semibold uppercase tracking-[0.08em] text-panel-accent">
Pinned
</span>
<span className="text-[12px] font-semibold text-panel-text-0">{title}</span>
</span>
<span className="flex items-center gap-2.5 text-panel-text-5">
{accessory}
<button
type="button"
data-pinned-group-unpin="true"
title="Unpin — returns to the stack"
onClick={onUnpin}
className="text-panel-accent"
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
<path d="M4 1h4v3.2l1.4 1.4V7H7v4L6 12l-1-1V7H2.6V5.6L4 4.2z" />
</svg>
</button>
</span>
</div>
{children}
</div>
);
}