diff --git a/webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx b/webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx index 5d65eaae98..328933852f 100644 --- a/webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx +++ b/webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx @@ -115,6 +115,21 @@ describe("SelectDropdown", () => { expect(trigger.classList.toString()).toContain("custom-trigger-class") }) + it("ensures open state is controlled via props", () => { + // Test that the component accepts and uses the open state controlled prop + render() + + // The component should render the dropdown root with correct props + const dropdown = screen.getByTestId("dropdown-root") + expect(dropdown).toBeInTheDocument() + + // Verify trigger and content are rendered + const trigger = screen.getByTestId("dropdown-trigger") + const content = screen.getByTestId("dropdown-content") + expect(trigger).toBeInTheDocument() + expect(content).toBeInTheDocument() + }) + // Tests for the new functionality describe("Option types", () => { it("renders separator options correctly", () => { @@ -131,20 +146,6 @@ describe("SelectDropdown", () => { expect(separators.length).toBe(1) }) - it("renders string separator (backward compatibility) correctly", () => { - const optionsWithStringSeparator = [ - { value: "option1", label: "Option 1" }, - { value: "sep-1", label: "────", disabled: true }, - { value: "option2", label: "Option 2" }, - ] - - render() - - // Check for separator - const separators = screen.getAllByTestId("dropdown-separator") - expect(separators.length).toBe(1) - }) - it("renders shortcut options correctly", () => { const shortcutText = "Ctrl+K" const optionsWithShortcut = [ diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx index b134894d25..775066732d 100644 --- a/webview-ui/src/components/ui/select-dropdown.tsx +++ b/webview-ui/src/components/ui/select-dropdown.tsx @@ -7,6 +7,7 @@ import { DropdownMenuSeparator, } from "./dropdown-menu" import { cn } from "@/lib/utils" +import { useEffect, useState } from "react" // Constants for option types export enum DropdownOptionType { @@ -57,6 +58,19 @@ export const SelectDropdown = React.forwardRef { + // Track open state + const [open, setOpen] = React.useState(false) + const [portalContainer, setPortalContainer] = useState() + + useEffect(() => { + // The dropdown menu uses a portal from @shadcn/ui which by default renders + // at the document root. This causes the menu to remain visible even when + // the parent ChatView component is hidden (during settings/history view). + // By moving the portal inside ChatView, the menu will properly hide when + // its parent is hidden. + setPortalContainer(document.getElementById("chat-view-portal") || undefined) + }, []) + // Find the selected option label const selectedOption = options.find((option) => option.value === value) const displayText = selectedOption?.label || placeholder || "" @@ -69,13 +83,15 @@ export const SelectDropdown = React.forwardRef + setOpen(false)} + onInteractOutside={() => setOpen(false)} + container={portalContainer} className={cn( "bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50", contentClassName, )}> {options.map((option, index) => { // Handle separator type - if (option.type === DropdownOptionType.SEPARATOR || option.label.includes("────")) { + if (option.type === DropdownOptionType.SEPARATOR) { return }