diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx index 9543014ff1..3e39dd9d6b 100644 --- a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx @@ -127,4 +127,36 @@ describe("DropdownWrapper Component", () => { expect(screen.getByText(buttonText)).toBeInTheDocument(); expect(screen.queryByText(/option 2/i)).not.toBeInTheDocument(); }); + + // Regression test for #45853 — react-select tracks the last click as + // selectValue and re-focuses it on reopen, leaving the previously-clicked + // option visually highlighted. Button variant overrides this by forcing + // value to null. + test("button variant does not mark a previously-clicked option as selected on reopen", async () => { + const buttonText = "Actions"; + render( + + ); + + // Open, click Option 2, then reopen + await userEvent.click(screen.getByText(buttonText)); + await userEvent.click(screen.getByText(/option 2/i)); + await userEvent.click(screen.getByText(buttonText)); + + // On reopen, no menu option should carry the selected state. + // react-select adds `--is-selected` to the option matching `value`; the + // fix forces value to null for button variant so this class never appears. + const options = document.querySelectorAll(".react-select__option"); + expect(options.length).toBeGreaterThan(0); + + options.forEach((option) => + expect(option.className).not.toMatch(/--is-selected/) + ); + }); }); diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx index 70f2067f19..63c7ae6a83 100644 --- a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx @@ -201,16 +201,12 @@ export const generateCustomDropdownStyles = ( stroke: COLORS["ui-fleet-black-75-over"], }, }, - ...(state.isFocused && { - backgroundColor: COLORS["ui-fleet-black-5"], - boxShadow: "none", - ".dropdown-wrapper__placeholder": { - color: COLORS["ui-fleet-black-75-down"], - }, - ".dropdown-wrapper__indicator path": { - stroke: COLORS["ui-fleet-black-75-down"], - }, - }), + // Note: state.isFocused is intentionally not used as a highlight + // trigger here. react-select's internal input retains focus after + // the menu closes (outside click, post-modal-close, etc.), so + // styling on it would leave the trigger visually highlighted + // indefinitely (#45853). Hover + menuIsOpen cover the meaningful + // interactive states for an action button. ...(state.menuIsOpen && { backgroundColor: COLORS["ui-fleet-black-5"], ".dropdown-wrapper__placeholder": { @@ -459,6 +455,7 @@ const DropdownWrapper = ({ }: IDropdownWrapper) => { const wrapperClassNames = classnames(baseClass, className, { [`${baseClass}__table-filter`]: variant === "table-filter", + [`${baseClass}__button`]: variant === "button", [`${wrapperClassname}`]: !!wrapperClassname, }); @@ -477,6 +474,13 @@ const DropdownWrapper = ({ // Ability to handle value of type string or CustomOptionType const getCurrentValue = () => { + // Button variant is a fire-and-forget action menu (selection isn't displayed + // via `controlShouldRenderValue`). Forcing null prevents react-select from + // tracking the last click as the selected value and re-focusing it on reopen + // (#45853). + if (variant === "button") { + return null; + } if (typeof value === "string") { return options.find((option) => option.value === value) || null; } diff --git a/frontend/components/forms/fields/DropdownWrapper/_styles.scss b/frontend/components/forms/fields/DropdownWrapper/_styles.scss index ec7ada57b6..b730b7ce86 100644 --- a/frontend/components/forms/fields/DropdownWrapper/_styles.scss +++ b/frontend/components/forms/fields/DropdownWrapper/_styles.scss @@ -20,4 +20,14 @@ height: 36px; } } + + // Button-variant: background while focused so keyboard tabbing is + // visible. Mirrors the menuIsOpen background applied via emotion in + // DropdownWrapper.tsx. Inputs always match :focus-visible, so this + // also applies on click — that's fine since it matches the open state. + &__button { + .react-select__control:has(input:focus-visible) { + background-color: $ui-fleet-black-5; + } + } }