style(quick-pane): use standard titlebar drag

This commit is contained in:
2026-08-28 14:43:43 +02:00
parent 9345b67c9f
commit d448152e7f
4 changed files with 32 additions and 38 deletions
+5 -2
View File
@@ -278,7 +278,7 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
assert.match(source, /new ResizeObserver\(resizeToContent\)/); assert.match(source, /new ResizeObserver\(resizeToContent\)/);
assert.match(source, /invoke\("resize_quick_pane", \{ height \}\)/); assert.match(source, /invoke\("resize_quick_pane", \{ height \}\)/);
assert.doesNotMatch(source, /onCloseRequested/); assert.doesNotMatch(source, /onCloseRequested/);
assert.match(source, /className="quick-pane-drag-region"/); assert.match(source, /className="quick-pane-titlebar"/);
assert.match(source, /startDragging\(\)/); assert.match(source, /startDragging\(\)/);
assert.match(source, /className="quick-pane-request"/); assert.match(source, /className="quick-pane-request"/);
assert.match(source, /className=\{`quick-pane-exchange/); assert.match(source, /className=\{`quick-pane-exchange/);
@@ -322,7 +322,10 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
stylesheet, stylesheet,
/\.quick-pane-composer[\s\S]*?height: 120px[\s\S]*?min-height: 110px[\s\S]*?max-height: 130px/, /\.quick-pane-composer[\s\S]*?height: 120px[\s\S]*?min-height: 110px[\s\S]*?max-height: 130px/,
); );
assert.match(stylesheet, /\.quick-pane-drag-region[\s\S]*?position:\s*absolute[\s\S]*?height:\s*10px[\s\S]*?cursor:\s*grab/); assert.match(
stylesheet,
/\.quick-pane-titlebar[\s\S]*?height:\s*20px[\s\S]*?border-bottom:[\s\S]*?cursor:\s*grab/,
);
assert.match( assert.match(
stylesheet, stylesheet,
/\.quick-pane-composer-actions[\s\S]*?position: absolute/, /\.quick-pane-composer-actions[\s\S]*?position: absolute/,
+5 -5
View File
@@ -234,7 +234,7 @@ describe("QuickPane", () => {
expect( expect(
Array.from(pane?.children ?? []).map((element) => element.classList[0]), Array.from(pane?.children ?? []).map((element) => element.classList[0]),
).toEqual([ ).toEqual([
"quick-pane-drag-region", "quick-pane-titlebar",
"quick-pane-toolbar", "quick-pane-toolbar",
"quick-pane-composer", "quick-pane-composer",
"quick-pane-exchange", "quick-pane-exchange",
@@ -280,13 +280,13 @@ describe("QuickPane", () => {
} }
}); });
it("starts native dragging from whole visible region, never toolbar controls", async () => { it("starts native dragging from the titlebar, never toolbar controls", async () => {
bridgeMock([defaultModels]); bridgeMock([defaultModels]);
const { container } = render(<QuickPane />); const { container } = render(<QuickPane />);
await preparedTrigger(); await preparedTrigger();
const dragRegion = container.querySelector(".quick-pane-drag-region")!; const titlebar = container.querySelector(".quick-pane-titlebar")!;
expect(dragRegion.querySelector(".quick-pane-drag-grip")).toBeTruthy(); expect(titlebar).toHaveTextContent("Quick Pane");
fireEvent.pointerDown(dragRegion); fireEvent.pointerDown(titlebar);
expect(mocks.startDragging).toHaveBeenCalledTimes(1); expect(mocks.startDragging).toHaveBeenCalledTimes(1);
fireEvent.pointerDown( fireEvent.pointerDown(
screen.getByRole("button", { name: "Close Quick Pane" }), screen.getByRole("button", { name: "Close Quick Pane" }),
+8 -6
View File
@@ -1136,14 +1136,16 @@ export default function QuickPane() {
return ( return (
<main className="quick-pane" ref={paneRef}> <main className="quick-pane" ref={paneRef}>
<div <div
className="quick-pane-drag-region" className="quick-pane-titlebar"
aria-hidden="true" onPointerDown={(event) => {
onPointerDown={() => if (event.button !== 0) return;
void getCurrentWindow() void getCurrentWindow()
.startDragging() .startDragging()
.catch(() => undefined) .catch(() => undefined);
} }}
/> >
<span>Quick Pane</span>
</div>
<header className="quick-pane-toolbar"> <header className="quick-pane-toolbar">
<div className="quick-pane-primary-controls"> <div className="quick-pane-primary-controls">
<ModelSelector <ModelSelector
+14 -25
View File
@@ -47,37 +47,26 @@ body {
align-items: center; align-items: center;
} }
.quick-pane-drag-region { .quick-pane-titlebar {
position: absolute; display: flex;
z-index: 1; height: 20px;
top: 0; flex: 0 0 20px;
right: 0; align-items: center;
left: 0; padding: 0 8px;
height: 10px; border-bottom: 1px solid #4b4b4b;
color: #aaa39a;
cursor: grab; cursor: grab;
touch-action: none; font-size: 10px;
font-weight: 750;
letter-spacing: 0.08em;
text-transform: uppercase;
user-select: none;
} }
.quick-pane-drag-region:active { .quick-pane-titlebar:active {
cursor: grabbing; cursor: grabbing;
} }
.quick-pane-drag-region::after {
position: absolute;
top: 2px;
right: 18px;
left: 18px;
height: 5px;
border-radius: 999px;
background: repeating-linear-gradient(
90deg,
#817c76 0 5px,
transparent 5px 9px
);
box-shadow: 0 0 5px rgba(190, 185, 178, 0.18);
content: "";
}
.quick-pane-toolbar { .quick-pane-toolbar {
flex: 0 0 auto; flex: 0 0 auto;
flex-wrap: nowrap; flex-wrap: nowrap;