diff --git a/apps/web/src/appInit.tsx b/apps/web/src/appInit.tsx index ccb7a8b82000e..8b2c9df02e25f 100644 --- a/apps/web/src/appInit.tsx +++ b/apps/web/src/appInit.tsx @@ -5,6 +5,7 @@ import * as Schema from "effect/Schema"; import { applyAppearanceContrast } from "./appearanceContrast"; import { applyAppearanceFontVariables, TYPOGRAPHY_ADVANCED_STORAGE_KEY } from "./appearanceFonts"; +import { applyGlassAppearance } from "./appearanceGlass"; import { startElectronBrowserHost } from "./browser/ElectronBrowserHost"; import { startBrowserProfileReporting } from "./browser/BrowserProfileReporter"; import { startPreviewTitleRecording } from "./browserHistoryStore"; @@ -83,12 +84,7 @@ function startClientAppearance(): void { applyAppearanceContrast(root, settings.appearanceContrast); } if (previous?.glassOpacity !== settings.glassOpacity) { - root.style.setProperty("--glass-opacity", `${settings.glassOpacity}%`); - if (settings.glassOpacity === 100) { - root.style.setProperty("--glass-blur", "0px"); - } else { - root.style.removeProperty("--glass-blur"); - } + applyGlassAppearance(root, settings.glassOpacity); } if ( previous?.fontFamilySans !== settings.fontFamilySans || diff --git a/apps/web/src/appearanceGlass.ts b/apps/web/src/appearanceGlass.ts new file mode 100644 index 0000000000000..1d527e5773cbf --- /dev/null +++ b/apps/web/src/appearanceGlass.ts @@ -0,0 +1,10 @@ +export function applyGlassAppearance(root: HTMLElement, opacity: number): void { + root.style.setProperty("--glass-opacity", `${opacity}%`); + if (opacity === 100) { + root.style.setProperty("--glass-blur", "0px"); + root.style.setProperty("--dialog-backdrop-filter", "none"); + } else { + root.style.removeProperty("--glass-blur"); + root.style.removeProperty("--dialog-backdrop-filter"); + } +} diff --git a/apps/web/src/components/ui/dialog.browser.test.tsx b/apps/web/src/components/ui/dialog.browser.test.tsx new file mode 100644 index 0000000000000..b82b66abcae70 --- /dev/null +++ b/apps/web/src/components/ui/dialog.browser.test.tsx @@ -0,0 +1,60 @@ +import { expect, it, vi } from "vite-plus/test"; +import { page } from "vite-plus/test/browser"; + +import "~/index.css"; +import { render } from "../../../test/render"; +import { applyGlassAppearance } from "../../appearanceGlass"; +import { Dialog, DialogPopup } from "./dialog"; + +function setup(opacity: number) { + const root = document.documentElement; + const attributes = ["style", "class"].map((name) => ({ name, value: root.getAttribute(name) })); + const motion = document.createElement("style"); + motion.textContent = '[data-slot="dialog-backdrop"] { transition: none !important; }'; + document.head.append(motion); + applyGlassAppearance(root, opacity); + return { + [Symbol.dispose]() { + motion.remove(); + for (const { name, value } of attributes) { + if (value === null) root.removeAttribute(name); + else root.setAttribute(name, value); + } + }, + }; +} + +it.each([80, 100])( + "applies glass opacity %i and updates backdrop filtering without changing dimming", + async (initialOpacity) => { + using _appearance = setup(initialOpacity); + await using _screen = await render( + + Dialog content + , + ); + await expect.element(page.getByRole("dialog", { name: "Example dialog" })).toBeVisible(); + const backdrop = document.querySelector('[data-slot="dialog-backdrop"]'); + if (!(backdrop instanceof HTMLElement)) throw new Error("Missing dialog backdrop"); + const root = document.documentElement; + expect(getComputedStyle(backdrop).backdropFilter).toBe( + initialOpacity === 100 ? "none" : "blur(4px)", + ); + + for (const appearance of ["light", "dark"]) { + root.classList.toggle("dark", appearance === "dark"); + const tint = getComputedStyle(backdrop).backgroundColor; + for (const opacity of [100, 95, 100, 80]) { + applyGlassAppearance(root, opacity); + const opaque = opacity === 100; + await vi.waitFor(() => + expect(getComputedStyle(backdrop).backdropFilter).toBe(opaque ? "none" : "blur(4px)"), + ); + expect(root.style.getPropertyValue("--glass-opacity")).toBe(`${opacity}%`); + expect(root.style.getPropertyValue("--glass-blur")).toBe(opaque ? "0px" : ""); + expect(root.style.getPropertyValue("--dialog-backdrop-filter")).toBe(opaque ? "none" : ""); + expect(getComputedStyle(backdrop).backgroundColor).toBe(tint); + } + } + }, +); diff --git a/apps/web/src/index.css b/apps/web/src/index.css index a8f3d33c1853d..9baf9ae2c6f1c 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -443,8 +443,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil @utility dialog-backdrop { background: color-mix(in srgb, var(--background) 60%, transparent); - -webkit-backdrop-filter: blur(4px); - backdrop-filter: blur(4px); + -webkit-backdrop-filter: var(--dialog-backdrop-filter, blur(4px)); + backdrop-filter: var(--dialog-backdrop-filter, blur(4px)); @variant dark { background: color-mix(in srgb, var(--background) 64%, transparent);