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

This commit is contained in:
Matt Rubens 2025-03-05 09:18:51 -05:00
commit 6b487d6a49
2 changed files with 36 additions and 16 deletions

View file

@ -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(<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
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(<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", () => {
const shortcutText = "Ctrl+K"
const optionsWithShortcut = [

View file

@ -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<React.ElementRef<typeof DropdownM
},
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
const selectedOption = options.find((option) => option.value === value)
const displayText = selectedOption?.label || placeholder || ""
@ -69,13 +83,15 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
type: "action",
action: option.value,
})
setOpen(false)
return
}
onChange(option.value)
setOpen(false)
}
return (
<DropdownMenu>
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger
ref={ref}
disabled={disabled}
@ -112,13 +128,16 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
<DropdownMenuContent
align={align}
sideOffset={sideOffset}
onEscapeKeyDown={() => 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 <DropdownMenuSeparator key={`sep-${index}`} />
}