11import { memo , useCallback , useMemo , useState } from "react"
22import { Trans } from "react-i18next"
33import { VSCodeCheckbox , VSCodeLink } from "@vscode/webview-ui-toolkit/react"
4+ import { Stamp , ListChecks , LayoutList } from "lucide-react"
45
56import { vscode } from "@src/utils/vscode"
67import { useExtensionState } from "@src/context/ExtensionStateContext"
78import { useAppTranslation } from "@src/i18n/TranslationContext"
8- import { AutoApproveToggle , AutoApproveSetting , autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
9- import { StandardTooltip } from "@src/components/ui"
9+ import { AutoApproveSetting , autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
10+ import { AutoApproveToggleDropdown } from "./AutoApproveToggleDropdown"
11+ import { StandardTooltip , Popover , PopoverContent , PopoverTrigger } from "@src/components/ui"
1012import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
1113import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
14+ import { cn } from "@src/lib/utils"
15+ import { useRooPortal } from "@src/components/ui/hooks/useRooPortal"
1216
1317interface AutoApproveMenuProps {
1418 style ?: React . CSSProperties
1519}
1620
1721const AutoApproveMenu = ( { style } : AutoApproveMenuProps ) => {
1822 const [ isExpanded , setIsExpanded ] = useState ( false )
23+ const portalContainer = useRooPortal ( "roo-portal" )
1924
2025 const {
2126 autoApprovalEnabled,
@@ -123,10 +128,6 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
123128 ] ,
124129 )
125130
126- const toggleExpanded = useCallback ( ( ) => {
127- setIsExpanded ( ( prev ) => ! prev )
128- } , [ ] )
129-
130131 const enabledActionsList = Object . entries ( toggles )
131132 . filter ( ( [ _key , value ] ) => ! ! value )
132133 . map ( ( [ key ] ) => t ( autoApproveSettingsConfig [ key as AutoApproveSetting ] . labelKey ) )
@@ -146,101 +147,118 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
146147 [ ] ,
147148 )
148149
150+ // Handler for Select All
151+ const handleSelectAll = useCallback ( ( ) => {
152+ const allSettings : AutoApproveSetting [ ] = Object . keys ( toggles ) as AutoApproveSetting [ ]
153+ allSettings . forEach ( ( key ) => {
154+ if ( ! toggles [ key ] ) {
155+ onAutoApproveToggle ( key , true )
156+ }
157+ } )
158+ } , [ toggles , onAutoApproveToggle ] )
159+
160+ // Handler for Select None
161+ const handleSelectNone = useCallback ( ( ) => {
162+ const allSettings : AutoApproveSetting [ ] = Object . keys ( toggles ) as AutoApproveSetting [ ]
163+ allSettings . forEach ( ( key ) => {
164+ if ( toggles [ key ] ) {
165+ onAutoApproveToggle ( key , false )
166+ }
167+ } )
168+ } , [ toggles , onAutoApproveToggle ] )
169+
170+ const trigger = (
171+ < PopoverTrigger
172+ className = { cn (
173+ "inline-flex items-center gap-1.5 relative whitespace-nowrap px-2 py-1 text-xs" ,
174+ "bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground" ,
175+ "transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset" ,
176+ "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer" ,
177+ ) }
178+ style = { style } >
179+ < Stamp className = "size-3.5 opacity-80 flex-shrink-0" />
180+ < span className = "font-medium" > { t ( "chat:autoApprove.title" ) } </ span >
181+ < span className = "text-vscode-descriptionForeground truncate max-w-[200px]" > { displayText } </ span >
182+ </ PopoverTrigger >
183+ )
184+
149185 return (
150- < div
151- style = { {
152- padding : "0 15px" ,
153- userSelect : "none" ,
154- borderTop : isExpanded
155- ? `0.5px solid color-mix(in srgb, var(--vscode-titleBar-inactiveForeground) 20%, transparent)`
156- : "none" ,
157- overflowY : "auto" ,
158- ...style ,
159- } } >
160- { isExpanded && (
161- < div className = "flex flex-col gap-2 py-4" >
162- < div
163- style = { {
164- color : "var(--vscode-descriptionForeground)" ,
165- fontSize : "12px" ,
166- } } >
167- < Trans
168- i18nKey = "chat:autoApprove.description"
169- components = { {
170- settingsLink : < VSCodeLink href = "#" onClick = { handleOpenSettings } /> ,
171- } }
172- />
186+ < Popover open = { isExpanded } onOpenChange = { setIsExpanded } >
187+ < StandardTooltip content = { t ( "chat:autoApprove.tooltip" ) } > { trigger } </ StandardTooltip >
188+
189+ < PopoverContent
190+ align = "start"
191+ sideOffset = { 4 }
192+ container = { portalContainer }
193+ className = "p-0 overflow-hidden min-w-[400px] max-w-[500px]" >
194+ < div className = "flex flex-col w-full" >
195+ { /* Header with master toggle */ }
196+ < div className = "flex items-center justify-between p-3 border-b border-vscode-dropdown-border" >
197+ < div className = "flex items-center gap-2" >
198+ < StandardTooltip
199+ content = { ! hasEnabledOptions ? t ( "chat:autoApprove.selectOptionsFirst" ) : undefined } >
200+ < VSCodeCheckbox
201+ checked = { effectiveAutoApprovalEnabled }
202+ disabled = { ! hasEnabledOptions }
203+ aria-label = {
204+ hasEnabledOptions
205+ ? t ( "chat:autoApprove.toggleAriaLabel" )
206+ : t ( "chat:autoApprove.disabledAriaLabel" )
207+ }
208+ onChange = { ( ) => {
209+ if ( hasEnabledOptions ) {
210+ const newValue = ! ( autoApprovalEnabled ?? false )
211+ setAutoApprovalEnabled ( newValue )
212+ vscode . postMessage ( { type : "autoApprovalEnabled" , bool : newValue } )
213+ }
214+ } }
215+ />
216+ </ StandardTooltip >
217+ < h4 className = "m-0 font-medium text-sm" > { t ( "chat:autoApprove.title" ) } </ h4 >
218+ </ div >
219+ < div className = "flex items-center gap-1" >
220+ < StandardTooltip content = { t ( "chat:autoApprove.selectAll" ) } >
221+ < button
222+ onClick = { handleSelectAll }
223+ className = "p-1 rounded hover:bg-vscode-list-hoverBackground transition-colors" >
224+ < ListChecks className = "size-4" />
225+ </ button >
226+ </ StandardTooltip >
227+ < StandardTooltip content = { t ( "chat:autoApprove.selectNone" ) } >
228+ < button
229+ onClick = { handleSelectNone }
230+ className = "p-1 rounded hover:bg-vscode-list-hoverBackground transition-colors" >
231+ < LayoutList className = "size-4" />
232+ </ button >
233+ </ StandardTooltip >
234+ </ div >
173235 </ div >
174236
175- < AutoApproveToggle { ...toggles } onToggle = { onAutoApproveToggle } />
176- </ div >
177- ) }
237+ { /* Description */ }
238+ < div className = "px-3 py-2 border-b border-vscode-dropdown-border" >
239+ < div
240+ style = { {
241+ color : "var(--vscode-descriptionForeground)" ,
242+ fontSize : "12px" ,
243+ } } >
244+ < Trans
245+ i18nKey = "chat:autoApprove.description"
246+ components = { {
247+ settingsLink : < VSCodeLink href = "#" onClick = { handleOpenSettings } /> ,
248+ } }
249+ />
250+ </ div >
251+ </ div >
178252
179- < div
180- style = { {
181- display : "flex" ,
182- alignItems : "center" ,
183- gap : "8px" ,
184- padding : "2px 0 0 0" ,
185- cursor : "pointer" ,
186- } }
187- onClick = { toggleExpanded } >
188- < div onClick = { ( e ) => e . stopPropagation ( ) } >
189- < StandardTooltip
190- content = { ! hasEnabledOptions ? t ( "chat:autoApprove.selectOptionsFirst" ) : undefined } >
191- < VSCodeCheckbox
192- checked = { effectiveAutoApprovalEnabled }
193- disabled = { ! hasEnabledOptions }
194- aria-label = {
195- hasEnabledOptions
196- ? t ( "chat:autoApprove.toggleAriaLabel" )
197- : t ( "chat:autoApprove.disabledAriaLabel" )
198- }
199- onChange = { ( ) => {
200- if ( hasEnabledOptions ) {
201- const newValue = ! ( autoApprovalEnabled ?? false )
202- setAutoApprovalEnabled ( newValue )
203- vscode . postMessage ( { type : "autoApprovalEnabled" , bool : newValue } )
204- }
205- // If no options enabled, do nothing
206- } }
207- />
208- </ StandardTooltip >
209- </ div >
210- < div
211- style = { {
212- display : "flex" ,
213- alignItems : "center" ,
214- gap : "4px" ,
215- flex : 1 ,
216- minWidth : 0 ,
217- } } >
218- < span
219- style = { {
220- color : "var(--vscode-foreground)" ,
221- flexShrink : 0 ,
222- } } >
223- { t ( "chat:autoApprove.title" ) }
224- </ span >
225- < span
226- style = { {
227- color : "var(--vscode-descriptionForeground)" ,
228- overflow : "hidden" ,
229- textOverflow : "ellipsis" ,
230- whiteSpace : "nowrap" ,
231- flex : 1 ,
232- minWidth : 0 ,
233- } } >
234- { displayText }
235- </ span >
236- < span
237- className = { `codicon codicon-chevron-right flex-shrink-0 transition-transform duration-200 ease-in-out ${
238- isExpanded ? "-rotate-90 ml-[2px]" : "rotate-0 -ml-[2px]"
239- } `}
240- />
253+ { /* Two-column layout for toggles */ }
254+ < div className = "p-3 max-h-[400px] overflow-y-auto" >
255+ < div className = "grid grid-cols-2 gap-x-4" >
256+ < AutoApproveToggleDropdown { ...toggles } onToggle = { onAutoApproveToggle } />
257+ </ div >
258+ </ div >
241259 </ div >
242- </ div >
243- </ div >
260+ </ PopoverContent >
261+ </ Popover >
244262 )
245263}
246264
0 commit comments