Skip to content

Commit 9fdc546

Browse files
committed
Fix portal stuff
1 parent 5f20fbb commit 9fdc546

File tree

10 files changed

+71
-69
lines changed

10 files changed

+71
-69
lines changed

webview-ui/src/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ const App = () => {
132132
const AppWithProviders = () => (
133133
<ExtensionStateContextProvider>
134134
<App />
135-
<div id="roo-portal" />
136135
</ExtensionStateContextProvider>
137136
)
138137

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
22
import DynamicTextArea from "react-textarea-autosize"
3+
34
import { mentionRegex, mentionRegexGlobal } from "../../../../src/shared/context-mentions"
4-
import { useExtensionState } from "../../context/ExtensionStateContext"
5+
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
6+
import { Mode, getAllModes } from "../../../../src/shared/modes"
7+
8+
import { vscode } from "@/utils/vscode"
59
import {
610
ContextMenuOptionType,
711
getContextMenuOptions,
812
insertMention,
913
removeMention,
1014
shouldShowContextMenu,
11-
} from "../../utils/context-mentions"
12-
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
13-
import ContextMenu from "./ContextMenu"
15+
} from "@/utils/context-mentions"
16+
import { SelectDropdown, DropdownOptionType } from "@/components/ui"
17+
18+
import { useExtensionState } from "../../context/ExtensionStateContext"
1419
import Thumbnails from "../common/Thumbnails"
15-
import { vscode } from "../../utils/vscode"
16-
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
17-
import { Mode, getAllModes } from "../../../../src/shared/modes"
1820
import { convertToMentionPath } from "../../utils/path-mentions"
19-
import { SelectDropdown, DropdownOptionType } from "../ui"
21+
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
22+
import ContextMenu from "./ContextMenu"
2023

2124
interface ChatTextAreaProps {
2225
inputValue: string

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
12751275
modeShortcutText={modeShortcutText}
12761276
/>
12771277

1278-
<div id="chat-view-portal" />
1278+
<div id="roo-portal" />
12791279
</div>
12801280
)
12811281
}

webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useCallback } from "react"
22
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
33

44
import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui"
5+
import { useRooPortal } from "@/components/ui/hooks"
56

67
import { vscode } from "../../../utils/vscode"
78
import { Checkpoint } from "./schema"
@@ -16,6 +17,7 @@ type CheckpointMenuProps = {
1617
export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => {
1718
const [isOpen, setIsOpen] = useState(false)
1819
const [isConfirming, setIsConfirming] = useState(false)
20+
const portalContainer = useRooPortal("roo-portal")
1921

2022
const isCurrent = currentHash === commitHash
2123
const isFirst = checkpoint.isFirst
@@ -60,7 +62,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
6062
<span className="codicon codicon-history" />
6163
</Button>
6264
</PopoverTrigger>
63-
<PopoverContent align="end">
65+
<PopoverContent align="end" container={portalContainer}>
6466
<div className="flex flex-col gap-2">
6567
{!isCurrent && (
6668
<div className="flex flex-col gap-1 group hover:text-foreground">

webview-ui/src/components/ui/dropdown-menu.tsx

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from "react"
22
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3+
import { PortalProps } from "@radix-ui/react-portal"
34
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
45

56
import { cn } from "@/lib/utils"
@@ -53,25 +54,21 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam
5354

5455
const DropdownMenuContent = React.forwardRef<
5556
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
56-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
57-
>(({ className, sideOffset = 4, ...props }, ref) => {
58-
const container = React.useMemo(() => document.getElementById("roo-portal"), [])
59-
60-
return (
61-
<DropdownMenuPrimitive.Portal container={container}>
62-
<DropdownMenuPrimitive.Content
63-
ref={ref}
64-
sideOffset={sideOffset}
65-
className={cn(
66-
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
67-
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
68-
className,
69-
)}
70-
{...props}
71-
/>
72-
</DropdownMenuPrimitive.Portal>
73-
)
74-
})
57+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & Pick<PortalProps, "container">
58+
>(({ className, sideOffset = 4, container, ...props }, ref) => (
59+
<DropdownMenuPrimitive.Portal container={container}>
60+
<DropdownMenuPrimitive.Content
61+
ref={ref}
62+
sideOffset={sideOffset}
63+
className={cn(
64+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
65+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
66+
className,
67+
)}
68+
{...props}
69+
/>
70+
</DropdownMenuPrimitive.Portal>
71+
))
7572
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
7673

7774
const DropdownMenuItem = React.forwardRef<
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./useClipboard"
2+
export * from "./useRooPortal"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useState } from "react"
2+
import { useMount } from "react-use"
3+
4+
export const useRooPortal = (id: string) => {
5+
const [container, setContainer] = useState<HTMLElement>()
6+
7+
useMount(() => setContainer(document.getElementById(id) ?? undefined))
8+
9+
return container
10+
}
Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from "react"
2+
import { PortalProps } from "@radix-ui/react-portal"
23
import * as PopoverPrimitive from "@radix-ui/react-popover"
34

45
import { cn } from "@/lib/utils"
@@ -11,25 +12,21 @@ const PopoverAnchor = PopoverPrimitive.Anchor
1112

1213
const PopoverContent = React.forwardRef<
1314
React.ElementRef<typeof PopoverPrimitive.Content>,
14-
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15-
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => {
16-
const container = React.useMemo(() => document.getElementById("roo-portal"), [])
17-
18-
return (
19-
<PopoverPrimitive.Portal container={container}>
20-
<PopoverPrimitive.Content
21-
ref={ref}
22-
align={align}
23-
sideOffset={sideOffset}
24-
className={cn(
25-
"z-50 w-72 rounded-xs border border-vscode-dropdown-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
26-
className,
27-
)}
28-
{...props}
29-
/>
30-
</PopoverPrimitive.Portal>
31-
)
32-
})
15+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & Pick<PortalProps, "container">
16+
>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
17+
<PopoverPrimitive.Portal container={container}>
18+
<PopoverPrimitive.Content
19+
ref={ref}
20+
align={align}
21+
sideOffset={sideOffset}
22+
className={cn(
23+
"z-50 w-72 rounded-xs border border-vscode-dropdown-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
24+
className,
25+
)}
26+
{...props}
27+
/>
28+
</PopoverPrimitive.Portal>
29+
))
3330
PopoverContent.displayName = PopoverPrimitive.Content.displayName
3431

3532
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

webview-ui/src/components/ui/select-dropdown.tsx

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import * as React from "react"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
import { useRooPortal } from "./hooks/useRooPortal"
26
import {
37
DropdownMenu,
48
DropdownMenuContent,
59
DropdownMenuItem,
610
DropdownMenuTrigger,
711
DropdownMenuSeparator,
812
} from "./dropdown-menu"
9-
import { cn } from "@/lib/utils"
1013

11-
// Constants for option types
1214
export enum DropdownOptionType {
1315
ITEM = "item",
1416
SEPARATOR = "separator",
@@ -19,7 +21,7 @@ export interface DropdownOption {
1921
value: string
2022
label: string
2123
disabled?: boolean
22-
type?: DropdownOptionType // Optional type to specify special behaviors
24+
type?: DropdownOptionType
2325
}
2426

2527
export interface SelectDropdownProps {
@@ -38,8 +40,6 @@ export interface SelectDropdownProps {
3840
shortcutText?: string
3941
}
4042

41-
// TODO: Get rid of this and use the native @shadcn/ui `Select` component.
42-
4343
export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownMenuTrigger>, SelectDropdownProps>(
4444
(
4545
{
@@ -59,24 +59,19 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
5959
},
6060
ref,
6161
) => {
62-
// Track open state
6362
const [open, setOpen] = React.useState(false)
63+
const portalContainer = useRooPortal("roo-portal")
6464

65-
// Find the selected option label
6665
const selectedOption = options.find((option) => option.value === value)
6766
const displayText = selectedOption?.label || placeholder || ""
6867

69-
// Handle menu item click
7068
const handleSelect = (option: DropdownOption) => {
71-
// Check if this is an action option by its explicit type
7269
if (option.type === DropdownOptionType.ACTION) {
73-
window.postMessage({
74-
type: "action",
75-
action: option.value,
76-
})
70+
window.postMessage({ type: "action", action: option.value })
7771
setOpen(false)
7872
return
7973
}
74+
8075
onChange(option.value)
8176
setOpen(false)
8277
}
@@ -94,7 +89,7 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
9489
triggerClassName,
9590
)}
9691
style={{
97-
width: "100%", // Take full width of parent
92+
width: "100%", // Take full width of parent.
9893
minWidth: "0",
9994
maxWidth: "100%",
10095
}}>
@@ -121,17 +116,16 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
121116
sideOffset={sideOffset}
122117
onEscapeKeyDown={() => setOpen(false)}
123118
onInteractOutside={() => setOpen(false)}
119+
container={portalContainer}
124120
className={cn(
125121
"bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50",
126122
contentClassName,
127123
)}>
128124
{options.map((option, index) => {
129-
// Handle separator type
130125
if (option.type === DropdownOptionType.SEPARATOR) {
131126
return <DropdownMenuSeparator key={`sep-${index}`} />
132127
}
133128

134-
// Handle shortcut text type (disabled label for keyboard shortcuts)
135129
if (
136130
option.type === DropdownOptionType.SHORTCUT ||
137131
(option.disabled && shortcutText && option.label.includes(shortcutText))
@@ -143,7 +137,6 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
143137
)
144138
}
145139

146-
// Regular menu items
147140
return (
148141
<DropdownMenuItem
149142
key={`item-${option.value}`}

webview-ui/src/components/ui/select.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from "react"
2+
import { PortalProps } from "@radix-ui/react-portal"
23
import * as SelectPrimitive from "@radix-ui/react-select"
34
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
45

@@ -37,10 +38,9 @@ function SelectContent({
3738
className,
3839
children,
3940
position = "popper",
41+
container,
4042
...props
41-
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
42-
const container = React.useMemo(() => document.getElementById("roo-portal"), [])
43-
43+
}: React.ComponentProps<typeof SelectPrimitive.Content> & Pick<PortalProps, "container">) {
4444
return (
4545
<SelectPrimitive.Portal container={container}>
4646
<SelectPrimitive.Content

0 commit comments

Comments
 (0)