From b46d5615ab16ea381a581da91c2e407ac54a9262 Mon Sep 17 00:00:00 2001 From: JacobLinCool Date: Thu, 13 Aug 2026 20:25:11 +0800 Subject: [PATCH] Fix product page layout alignment --- app/globals.css | 6 +-- .../catalog/components/problem-catalog.tsx | 2 +- .../components/product-page-layout.test.ts | 51 +++++++++++++++++++ .../profiles/components/profile-settings.tsx | 16 +++--- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 src/features/catalog/components/product-page-layout.test.ts diff --git a/app/globals.css b/app/globals.css index 4c8c466..2c086ee 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1128,7 +1128,6 @@ body { min-height: 100vh; overflow: auto; } .catalog-toolbar { position: sticky; top: 0; z-index: 15; display: grid; grid-template-columns: minmax(210px, 1fr) repeat(4, minmax(110px, auto)); align-items: end; gap: 8px; margin-bottom: 34px; padding: 10px; border: 1px solid var(--product-line); border-radius: 10px; background: color-mix(in srgb, var(--product-bg), transparent 7%); backdrop-filter: blur(12px); } .catalog-toolbar input, .catalog-toolbar select { min-width: 0; border: 0; outline: 0; background: transparent; color: var(--product-text); font-size: 12px; } -.catalog-search input { flex: 1; } .catalog-collection { margin-bottom: 42px; } .collection-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 14px; } .collection-heading span { color: var(--product-muted); font-size: 10px; letter-spacing: .09em; text-transform: uppercase; } @@ -1188,6 +1187,7 @@ body { min-height: 100vh; overflow: auto; } .sign-in-empty > svg, .custom-collection-intro > svg { color: var(--product-primary); } .sign-in-empty h2, .custom-collection-intro h2 { margin: 5px 0 0; } .sign-in-empty p, .custom-collection-intro p { max-width: 620px; margin: 0 0 10px; color: var(--product-muted); font-size: 13px; line-height: 1.6; } +.profile-settings-content { max-width: 760px; } .profile-form { display: grid; gap: 17px; padding: 30px; border: 1px solid var(--product-line); border-radius: 12px; background: var(--product-surface); } .profile-identity { display: flex; align-items: center; gap: 13px; padding-bottom: 18px; border-bottom: 1px solid var(--product-line); } .profile-identity img { width: 48px; height: 48px; border-radius: 50%; } @@ -1676,7 +1676,7 @@ body { min-height: 100vh; overflow: auto; } .dashboard-columns { grid-template-columns: 1fr; } .product-section { padding: 22px 18px; } .catalog-toolbar { position: static; grid-template-columns: 1fr 1fr; } - .catalog-search { grid-column: 1 / -1; } + .catalog-toolbar > .catalog-toolbar-search { grid-column: 1 / -1; } .product-page .problem-list-head { display: none; } .product-page .problem-row { position: relative; grid-template-columns: 24px 30px minmax(0, 1fr) 16px; gap: 7px; padding: 12px 4px; } .product-page .problem-row .difficulty-pill { grid-column: 3; } @@ -1727,7 +1727,7 @@ body { min-height: 100vh; overflow: auto; } @media (max-width: 430px) { .catalog-toolbar { grid-template-columns: 1fr; } - .catalog-search { grid-column: auto; } + .catalog-toolbar > .catalog-toolbar-search { grid-column: auto; } .dashboard-hero h1 { font-size: 36px; } .hero-actions { display: grid; } .primary-action, .secondary-action { width: 100%; } diff --git a/src/features/catalog/components/problem-catalog.tsx b/src/features/catalog/components/problem-catalog.tsx index 049b5e4..2edaa44 100644 --- a/src/features/catalog/components/problem-catalog.tsx +++ b/src/features/catalog/components/problem-catalog.tsx @@ -52,7 +52,7 @@ export function ProblemCatalog() { return
Learn

{text.catalog}

{text.catalogIntro}

- + diff --git a/src/features/catalog/components/product-page-layout.test.ts b/src/features/catalog/components/product-page-layout.test.ts new file mode 100644 index 0000000..317252a --- /dev/null +++ b/src/features/catalog/components/product-page-layout.test.ts @@ -0,0 +1,51 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { test, vi } from "vitest"; + +vi.mock("../../platform/components/app-shell", () => ({ + useProduct: () => ({ + locale: "zh-TW", + refreshSession: async () => undefined, + session: undefined, + sessionStatus: "loading", + }), +})); + +vi.mock("../model/education-model", async () => { + const actual = await vi.importActual("../model/education-model"); + return { + ...actual, + useCatalog: () => ({ collections: [], error: "", loading: false }), + }; +}); + +import { ProfileSettings } from "../../profiles/components/profile-settings"; +import { ProblemCatalog } from "./problem-catalog"; + +test("problem catalog search uses only the toolbar FilterField layout", () => { + const html = renderToStaticMarkup(createElement(ProblemCatalog)); + + assert.match(html, /class="ui-filter-field catalog-toolbar-search"/u); + assert.doesNotMatch(html, /class="[^"]*\bcatalog-search\b/u); + assert.match(html, /class="ui-filter-control"[^>]*> { + const css = readFileSync(new URL("../../../../app/globals.css", import.meta.url), "utf8"); + const rules = [...css.matchAll(/\.catalog-toolbar > \.catalog-toolbar-search\s*\{([^}]*)\}/gu)] + .map((match) => match[1].trim()); + + assert.deepEqual(rules, ["grid-column: 1 / -1;", "grid-column: auto;"]); +}); + +test("profile settings uses the standard product page width", () => { + const html = renderToStaticMarkup(createElement(ProfileSettings)); + const css = readFileSync(new URL("../../../../app/globals.css", import.meta.url), "utf8"); + + assert.match(html, /
/u); + assert.match(html, /
/u); + assert.doesNotMatch(html, /\bnarrow-page\b/u); + assert.match(css, /\.profile-settings-content\s*\{\s*max-width:\s*760px;\s*\}/u); +}); diff --git a/src/features/profiles/components/profile-settings.tsx b/src/features/profiles/components/profile-settings.tsx index 2cb5684..d7685c7 100644 --- a/src/features/profiles/components/profile-settings.tsx +++ b/src/features/profiles/components/profile-settings.tsx @@ -119,14 +119,15 @@ export function ProfileSettings() { } } - if (erasureMessage) return
; + if (erasureMessage) return
; - return
+ return

{text.title}

{text.intro}

- {sessionStatus === "loading" &&
{text.loading}
} - {sessionStatus === "error" &&
{locale === "zh-TW" ? "無法確認帳號狀態。" : "Could not verify your account."}
} - {sessionStatus === "ready" && !session?.authenticated &&
} - {sessionStatus === "ready" && session?.authenticated && false}>{(profile) => <> +
+ {sessionStatus === "loading" &&
{text.loading}
} + {sessionStatus === "error" &&
{locale === "zh-TW" ? "無法確認帳號狀態。" : "Could not verify your account."}
} + {sessionStatus === "ready" && !session?.authenticated &&
} + {sessionStatus === "ready" && session?.authenticated && false}>{(profile) => <>
void save(event, profile)}>
{profile.displayName}@{profile.login} · {profile.verifiedSolvedCount} {text.verified}
@@ -146,6 +147,7 @@ export function ProfileSettings() {
- }
} + }} +
; }