diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx
index 99dda495d0..389f5709fc 100644
--- a/webview-ui/src/App.tsx
+++ b/webview-ui/src/App.tsx
@@ -132,7 +132,6 @@ const App = () => {
const AppWithProviders = () => (
-
)
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index 3df3e87e9b..1002788dbc 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -1,22 +1,25 @@
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
import DynamicTextArea from "react-textarea-autosize"
+
import { mentionRegex, mentionRegexGlobal } from "../../../../src/shared/context-mentions"
-import { useExtensionState } from "../../context/ExtensionStateContext"
+import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
+import { Mode, getAllModes } from "../../../../src/shared/modes"
+
+import { vscode } from "@/utils/vscode"
import {
ContextMenuOptionType,
getContextMenuOptions,
insertMention,
removeMention,
shouldShowContextMenu,
-} from "../../utils/context-mentions"
+} from "@/utils/context-mentions"
+import { SelectDropdown, DropdownOptionType } from "@/components/ui"
+
+import { useExtensionState } from "../../context/ExtensionStateContext"
+import Thumbnails from "../common/Thumbnails"
+import { convertToMentionPath } from "../../utils/path-mentions"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
-import Thumbnails from "../common/Thumbnails"
-import { vscode } from "../../utils/vscode"
-import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
-import { Mode, getAllModes } from "../../../../src/shared/modes"
-import { convertToMentionPath } from "../../utils/path-mentions"
-import { SelectDropdown, DropdownOptionType } from "../ui"
interface ChatTextAreaProps {
inputValue: string
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 5ac7f50559..09dcdd3ca1 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -1275,7 +1275,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
modeShortcutText={modeShortcutText}
/>
-
+
)
}
diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
index 63867c9858..b6aaebd518 100644
--- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
+++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
@@ -2,6 +2,7 @@ import { useState, useCallback } from "react"
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui"
+import { useRooPortal } from "@/components/ui/hooks"
import { vscode } from "../../../utils/vscode"
import { Checkpoint } from "./schema"
@@ -16,6 +17,7 @@ type CheckpointMenuProps = {
export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => {
const [isOpen, setIsOpen] = useState(false)
const [isConfirming, setIsConfirming] = useState(false)
+ const portalContainer = useRooPortal("roo-portal")
const isCurrent = currentHash === commitHash
const isFirst = checkpoint.isFirst
@@ -60,7 +62,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
-
+
{!isCurrent && (
diff --git a/webview-ui/src/components/ui/dropdown-menu.tsx b/webview-ui/src/components/ui/dropdown-menu.tsx
index 3193f497ca..c65f18b1b5 100644
--- a/webview-ui/src/components/ui/dropdown-menu.tsx
+++ b/webview-ui/src/components/ui/dropdown-menu.tsx
@@ -1,5 +1,6 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { PortalProps } from "@radix-ui/react-portal"
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
@@ -53,25 +54,21 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam
const DropdownMenuContent = React.forwardRef<
React.ElementRef
,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => {
- const container = React.useMemo(() => document.getElementById("roo-portal"), [])
-
- return (
-
-
-
- )
-})
+ React.ComponentPropsWithoutRef & Pick
+>(({ className, sideOffset = 4, container, ...props }, ref) => (
+
+
+
+))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
diff --git a/webview-ui/src/components/ui/hooks/index.ts b/webview-ui/src/components/ui/hooks/index.ts
index 0ca9075f59..46aff4f28d 100644
--- a/webview-ui/src/components/ui/hooks/index.ts
+++ b/webview-ui/src/components/ui/hooks/index.ts
@@ -1 +1,2 @@
export * from "./useClipboard"
+export * from "./useRooPortal"
diff --git a/webview-ui/src/components/ui/hooks/useRooPortal.ts b/webview-ui/src/components/ui/hooks/useRooPortal.ts
new file mode 100644
index 0000000000..25ef139e64
--- /dev/null
+++ b/webview-ui/src/components/ui/hooks/useRooPortal.ts
@@ -0,0 +1,10 @@
+import { useState } from "react"
+import { useMount } from "react-use"
+
+export const useRooPortal = (id: string) => {
+ const [container, setContainer] = useState()
+
+ useMount(() => setContainer(document.getElementById(id) ?? undefined))
+
+ return container
+}
diff --git a/webview-ui/src/components/ui/popover.tsx b/webview-ui/src/components/ui/popover.tsx
index b6235853ca..9fc035ad2a 100644
--- a/webview-ui/src/components/ui/popover.tsx
+++ b/webview-ui/src/components/ui/popover.tsx
@@ -1,4 +1,5 @@
import * as React from "react"
+import { PortalProps } from "@radix-ui/react-portal"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
@@ -11,25 +12,21 @@ const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => {
- const container = React.useMemo(() => document.getElementById("roo-portal"), [])
-
- return (
-
-
-
- )
-})
+ React.ComponentPropsWithoutRef & Pick
+>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
+
+
+
+))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx
index eef474cd02..bec496ed50 100644
--- a/webview-ui/src/components/ui/select-dropdown.tsx
+++ b/webview-ui/src/components/ui/select-dropdown.tsx
@@ -1,4 +1,8 @@
import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+import { useRooPortal } from "./hooks/useRooPortal"
import {
DropdownMenu,
DropdownMenuContent,
@@ -6,9 +10,7 @@ import {
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "./dropdown-menu"
-import { cn } from "@/lib/utils"
-// Constants for option types
export enum DropdownOptionType {
ITEM = "item",
SEPARATOR = "separator",
@@ -19,7 +21,7 @@ export interface DropdownOption {
value: string
label: string
disabled?: boolean
- type?: DropdownOptionType // Optional type to specify special behaviors
+ type?: DropdownOptionType
}
export interface SelectDropdownProps {
@@ -38,8 +40,6 @@ export interface SelectDropdownProps {
shortcutText?: string
}
-// TODO: Get rid of this and use the native @shadcn/ui `Select` component.
-
export const SelectDropdown = React.forwardRef, SelectDropdownProps>(
(
{
@@ -59,24 +59,19 @@ export const SelectDropdown = React.forwardRef {
- // Track open state
const [open, setOpen] = React.useState(false)
+ const portalContainer = useRooPortal("roo-portal")
- // Find the selected option label
const selectedOption = options.find((option) => option.value === value)
const displayText = selectedOption?.label || placeholder || ""
- // Handle menu item click
const handleSelect = (option: DropdownOption) => {
- // Check if this is an action option by its explicit type
if (option.type === DropdownOptionType.ACTION) {
- window.postMessage({
- type: "action",
- action: option.value,
- })
+ window.postMessage({ type: "action", action: option.value })
setOpen(false)
return
}
+
onChange(option.value)
setOpen(false)
}
@@ -94,7 +89,7 @@ export const SelectDropdown = React.forwardRef
@@ -121,17 +116,16 @@ 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) {
return
}
- // Handle shortcut text type (disabled label for keyboard shortcuts)
if (
option.type === DropdownOptionType.SHORTCUT ||
(option.disabled && shortcutText && option.label.includes(shortcutText))
@@ -143,7 +137,6 @@ export const SelectDropdown = React.forwardRef) {
- const container = React.useMemo(() => document.getElementById("roo-portal"), [])
-
+}: React.ComponentProps & Pick) {
return (