From 9fdc546f0189dbd21999b18227cea289cab9089f Mon Sep 17 00:00:00 2001 From: cte Date: Sun, 9 Mar 2025 21:49:29 -0700 Subject: [PATCH] Fix portal stuff --- webview-ui/src/App.tsx | 1 - .../src/components/chat/ChatTextArea.tsx | 19 +++++----- webview-ui/src/components/chat/ChatView.tsx | 2 +- .../chat/checkpoints/CheckpointMenu.tsx | 4 ++- .../src/components/ui/dropdown-menu.tsx | 35 +++++++++---------- webview-ui/src/components/ui/hooks/index.ts | 1 + .../src/components/ui/hooks/useRooPortal.ts | 10 ++++++ webview-ui/src/components/ui/popover.tsx | 35 +++++++++---------- .../src/components/ui/select-dropdown.tsx | 27 ++++++-------- webview-ui/src/components/ui/select.tsx | 6 ++-- 10 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 webview-ui/src/components/ui/hooks/useRooPortal.ts 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 (