Просмотр исходного кода

Merge remote-tracking branch 'origin/main' into feat/context-proxy

Matt Rubens 10 месяцев назад
Родитель
Сommit
6b487d6a49

+ 15 - 14
webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx

@@ -115,6 +115,21 @@ describe("SelectDropdown", () => {
 		expect(trigger.classList.toString()).toContain("custom-trigger-class")
 		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(<SelectDropdown value="option1" options={options} onChange={onChangeMock} />)
+
+		// 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
 	// Tests for the new functionality
 	describe("Option types", () => {
 	describe("Option types", () => {
 		it("renders separator options correctly", () => {
 		it("renders separator options correctly", () => {
@@ -131,20 +146,6 @@ describe("SelectDropdown", () => {
 			expect(separators.length).toBe(1)
 			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(<SelectDropdown value="option1" options={optionsWithStringSeparator} onChange={onChangeMock} />)
-
-			// Check for separator
-			const separators = screen.getAllByTestId("dropdown-separator")
-			expect(separators.length).toBe(1)
-		})
-
 		it("renders shortcut options correctly", () => {
 		it("renders shortcut options correctly", () => {
 			const shortcutText = "Ctrl+K"
 			const shortcutText = "Ctrl+K"
 			const optionsWithShortcut = [
 			const optionsWithShortcut = [

+ 21 - 2
webview-ui/src/components/ui/select-dropdown.tsx

@@ -7,6 +7,7 @@ import {
 	DropdownMenuSeparator,
 	DropdownMenuSeparator,
 } from "./dropdown-menu"
 } from "./dropdown-menu"
 import { cn } from "@/lib/utils"
 import { cn } from "@/lib/utils"
+import { useEffect, useState } from "react"
 
 
 // Constants for option types
 // Constants for option types
 export enum DropdownOptionType {
 export enum DropdownOptionType {
@@ -57,6 +58,19 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
 		},
 		},
 		ref,
 		ref,
 	) => {
 	) => {
+		// Track open state
+		const [open, setOpen] = React.useState(false)
+		const [portalContainer, setPortalContainer] = useState<HTMLElement>()
+
+		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
 		// Find the selected option label
 		const selectedOption = options.find((option) => option.value === value)
 		const selectedOption = options.find((option) => option.value === value)
 		const displayText = selectedOption?.label || placeholder || ""
 		const displayText = selectedOption?.label || placeholder || ""
@@ -69,13 +83,15 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
 					type: "action",
 					type: "action",
 					action: option.value,
 					action: option.value,
 				})
 				})
+				setOpen(false)
 				return
 				return
 			}
 			}
 			onChange(option.value)
 			onChange(option.value)
+			setOpen(false)
 		}
 		}
 
 
 		return (
 		return (
-			<DropdownMenu>
+			<DropdownMenu open={open} onOpenChange={setOpen}>
 				<DropdownMenuTrigger
 				<DropdownMenuTrigger
 					ref={ref}
 					ref={ref}
 					disabled={disabled}
 					disabled={disabled}
@@ -112,13 +128,16 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
 				<DropdownMenuContent
 				<DropdownMenuContent
 					align={align}
 					align={align}
 					sideOffset={sideOffset}
 					sideOffset={sideOffset}
+					onEscapeKeyDown={() => setOpen(false)}
+					onInteractOutside={() => setOpen(false)}
+					container={portalContainer}
 					className={cn(
 					className={cn(
 						"bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50",
 						"bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50",
 						contentClassName,
 						contentClassName,
 					)}>
 					)}>
 					{options.map((option, index) => {
 					{options.map((option, index) => {
 						// Handle separator type
 						// Handle separator type
-						if (option.type === DropdownOptionType.SEPARATOR || option.label.includes("────")) {
+						if (option.type === DropdownOptionType.SEPARATOR) {
 							return <DropdownMenuSeparator key={`sep-${index}`} />
 							return <DropdownMenuSeparator key={`sep-${index}`} />
 						}
 						}