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");