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) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7157-steps-dropdown-theme.md
Normal file
7
.changeset/fn-7157-steps-dropdown-theme.md
Normal file
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(<Host steps={[]} />);
|
||||
@@ -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(<Host steps={[STEP, STEP_WITHOUT_DESCRIPTION, STEP_TWO]} initial={["browser-verification", "test-review"]} />);
|
||||
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(<Host steps={[STEP_WITHOUT_DESCRIPTION]} />);
|
||||
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(<Host steps={[STEP]} />);
|
||||
const trigger = screen.getByTestId("wf-optional-steps-dropdown-trigger");
|
||||
|
||||
Reference in New Issue
Block a user