FN-7034: Align steps dropdown trigger styling

Align the optional steps dropdown trigger with shared task creation button styling.

- Reuse the dashboard btn btn-sm classes for the workflow optional steps trigger.
- Remove bespoke trigger button styling so shared button tokens control padding, border, radius, and states.
- Extend dropdown tests to cover shared classes across empty, selected, and disabled states.
- Add a patch changeset for the published Fusion package.

Files changed:
 .changeset/fn-7034-steps-dropdown-style.md         |  7 +++
 .../components/WorkflowOptionalStepsDropdown.css   | 14 ------
 .../components/WorkflowOptionalStepsDropdown.tsx   |  9 +++-
 .../WorkflowOptionalStepsDropdown.test.tsx         | 50 ++++++++++++++++++++--
 4 files changed, 61 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-7034

Fusion-Task-Lineage: 3ca2a726-2c67-44a0-9a88-467e4fc1ad6b
This commit is contained in:
gsxdsm
2026-06-25 20:28:38 -07:00
parent 4a4e389254
commit 6415eed0c1
4 changed files with 61 additions and 19 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Match the optional steps dropdown trigger to shared task creation buttons.
category: fix
dev: Reuses the dashboard `.btn .btn-sm` trigger styling for WorkflowOptionalStepsDropdown.

View File

@@ -6,20 +6,6 @@
display: inline-flex; display: inline-flex;
} }
.wf-optional-steps-dropdown-trigger {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 6px;
padding: 4px 8px;
font-size: 0.8rem;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 6px);
background: var(--surface, transparent);
color: inherit;
cursor: pointer;
}
.wf-optional-steps-dropdown-trigger:disabled { .wf-optional-steps-dropdown-trigger:disabled {
opacity: 0.5; opacity: 0.5;
cursor: default; cursor: default;

View File

@@ -16,6 +16,11 @@
* matching the quick-add card's prior no-chip-block behavior and the modal's * matching the quick-add card's prior no-chip-block behavior and the modal's
* empty-state choice, so both surfaces look identical. * empty-state choice, so both surfaces look identical.
* *
* FNXC:TaskCreationButtons 2026-06-25-00:00:
* The optional-steps trigger must reuse shared `.btn .btn-sm` styling so it has
* the same padding, background, border, radius, and interaction affordances as
* the quick-add and New Task action buttons on every creation surface.
*
* Accessibility: trigger has aria-haspopup/aria-expanded; the panel is a * Accessibility: trigger has aria-haspopup/aria-expanded; the panel is a
* role="listbox" labelled by the trigger; each option is a role="option" with * role="listbox" labelled by the trigger; each option is a role="option" with
* aria-checked. Escape closes and refocuses the trigger; arrow keys move the * aria-checked. Escape closes and refocuses the trigger; arrow keys move the
@@ -145,7 +150,7 @@ export function WorkflowOptionalStepsDropdown({
ref={triggerRef} ref={triggerRef}
type="button" type="button"
id={labelId} id={labelId}
className="wf-optional-steps-dropdown-trigger" className="btn btn-sm wf-optional-steps-dropdown-trigger"
data-testid={triggerTestId} data-testid={triggerTestId}
aria-haspopup="listbox" aria-haspopup="listbox"
aria-expanded={isOpen} aria-expanded={isOpen}
@@ -157,7 +162,7 @@ export function WorkflowOptionalStepsDropdown({
onKeyDown={onTriggerKeyDown} onKeyDown={onTriggerKeyDown}
> >
<span>{triggerLabel}</span> <span>{triggerLabel}</span>
<ChevronDown size={13} aria-hidden /> <ChevronDown size={12} aria-hidden />
</button> </button>
{isOpen && {isOpen &&

View File

@@ -13,8 +13,27 @@ const STEP: ResolvedWorkflowOptionalStep = {
defaultOn: false, defaultOn: false,
}; };
const STEP_TWO: ResolvedWorkflowOptionalStep = {
templateId: "test-review",
name: "Test Review",
description: "Review test coverage",
icon: "check-circle",
phase: "post-implementation",
defaultOn: false,
};
// Controlled host: parent owns the enabled set, mirroring the create surfaces. // Controlled host: parent owns the enabled set, mirroring the create surfaces.
function Host({ steps, initial = [] }: { steps: ResolvedWorkflowOptionalStep[]; initial?: string[] }) { function Host({
steps,
initial = [],
disabled = false,
triggerTestId,
}: {
steps: ResolvedWorkflowOptionalStep[];
initial?: string[];
disabled?: boolean;
triggerTestId?: string;
}) {
const [enabled, setEnabled] = useState<string[]>(initial); const [enabled, setEnabled] = useState<string[]>(initial);
return ( return (
<WorkflowOptionalStepsDropdown <WorkflowOptionalStepsDropdown
@@ -23,10 +42,18 @@ function Host({ steps, initial = [] }: { steps: ResolvedWorkflowOptionalStep[];
onToggle={(id) => onToggle={(id) =>
setEnabled((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])) setEnabled((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]))
} }
disabled={disabled}
triggerTestId={triggerTestId}
/> />
); );
} }
function expectSharedButtonTrigger(trigger: HTMLElement) {
expect(trigger).toHaveClass("btn", "btn-sm", "wf-optional-steps-dropdown-trigger");
expect(trigger).toHaveAttribute("aria-haspopup", "listbox");
expect(trigger).toHaveAttribute("aria-expanded");
}
afterEach(() => { afterEach(() => {
cleanup(); cleanup();
vi.clearAllMocks(); vi.clearAllMocks();
@@ -38,9 +65,26 @@ describe("WorkflowOptionalStepsDropdown", () => {
expect(container.firstChild).toBeNull(); expect(container.firstChild).toBeNull();
}); });
it("reflects the selected count in the trigger label", () => { it("uses shared button classes and preserves trigger attributes when none are selected", () => {
render(<Host steps={[STEP]} />); render(<Host steps={[STEP]} triggerTestId="custom-optional-steps-trigger" />);
const trigger = screen.getByTestId("custom-optional-steps-trigger");
expectSharedButtonTrigger(trigger);
expect(trigger).toHaveTextContent("Steps: none");
expect(trigger).toHaveAttribute("aria-expanded", "false");
});
it("uses shared button classes and count label when multiple steps are selected", () => {
render(<Host steps={[STEP, STEP_TWO]} initial={["browser-verification", "test-review"]} />);
const trigger = screen.getByTestId("wf-optional-steps-dropdown-trigger"); const trigger = screen.getByTestId("wf-optional-steps-dropdown-trigger");
expectSharedButtonTrigger(trigger);
expect(trigger).toHaveTextContent("Steps: 2 selected");
});
it("uses shared button classes and disabled semantics when submitting", () => {
render(<Host steps={[STEP]} disabled />);
const trigger = screen.getByTestId("wf-optional-steps-dropdown-trigger");
expectSharedButtonTrigger(trigger);
expect(trigger).toBeDisabled();
expect(trigger).toHaveTextContent("Steps: none"); expect(trigger).toHaveTextContent("Steps: none");
}); });