From 1e893db1407eaa88a76cd3c29e03298f67923d0c Mon Sep 17 00:00:00 2001 From: DavidQ Date: Sat, 20 Jun 2026 12:14:50 -0400 Subject: [PATCH] PR_26171_034 style idea board chevrons inline --- assets/theme-v2/css/tables.css | 38 +++++++++++++++++++ .../tools/IdeaBoardTableNotes.spec.mjs | 36 +++++++++++++++--- .../tools/ToolboxRoutePages.spec.mjs | 30 ++++++++++++++- toolbox/idea-board/index.js | 14 +++++-- 4 files changed, 107 insertions(+), 11 deletions(-) diff --git a/assets/theme-v2/css/tables.css b/assets/theme-v2/css/tables.css index 78461f812..8ab6de76b 100644 --- a/assets/theme-v2/css/tables.css +++ b/assets/theme-v2/css/tables.css @@ -50,6 +50,44 @@ td { text-transform: none } +.data-table [data-idea-board-idea-cell] { + cursor: pointer +} + +.idea-board-idea-label { + display: inline-flex; + align-items: center; + gap: .35em; + color: inherit; + font: inherit; + line-height: inherit; + vertical-align: baseline +} + +.idea-board-idea-chevron { + display: inline-block; + width: 1em; + height: 1em; + flex: 0 0 1em; + background: currentColor; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain +} + +.idea-board-idea-chevron--down { + -webkit-mask-image: url("../images/gfs-chevron-down.svg"); + mask-image: url("../images/gfs-chevron-down.svg") +} + +.idea-board-idea-chevron--up { + -webkit-mask-image: url("../images/gfs-chevron-up.svg"); + mask-image: url("../images/gfs-chevron-up.svg") +} + .tool-form-table { table-layout: fixed; width: 100% diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs index 3da732b14..2f1775a74 100644 --- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs @@ -10,6 +10,32 @@ function restoreEnvValue(key, value) { process.env[key] = value; } +async function expectIdeaChevron(page, ideaId, iconName) { + const metrics = await page.locator(`[data-idea-board-idea-row='${ideaId}'] th`).evaluate((cell, targetIdeaId) => { + const label = cell.querySelector(".idea-board-idea-label"); + const icon = cell.querySelector(`[data-idea-board-chevron='${targetIdeaId}']`); + const cellStyles = getComputedStyle(cell); + const labelStyles = getComputedStyle(label); + const iconStyles = getComputedStyle(icon); + return { + iconName: icon.dataset.ideaBoardChevronIcon, + labelDisplay: labelStyles.display, + iconWidth: Number.parseFloat(iconStyles.width), + iconHeight: Number.parseFloat(iconStyles.height), + fontSize: Number.parseFloat(cellStyles.fontSize), + iconColor: iconStyles.backgroundColor, + textColor: cellStyles.color, + maskImage: iconStyles.getPropertyValue("-webkit-mask-image") || iconStyles.maskImage, + }; + }, ideaId); + expect(metrics.iconName).toBe(iconName); + expect(metrics.labelDisplay).toBe("inline-flex"); + expect(Math.abs(metrics.iconWidth - metrics.fontSize)).toBeLessThanOrEqual(1); + expect(Math.abs(metrics.iconHeight - metrics.fontSize)).toBeLessThanOrEqual(1); + expect(metrics.iconColor).toBe(metrics.textColor); + expect(metrics.maskImage).toContain(iconName); +} + test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page }) => { const server = await startRepoServer(); const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; @@ -58,7 +84,7 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await expect(page.getByText("Selected")).toHaveCount(0); await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] th")).toHaveText("Top Thoughts"); - await expect(page.locator("[data-idea-board-chevron='top-thoughts']")).toHaveAttribute("src", /gfs-chevron-down\.svg$/); + await expectIdeaChevron(page, "top-thoughts", "gfs-chevron-down.svg"); await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(0)).toHaveText("Smartest person wins..."); await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(1)).toHaveText("Exploring"); await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(2)).toHaveText("2026-06-20"); @@ -66,7 +92,7 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Delete"]); await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] th")).toHaveText("Sky Orchard"); - await expect(page.locator("[data-idea-board-chevron='sky-orchard']")).toHaveAttribute("src", /gfs-chevron-down\.svg$/); + await expectIdeaChevron(page, "sky-orchard", "gfs-chevron-down.svg"); await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] td").nth(0)).toHaveText("Grow floating islands..."); await expect(page.locator("[data-idea-board-notes-count='sky-orchard']")).toHaveText("3 Notes"); await expect(page.locator("[data-idea-board-idea-row='clockwork-courier'] th")).toHaveText("Clockwork Courier"); @@ -77,7 +103,7 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await expect(page.locator("[data-idea-board-expanded-row]")).toHaveCount(0); await page.locator("[data-idea-board-idea-cell='top-thoughts']").click(); await expect(page.locator("[data-idea-board-expanded-row='top-thoughts']")).toBeVisible(); - await expect(page.locator("[data-idea-board-chevron='top-thoughts']")).toHaveAttribute("src", /gfs-chevron-up\.svg$/); + await expectIdeaChevron(page, "top-thoughts", "gfs-chevron-up.svg"); await expect(page.locator("[data-idea-board-expanded-row='top-thoughts'] [data-idea-board-notes-header='top-thoughts']")).toHaveText("Notes"); await expect(page.locator("[data-idea-board-notes-table='top-thoughts'] th[scope='col']")).toHaveText(["Note", "Actions"]); await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Type" })).toHaveCount(0); @@ -113,12 +139,12 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await page.locator("[data-idea-board-idea-cell='sky-orchard']").click(); await expect(page.locator("[data-idea-board-expanded-row='top-thoughts']")).toHaveCount(0); await expect(page.locator("[data-idea-board-expanded-row='sky-orchard']")).toBeVisible(); - await expect(page.locator("[data-idea-board-chevron='sky-orchard']")).toHaveAttribute("src", /gfs-chevron-up\.svg$/); + await expectIdeaChevron(page, "sky-orchard", "gfs-chevron-up.svg"); await page.locator("[data-idea-board-notes-count='sky-orchard']").click(); await expect(page.locator("[data-idea-board-expanded-row='sky-orchard']")).toBeVisible(); await page.locator("[data-idea-board-idea-cell='sky-orchard']").click(); await expect(page.locator("[data-idea-board-expanded-row]")).toHaveCount(0); - await expect(page.locator("[data-idea-board-chevron='sky-orchard']")).toHaveAttribute("src", /gfs-chevron-down\.svg$/); + await expectIdeaChevron(page, "sky-orchard", "gfs-chevron-down.svg"); await page.locator("[data-idea-board-add-idea]").click(); const ideaInputRow = page.locator("[data-idea-board-idea-input-row]").last(); diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs index 5c17468c2..60fa5e24b 100644 --- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs +++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs @@ -112,6 +112,32 @@ function restoreEnvValue(key, value) { process.env[key] = value; } +async function expectIdeaChevron(page, ideaId, iconName) { + const metrics = await page.locator(`[data-idea-board-idea-row='${ideaId}'] th`).evaluate((cell, targetIdeaId) => { + const label = cell.querySelector(".idea-board-idea-label"); + const icon = cell.querySelector(`[data-idea-board-chevron='${targetIdeaId}']`); + const cellStyles = getComputedStyle(cell); + const labelStyles = getComputedStyle(label); + const iconStyles = getComputedStyle(icon); + return { + iconName: icon.dataset.ideaBoardChevronIcon, + labelDisplay: labelStyles.display, + iconWidth: Number.parseFloat(iconStyles.width), + iconHeight: Number.parseFloat(iconStyles.height), + fontSize: Number.parseFloat(cellStyles.fontSize), + iconColor: iconStyles.backgroundColor, + textColor: cellStyles.color, + maskImage: iconStyles.getPropertyValue("-webkit-mask-image") || iconStyles.maskImage, + }; + }, ideaId); + expect(metrics.iconName).toBe(iconName); + expect(metrics.labelDisplay).toBe("inline-flex"); + expect(Math.abs(metrics.iconWidth - metrics.fontSize)).toBeLessThanOrEqual(1); + expect(Math.abs(metrics.iconHeight - metrics.fontSize)).toBeLessThanOrEqual(1); + expect(metrics.iconColor).toBe(metrics.textColor); + expect(metrics.maskImage).toContain(iconName); +} + test("tools route aliases render toolbox tool pages", async ({ page }) => { const server = await startRepoServer(); const failedRequests = []; @@ -226,13 +252,13 @@ test("Idea Board launches from Toolbox with accordion table notes model", async await expect(page.locator("[data-idea-board-notes-count='top-thoughts']")).toHaveText("3 Notes"); await expect(page.locator("[data-idea-board-notes-count='sky-orchard']")).toHaveText("3 Notes"); await expect(page.locator("[data-idea-board-notes-count='clockwork-courier']")).toHaveText("0 Notes"); - await expect(page.locator("[data-idea-board-chevron='top-thoughts']")).toHaveAttribute("src", /gfs-chevron-down\.svg$/); + await expectIdeaChevron(page, "top-thoughts", "gfs-chevron-down.svg"); await expect(page.locator("[data-idea-board-status]")).toHaveText("Idea Board table edits are in-page only. No project records, auth, AI, or database behavior is connected."); await page.locator("[data-idea-board-notes-count='top-thoughts']").click(); await expect(page.locator("[data-idea-board-expanded-row]")).toHaveCount(0); await page.locator("[data-idea-board-idea-cell='top-thoughts']").click(); await expect(page.locator("[data-idea-board-expanded-row='top-thoughts']")).toBeVisible(); - await expect(page.locator("[data-idea-board-chevron='top-thoughts']")).toHaveAttribute("src", /gfs-chevron-up\.svg$/); + await expectIdeaChevron(page, "top-thoughts", "gfs-chevron-up.svg"); await expect(page.locator("[data-idea-board-notes-header='top-thoughts']")).toHaveText("Notes"); await expect(page.locator("[data-idea-board-notes-table='top-thoughts'] th[scope='col']")).toHaveText(["Note", "Actions"]); await expect(page.getByText("Notes for Sky Orchard")).toHaveCount(0); diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js index adc397783..75d52d64d 100644 --- a/toolbox/idea-board/index.js +++ b/toolbox/idea-board/index.js @@ -188,13 +188,19 @@ function renderIdeaRow(tbody, record) { idea.setAttribute("aria-expanded", String(expanded)); idea.setAttribute("role", "button"); idea.setAttribute("aria-label", `${expanded ? "Collapse" : "Expand"} notes for ${record.idea}`); + const ideaLabel = document.createElement("span"); + ideaLabel.className = "idea-board-idea-label"; const ideaText = document.createElement("span"); + ideaText.className = "idea-board-idea-label__text"; ideaText.textContent = record.idea; - const chevron = document.createElement("img"); - chevron.alt = ""; + const chevron = document.createElement("span"); + const chevronIcon = expanded ? "gfs-chevron-up.svg" : "gfs-chevron-down.svg"; + chevron.className = `idea-board-idea-chevron idea-board-idea-chevron--${expanded ? "up" : "down"}`; + chevron.setAttribute("aria-hidden", "true"); chevron.dataset.ideaBoardChevron = record.ideaId; - chevron.src = expanded ? "assets/theme-v2/images/gfs-chevron-up.svg" : "assets/theme-v2/images/gfs-chevron-down.svg"; - idea.append(ideaText, " ", chevron); + chevron.dataset.ideaBoardChevronIcon = chevronIcon; + ideaLabel.append(ideaText, chevron); + idea.append(ideaLabel); row.append(idea); row.append(cell(record.pitch)); row.append(cell(record.status));