diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx
index 8961fc7f5d..b76601ea9e 100644
--- a/webview-ui/src/components/chat/AutoApproveMenu.tsx
+++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx
@@ -1,14 +1,18 @@
import { memo, useCallback, useMemo, useState } from "react"
import { Trans } from "react-i18next"
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import { Stamp, ListChecks, LayoutList } from "lucide-react"
import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useAppTranslation } from "@src/i18n/TranslationContext"
-import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
-import { StandardTooltip } from "@src/components/ui"
+import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
+import { AutoApproveToggleDropdown } from "./AutoApproveToggleDropdown"
+import { StandardTooltip, Popover, PopoverContent, PopoverTrigger } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
+import { cn } from "@src/lib/utils"
+import { useRooPortal } from "@src/components/ui/hooks/useRooPortal"
interface AutoApproveMenuProps {
style?: React.CSSProperties
@@ -16,6 +20,7 @@ interface AutoApproveMenuProps {
const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const [isExpanded, setIsExpanded] = useState(false)
+ const portalContainer = useRooPortal("roo-portal")
const {
autoApprovalEnabled,
@@ -123,10 +128,6 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
],
)
- const toggleExpanded = useCallback(() => {
- setIsExpanded((prev) => !prev)
- }, [])
-
const enabledActionsList = Object.entries(toggles)
.filter(([_key, value]) => !!value)
.map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey))
@@ -146,101 +147,118 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
[],
)
+ // Handler for Select All
+ const handleSelectAll = useCallback(() => {
+ const allSettings: AutoApproveSetting[] = Object.keys(toggles) as AutoApproveSetting[]
+ allSettings.forEach((key) => {
+ if (!toggles[key]) {
+ onAutoApproveToggle(key, true)
+ }
+ })
+ }, [toggles, onAutoApproveToggle])
+
+ // Handler for Select None
+ const handleSelectNone = useCallback(() => {
+ const allSettings: AutoApproveSetting[] = Object.keys(toggles) as AutoApproveSetting[]
+ allSettings.forEach((key) => {
+ if (toggles[key]) {
+ onAutoApproveToggle(key, false)
+ }
+ })
+ }, [toggles, onAutoApproveToggle])
+
+ const trigger = (
+
+
+ {t("chat:autoApprove.title")}
+ {displayText}
+
+ )
+
return (
-
- {isExpanded && (
-
-
-
,
- }}
- />
+
+ {trigger}
+
+
+
+ {/* Header with master toggle */}
+
+
+
+ {
+ if (hasEnabledOptions) {
+ const newValue = !(autoApprovalEnabled ?? false)
+ setAutoApprovalEnabled(newValue)
+ vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
+ }
+ }}
+ />
+
+
{t("chat:autoApprove.title")}
+
+
+
+
+
+
+
+
+
-
-
- )}
+ {/* Description */}
+
-
-
e.stopPropagation()}>
-
- {
- if (hasEnabledOptions) {
- const newValue = !(autoApprovalEnabled ?? false)
- setAutoApprovalEnabled(newValue)
- vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
- }
- // If no options enabled, do nothing
- }}
- />
-
+ {/* Two-column layout for toggles */}
+
-
-
- {t("chat:autoApprove.title")}
-
-
- {displayText}
-
-
-
-
-
+
+
)
}
diff --git a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx
new file mode 100644
index 0000000000..279194593a
--- /dev/null
+++ b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx
@@ -0,0 +1,73 @@
+import type { GlobalSettings } from "@roo-code/types"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+import { cn } from "@/lib/utils"
+import { StandardTooltip } from "@/components/ui"
+import { autoApproveSettingsConfig, AutoApproveSetting } from "../settings/AutoApproveToggle"
+
+type AutoApproveToggles = Pick<
+ GlobalSettings,
+ | "alwaysAllowReadOnly"
+ | "alwaysAllowWrite"
+ | "alwaysAllowBrowser"
+ | "alwaysApproveResubmit"
+ | "alwaysAllowMcp"
+ | "alwaysAllowModeSwitch"
+ | "alwaysAllowSubtasks"
+ | "alwaysAllowExecute"
+ | "alwaysAllowFollowupQuestions"
+ | "alwaysAllowUpdateTodoList"
+>
+
+type AutoApproveToggleDropdownProps = AutoApproveToggles & {
+ onToggle: (key: AutoApproveSetting, value: boolean) => void
+}
+
+export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveToggleDropdownProps) => {
+ const { t } = useAppTranslation()
+
+ // Split settings into two columns for better layout
+ const settings = Object.values(autoApproveSettingsConfig)
+ const halfLength = Math.ceil(settings.length / 2)
+ const leftColumn = settings.slice(0, halfLength)
+ const rightColumn = settings.slice(halfLength)
+
+ const renderToggleItem = ({
+ key,
+ descriptionKey,
+ labelKey,
+ icon,
+ testId,
+ }: (typeof autoApproveSettingsConfig)[AutoApproveSetting]) => (
+
+
+
+ )
+
+ return (
+ <>
+
{leftColumn.map(renderToggleItem)}
+
{rightColumn.map(renderToggleItem)}
+ >
+ )
+}
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 44eeb33b66..3cd10138e8 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -1882,11 +1882,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction
-
-
- )}
{task && (
<>
@@ -1909,9 +1904,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction
-
{areButtonsVisible && (
+ {/* Auto-Approve Menu positioned below text area */}
+
+
{isProfileDisabled && (
diff --git a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx
index 185e5eeec6..0c185fec4d 100644
--- a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx
@@ -115,7 +115,7 @@ describe("AutoApproveMenu", () => {
expect(screen.getByText("Read-only operations")).toBeInTheDocument()
})
- it("should not allow toggling master checkbox when no options are selected", () => {
+ it("should not allow toggling master checkbox when no options are selected", async () => {
;(useExtensionState as ReturnType
).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: false,
@@ -124,6 +124,15 @@ describe("AutoApproveMenu", () => {
render()
+ // Click to open the dropdown
+ const trigger = screen.getByText("Auto-approve")
+ fireEvent.click(trigger)
+
+ // Wait for the dropdown to open
+ await waitFor(() => {
+ expect(screen.getByRole("checkbox")).toBeInTheDocument()
+ })
+
// Click on the master checkbox
const masterCheckbox = screen.getByRole("checkbox")
fireEvent.click(masterCheckbox)
@@ -132,7 +141,7 @@ describe("AutoApproveMenu", () => {
expect(mockPostMessage).not.toHaveBeenCalled()
})
- it("should toggle master checkbox when options are selected", () => {
+ it("should toggle master checkbox when options are selected", async () => {
;(useExtensionState as ReturnType).mockReturnValue({
...defaultExtensionState,
autoApprovalEnabled: true,
@@ -141,6 +150,15 @@ describe("AutoApproveMenu", () => {
render()
+ // Click to open the dropdown
+ const trigger = screen.getByText("Auto-approve")
+ fireEvent.click(trigger)
+
+ // Wait for the dropdown to open
+ await waitFor(() => {
+ expect(screen.getByRole("checkbox")).toBeInTheDocument()
+ })
+
// Click on the master checkbox
const masterCheckbox = screen.getByRole("checkbox")
fireEvent.click(masterCheckbox)
@@ -164,9 +182,9 @@ describe("AutoApproveMenu", () => {
render()
- // Expand the menu
- const menuContainer = screen.getByText("Auto-approve").parentElement
- fireEvent.click(menuContainer!)
+ // Click to open the dropdown
+ const trigger = screen.getByText("Auto-approve")
+ fireEvent.click(trigger)
// Wait for the menu to expand and find the read-only button
await waitFor(() => {
@@ -192,9 +210,9 @@ describe("AutoApproveMenu", () => {
render()
- // Expand the menu
- const menuContainer = screen.getByText("Auto-approve").parentElement
- fireEvent.click(menuContainer!)
+ // Click to open the dropdown
+ const trigger = screen.getByText("Auto-approve")
+ fireEvent.click(trigger)
await waitFor(() => {
expect(screen.getByTestId("always-allow-write-toggle")).toBeInTheDocument()
@@ -240,9 +258,9 @@ describe("AutoApproveMenu", () => {
render()
- // Expand the menu
- const menuContainer = screen.getByText("Auto-approve").parentElement
- fireEvent.click(menuContainer!)
+ // Click to open the dropdown
+ const trigger = screen.getByText("Auto-approve")
+ fireEvent.click(trigger)
await waitFor(() => {
expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument()
@@ -279,9 +297,9 @@ describe("AutoApproveMenu", () => {
render()
- // Expand the menu
- const menuContainer = screen.getByText("Auto-approve").parentElement
- fireEvent.click(menuContainer!)
+ // Click to open the dropdown
+ const trigger = screen.getByText("Auto-approve")
+ fireEvent.click(trigger)
await waitFor(() => {
expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument()