From 8ef9750064564d4e74c14bdc4aec56878b71adc1 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 27 Jun 2026 20:21:59 -0700 Subject: [PATCH] FN-7157: theme quick entry steps dropdown Align the optional workflow steps menu with dashboard theme tokens. - Replace hardcoded fallback styling with shared surface, border, radius, shadow, hover, and accent tokens. - Add dropdown CSS regression coverage for canonical tokens and no hardcoded fallback colors. - Cover open-panel option states with multiple selections and missing descriptions. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7157-steps-dropdown-theme.md | 7 +++ .../components/WorkflowOptionalStepsDropdown.css | 15 +++--- .../WorkflowOptionalStepsDropdown.test.tsx | 57 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-7157 Fusion-Task-Lineage: 2b68c021-416c-403d-b137-1afc91870d3f Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7157-steps-dropdown-theme.md | 7 +++ .../WorkflowOptionalStepsDropdown.css | 15 +++-- .../WorkflowOptionalStepsDropdown.test.tsx | 57 +++++++++++++++++++ 3 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 .changeset/fn-7157-steps-dropdown-theme.md diff --git a/.changeset/fn-7157-steps-dropdown-theme.md b/.changeset/fn-7157-steps-dropdown-theme.md new file mode 100644 index 0000000000..77a1ebad0c --- /dev/null +++ b/.changeset/fn-7157-steps-dropdown-theme.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Theme the quick-entry steps drop-down with canonical dashboard menu tokens. +category: fix +dev: Aligns WorkflowOptionalStepsDropdown panel CSS with shared dropdown surface, border, radius, shadow, and hover tokens. diff --git a/packages/dashboard/app/components/WorkflowOptionalStepsDropdown.css b/packages/dashboard/app/components/WorkflowOptionalStepsDropdown.css index dff10a8c26..6cfb8b6f1e 100644 --- a/packages/dashboard/app/components/WorkflowOptionalStepsDropdown.css +++ b/packages/dashboard/app/components/WorkflowOptionalStepsDropdown.css @@ -20,11 +20,11 @@ max-height: 320px; overflow-y: auto; padding: 4px; - background: var(--surface, #fff); + /* FNXC:DashboardThemeTokens 2026-06-27-00:00: Optional-step panels share the canonical dropdown surface tokens so Quick Entry, Task Form, and Inline Create render correctly across dark, light, and shadcn color themes without hardcoded hex or px fallbacks. */ + background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius-sm, 6px); - /* FNXC:DashboardThemeTokens 2026-06-25-13:15: Shadow fallback must stay tokenized via color-mix, never a raw alpha color call, so the global/component no-raw-rgb CSS hygiene guards pass. */ - box-shadow: var(--shadow-md, 0 6px 20px color-mix(in srgb, #000000 18%, transparent)); + border-radius: var(--radius); + box-shadow: var(--shadow-lg); } .wf-optional-steps-dropdown-option { @@ -32,18 +32,17 @@ align-items: flex-start; gap: 8px; padding: 6px 8px; - border-radius: var(--radius-sm, 6px); + border-radius: var(--radius-sm); cursor: pointer; } .wf-optional-steps-dropdown-option:hover, .wf-optional-steps-dropdown-option.is-active { - /* FNXC:DashboardTheming 2026-06-21-00:00: Status/theme-token tests ban raw rgba fallbacks for --surface-hover; use the shared color-mix fallback so every color theme, including newly added shadcn variants, keeps tokenized hover surfaces. */ - background: var(--surface-hover, color-mix(in srgb, var(--surface) 90%, var(--text) 10%)); + background: var(--card-hover); } .wf-optional-steps-dropdown-option:focus-visible { - outline: 2px solid var(--accent, #4f7cff); + outline: 2px solid var(--accent); outline-offset: -2px; } diff --git a/packages/dashboard/app/components/__tests__/WorkflowOptionalStepsDropdown.test.tsx b/packages/dashboard/app/components/__tests__/WorkflowOptionalStepsDropdown.test.tsx index 6ceac1ab9e..02e6663c4d 100644 --- a/packages/dashboard/app/components/__tests__/WorkflowOptionalStepsDropdown.test.tsx +++ b/packages/dashboard/app/components/__tests__/WorkflowOptionalStepsDropdown.test.tsx @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { describe, it, expect, vi, afterEach } from "vitest"; import { render, screen, fireEvent, cleanup, within } from "@testing-library/react"; import { useState } from "react"; @@ -22,6 +23,16 @@ const STEP_TWO: ResolvedWorkflowOptionalStep = { defaultOn: false, }; +const STEP_WITHOUT_DESCRIPTION: ResolvedWorkflowOptionalStep = { + templateId: "docs", + name: "Docs", + icon: "file-text", + phase: "post-implementation", + defaultOn: false, +}; + +const DROPDOWN_CSS = readFileSync("app/components/WorkflowOptionalStepsDropdown.css", "utf8"); + // Controlled host: parent owns the enabled set, mirroring the create surfaces. function Host({ steps, @@ -59,6 +70,21 @@ afterEach(() => { vi.clearAllMocks(); }); +describe("WorkflowOptionalStepsDropdown CSS", () => { + it("uses canonical dropdown theme tokens for the portal panel", () => { + expect(DROPDOWN_CSS).toContain("background: var(--surface);"); + expect(DROPDOWN_CSS).toContain("border: 1px solid var(--border);"); + expect(DROPDOWN_CSS).toContain("border-radius: var(--radius);"); + expect(DROPDOWN_CSS).toContain("box-shadow: var(--shadow-lg);"); + }); + + it("does not keep hardcoded color or px fallbacks in tokenized rules", () => { + expect(DROPDOWN_CSS).not.toMatch(/var\(--[^,]+,\s*#/); + expect(DROPDOWN_CSS).not.toMatch(/var\(--[^,]+,\s*\d+px/); + expect(DROPDOWN_CSS).not.toContain("#4f7cff"); + }); +}); + describe("WorkflowOptionalStepsDropdown", () => { it("renders nothing when there are no optional steps", () => { const { container } = render(); @@ -123,6 +149,37 @@ describe("WorkflowOptionalStepsDropdown", () => { expect(within(panel).getByText("Browser Verification")).toBeTruthy(); }); + it("renders open-panel data states for multiple selections and optional descriptions", () => { + render(); + fireEvent.click(screen.getByTestId("wf-optional-steps-dropdown-trigger")); + + const panel = screen.getByTestId("wf-optional-steps-dropdown-panel"); + const selectedWithDescription = within(panel).getByTestId( + "wf-optional-steps-dropdown-option-browser-verification", + ); + const unselectedWithoutDescription = within(panel).getByTestId("wf-optional-steps-dropdown-option-docs"); + const secondSelected = within(panel).getByTestId("wf-optional-steps-dropdown-option-test-review"); + + expect(selectedWithDescription).toHaveAttribute("role", "option"); + expect(selectedWithDescription).toHaveAttribute("aria-checked", "true"); + expect(within(selectedWithDescription).getByText("Verify web application functionality using browser automation")).toBeTruthy(); + expect(unselectedWithoutDescription).toHaveAttribute("role", "option"); + expect(unselectedWithoutDescription).toHaveAttribute("aria-checked", "false"); + expect(within(unselectedWithoutDescription).queryByText("Verify web application functionality using browser automation")).toBeNull(); + expect(secondSelected).toHaveAttribute("role", "option"); + expect(secondSelected).toHaveAttribute("aria-checked", "true"); + }); + + it("keeps option rows accessible when the open panel starts with zero selected", () => { + render(); + fireEvent.click(screen.getByTestId("wf-optional-steps-dropdown-trigger")); + + const option = screen.getByTestId("wf-optional-steps-dropdown-option-docs"); + expect(option).toHaveAttribute("role", "option"); + expect(option).toHaveAttribute("aria-checked", "false"); + expect(within(option).getByText("Docs")).toBeTruthy(); + }); + it("closes on Escape", () => { render(); const trigger = screen.getByTestId("wf-optional-steps-dropdown-trigger");