diff --git a/.changeset/green-baths-doubt.md b/.changeset/green-baths-doubt.md new file mode 100644 index 0000000000..e2c61b5eb8 --- /dev/null +++ b/.changeset/green-baths-doubt.md @@ -0,0 +1,5 @@ +--- +"@emdash-cms/admin": patch +--- + +Fixes admin body colors and mobile dialog width limits overriding public themes when the admin stylesheet is loaded on a public page. diff --git a/packages/admin/package.json b/packages/admin/package.json index c1fcbe4fba..5a00edaeda 100644 --- a/packages/admin/package.json +++ b/packages/admin/package.json @@ -48,7 +48,7 @@ "dev": "tsdown --watch", "prepublishOnly": "node --run build", "check": "publint && attw --pack --ignore-rules=cjs-resolves-to-esm --ignore-rules=no-resolution", - "test": "vitest", + "test": "pnpm exec tailwindcss -i src/styles.css -o dist/styles.css --minify && vitest", "typecheck": "tsgo --noEmit", "locale:compile": "lingui compile --namespace es", "locale:copy": "node ./scripts/copy-locales.js", diff --git a/packages/admin/src/styles.css b/packages/admin/src/styles.css index d111f8b056..6ca4cd64d7 100644 --- a/packages/admin/src/styles.css +++ b/packages/admin/src/styles.css @@ -84,7 +84,7 @@ border-color: var(--color-kumo-line); } -body { +body:has(> #admin-root) { background-color: var(--color-kumo-elevated); color: var(--text-color-kumo-default); -webkit-font-smoothing: antialiased; @@ -1244,7 +1244,7 @@ div:has(> [role="listbox"] > .emdash-compact-select-option) { * which overrides max-width in CSS. We need to cap min-width too. */ @media (max-width: 639px) { - [role="dialog"] { + body:has(> #admin-root) [role="dialog"] { min-width: 0 !important; max-width: calc(100vw - 2rem); } diff --git a/packages/admin/tests/routes/bylines.test.tsx b/packages/admin/tests/routes/bylines.test.tsx index 3165ae8b2b..9180c345e7 100644 --- a/packages/admin/tests/routes/bylines.test.tsx +++ b/packages/admin/tests/routes/bylines.test.tsx @@ -186,12 +186,16 @@ describe("BylinesPage directory", () => { it("keeps the create dialog and its actions within a narrow viewport", async () => { await page.viewport(320, 640); + const container = document.createElement("div"); + container.id = "admin-root"; + document.body.append(container); const screen = await render( , + { container }, ); await screen.getByRole("button", { name: "New byline" }).first().click(); diff --git a/packages/admin/tests/styles.test.ts b/packages/admin/tests/styles.test.ts new file mode 100644 index 0000000000..b7199c7824 --- /dev/null +++ b/packages/admin/tests/styles.test.ts @@ -0,0 +1,53 @@ +import { afterEach, beforeEach, expect, it } from "vitest"; +import { page } from "vitest/browser"; + +import "../dist/styles.css"; + +let theme: HTMLStyleElement; +let root: HTMLDivElement; + +beforeEach(() => { + theme = document.createElement("style"); + theme.textContent = `@layer theme { + body { background-color: rgb(12, 34, 56); color: rgb(210, 220, 230); } + [role="dialog"] { position: fixed; inset: 0; width: 100vw; } + }`; + document.head.append(theme); + root = document.createElement("div"); + document.body.append(root); +}); + +afterEach(async () => { + theme.remove(); + root.remove(); + await page.viewport(1280, 800); +}); + +it("preserves a public theme's layered body colors when admin CSS is loaded", () => { + expect(getComputedStyle(document.body).backgroundColor).toBe("rgb(12, 34, 56)"); + expect(getComputedStyle(document.body).color).toBe("rgb(210, 220, 230)"); +}); + +it("preserves a public full-width mobile dialog when admin CSS is loaded", async () => { + await page.viewport(375, 800); + const dialog = document.createElement("div"); + dialog.setAttribute("role", "dialog"); + root.append(dialog); + expect(dialog.getBoundingClientRect().width).toBe(375); +}); + +it("keeps portaled admin dialogs within the mobile viewport", async () => { + await page.viewport(375, 800); + root.id = "admin-root"; + const portal = document.createElement("div"); + const dialog = document.createElement("div"); + dialog.setAttribute("role", "dialog"); + dialog.style.minWidth = "512px"; + portal.append(dialog); + document.body.append(portal); + try { + expect(dialog.getBoundingClientRect().width).toBe(343); + } finally { + portal.remove(); + } +});