diff --git a/docs/src/main.tsx b/docs/src/main.tsx
index 9222714..ab8fa74 100644
--- a/docs/src/main.tsx
+++ b/docs/src/main.tsx
@@ -809,6 +809,10 @@ function App() {
Use explicit header, action-row, or chrome modes only when the layout should not
adapt.
+
+ Floating chrome joins its title tab to the frame rail. Stacked chrome keeps a
+ full-width title bar inside the outer frame.
+
Menu popups clamp to the available viewport at narrow widths.
diff --git a/docs/src/window-regression-fixtures.tsx b/docs/src/window-regression-fixtures.tsx
index c5e43da..34d188a 100644
--- a/docs/src/window-regression-fixtures.tsx
+++ b/docs/src/window-regression-fixtures.tsx
@@ -161,8 +161,17 @@ export function WindowRegressionFixtures() {
-
-
+
+
Changes saved
diff --git a/src/styles.css b/src/styles.css
index cfbeddd..8b78d68 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1139,16 +1139,18 @@
}
.greyui-window-frame {
+ --greyui-window-shadow: 3px 3px 6px rgb(0 0 0 / 22%);
+
position: relative;
display: grid;
isolation: isolate;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
- padding: var(--greyui-tab-height) 3px 3px;
+ padding: calc(var(--greyui-tab-height) + 3px) 3px 3px;
border: 0;
background: transparent;
- box-shadow: 3px 3px 6px rgb(0 0 0 / 22%);
+ box-shadow: none;
}
.greyui-window-frame::before {
@@ -1160,13 +1162,15 @@
background: var(--greyui-panel);
box-shadow:
inset 1px 1px 0 var(--greyui-border-light),
- inset -1px -1px 0 var(--greyui-border-dark);
+ inset -1px -1px 0 var(--greyui-border-dark),
+ var(--greyui-window-shadow);
pointer-events: none;
}
.greyui-window[data-active="false"] .greyui-window-frame {
+ --greyui-window-shadow: 2px 2px 4px rgb(0 0 0 / 18%);
+
opacity: 0.94;
- box-shadow: 2px 2px 4px rgb(0 0 0 / 18%);
}
.greyui-window[data-collapsed="true"] .greyui-window-frame {
diff --git a/tests/browser/docs-geometry.spec.ts b/tests/browser/docs-geometry.spec.ts
index 66a9c14..033f818 100644
--- a/tests/browser/docs-geometry.spec.ts
+++ b/tests/browser/docs-geometry.spec.ts
@@ -108,10 +108,52 @@ for (const viewport of viewports) {
"position",
"absolute",
);
+ await expectFloatingFrameRail(suite, 520);
+ await expectFloatingFrameRail(suite, 640);
+ await expectFloatingFrameRail(suite, 820);
+ await expectStackedTitleJoin(suite, 280);
+ await expectStackedTitleJoin(suite, 760);
}
});
}
+async function expectFloatingFrameRail(suite: Locator, width: number) {
+ const geometry = await windowGeometry(suite, width);
+
+ expect(geometry.framePaddingTop).toBe(geometry.tabHeight + 3);
+ expect(geometry.bodyTop - geometry.tabBottom).toBeCloseTo(3, 5);
+}
+
+async function expectStackedTitleJoin(suite: Locator, width: number) {
+ const geometry = await windowGeometry(suite, width);
+
+ expect(geometry.bodyTop - geometry.tabBottom).toBeCloseTo(0, 5);
+}
+
+async function windowGeometry(suite: Locator, width: number) {
+ return suite.locator(`[data-regression-width='${width}']`).evaluate((fixture) => {
+ const frame = fixture.querySelector(".greyui-window-frame");
+ const tab = fixture.querySelector(".greyui-window-tab");
+ const body = fixture.querySelector(".greyui-window-body");
+ if (frame === null || tab === null || body === null) {
+ throw new Error(
+ `Missing Window geometry for ${fixture.dataset.regressionWidth ?? "unknown"}`,
+ );
+ }
+
+ const frameStyle = getComputedStyle(frame);
+ const tabRect = tab.getBoundingClientRect();
+ const bodyRect = body.getBoundingClientRect();
+
+ return {
+ bodyTop: bodyRect.top,
+ framePaddingTop: Number.parseFloat(frameStyle.paddingTop),
+ tabBottom: tabRect.bottom,
+ tabHeight: tabRect.height,
+ };
+ });
+}
+
test("edge menu and form popups remain inside the viewport", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/");
diff --git a/tests/styles.test.ts b/tests/styles.test.ts
index f07a4e3..2f1b2d6 100644
--- a/tests/styles.test.ts
+++ b/tests/styles.test.ts
@@ -64,13 +64,19 @@ describe("default theme contrast", () => {
css.indexOf(".greyui-window-frame {"),
css.indexOf(".greyui-window-frame::before"),
);
+ const frameSurfaceRule = css.slice(
+ css.indexOf(".greyui-window-frame::before"),
+ css.indexOf('.greyui-window[data-active="false"] .greyui-window-frame'),
+ );
const bodyRule = css.slice(
css.indexOf(".greyui-window-body {"),
css.indexOf(".greyui-window-content {"),
);
expect(rootRule).not.toContain("box-shadow:");
- expect(frameRule).toContain("box-shadow:");
+ expect(frameRule).toContain("padding: calc(var(--greyui-tab-height) + 3px) 3px 3px;");
+ expect(frameRule).toContain("box-shadow: none;");
+ expect(frameSurfaceRule).toContain("var(--greyui-window-shadow)");
expect(css).toMatch(/\.greyui-window-frame::before\s*\{[\s\S]*?border:/);
expect(bodyRule).not.toContain("border:");
expect(bodyRule).not.toContain("box-shadow:");