@@ -4,11 +4,19 @@ import {
44 Folder as FolderIcon ,
55 FolderOpen ,
66 GitBranch ,
7+ X ,
78} from "@phosphor-icons/react" ;
89import { ROOT_LOGGER , type RootLogger } from "@posthog/di/logger" ;
910import { useService } from "@posthog/di/react" ;
1011import { useHostTRPCClient } from "@posthog/host-router/react" ;
1112import {
13+ AlertDialog ,
14+ AlertDialogClose ,
15+ AlertDialogContent ,
16+ AlertDialogDescription ,
17+ AlertDialogFooter ,
18+ AlertDialogHeader ,
19+ AlertDialogTitle ,
1220 Button ,
1321 DropdownMenu ,
1422 DropdownMenuContent ,
@@ -21,6 +29,7 @@ import { useFolders } from "@posthog/ui/features/folders/useFolders";
2129import { FIELD_TRIGGER_CLASS } from "@posthog/ui/styles/fieldTrigger" ;
2230import { Flex , Text } from "@radix-ui/themes" ;
2331import { type RefObject , useState } from "react" ;
32+ import { toast } from "sonner" ;
2433
2534interface FolderPickerProps {
2635 value : string ;
@@ -43,6 +52,7 @@ export function FolderPicker({
4352 getRecentFolders,
4453 getFolderDisplayName,
4554 addFolder,
55+ removeFolder,
4656 updateLastAccessed,
4757 getFolderByPath,
4858 } = useFolders ( ) ;
@@ -52,13 +62,37 @@ export function FolderPicker({
5262 const isField = variant === "field" ;
5363
5464 const [ isOpening , setIsOpening ] = useState ( false ) ;
65+ const [ menuOpen , setMenuOpen ] = useState ( false ) ;
66+ const [ pendingRemoval , setPendingRemoval ] = useState < {
67+ id : string ;
68+ name : string ;
69+ path : string ;
70+ } | null > ( null ) ;
71+ const [ isRemoving , setIsRemoving ] = useState ( false ) ;
5572
5673 const handleSelect = ( path : string ) => {
5774 onChange ( path ) ;
5875 const folder = getFolderByPath ( path ) ;
5976 if ( folder ) updateLastAccessed ( folder . id ) ;
6077 } ;
6178
79+ const handleConfirmRemoval = async ( ) => {
80+ if ( ! pendingRemoval ) return ;
81+ setIsRemoving ( true ) ;
82+ try {
83+ await removeFolder ( pendingRemoval . id ) ;
84+ if ( pendingRemoval . path === value ) onChange ( "" ) ;
85+ setPendingRemoval ( null ) ;
86+ } catch ( error ) {
87+ log . error ( "Failed to remove folder" , { error } ) ;
88+ toast . error ( "Couldn't remove folder" , {
89+ description : error instanceof Error ? error . message : String ( error ) ,
90+ } ) ;
91+ } finally {
92+ setIsRemoving ( false ) ;
93+ }
94+ } ;
95+
6296 const handleOpenFilePicker = async ( ) => {
6397 if ( isOpening ) return ;
6498 setIsOpening ( true ) ;
@@ -136,7 +170,7 @@ export function FolderPicker({
136170 }
137171
138172 return (
139- < DropdownMenu >
173+ < DropdownMenu open = { menuOpen } onOpenChange = { setMenuOpen } >
140174 < DropdownMenuTrigger
141175 render = {
142176 isField ? (
@@ -166,6 +200,7 @@ export function FolderPicker({
166200 < DropdownMenuItem
167201 key = { folder . id }
168202 onClick = { ( ) => handleSelect ( folder . path ) }
203+ className = "group"
169204 >
170205 < GitBranch size = { 12 } className = "shrink-0" />
171206 < span
@@ -174,6 +209,24 @@ export function FolderPicker({
174209 >
175210 { folder . name }
176211 </ span >
212+ < button
213+ type = "button"
214+ aria-label = { `Remove ${ folder . name } from recents` }
215+ className = "-mr-1 ml-1 shrink-0 rounded p-0.5 text-(--gray-9) opacity-0 hover:bg-(--gray-4) hover:text-(--gray-12) focus-visible:opacity-100 group-hover:opacity-100"
216+ onPointerDown = { ( e ) => e . stopPropagation ( ) }
217+ onClick = { ( e ) => {
218+ e . stopPropagation ( ) ;
219+ e . preventDefault ( ) ;
220+ setMenuOpen ( false ) ;
221+ setPendingRemoval ( {
222+ id : folder . id ,
223+ name : folder . name ,
224+ path : folder . path ,
225+ } ) ;
226+ } }
227+ >
228+ < X size = { 12 } />
229+ </ button >
177230 </ DropdownMenuItem >
178231 ) ) }
179232 < DropdownMenuSeparator />
@@ -182,6 +235,36 @@ export function FolderPicker({
182235 < span className = "whitespace-nowrap" > Open folder...</ span >
183236 </ DropdownMenuItem >
184237 </ DropdownMenuContent >
238+
239+ < AlertDialog
240+ open = { pendingRemoval !== null }
241+ onOpenChange = { ( open ) => {
242+ if ( ! open && ! isRemoving ) setPendingRemoval ( null ) ;
243+ } }
244+ >
245+ < AlertDialogContent className = "max-w-md" >
246+ < AlertDialogHeader >
247+ < AlertDialogTitle > Remove folder</ AlertDialogTitle >
248+ < AlertDialogDescription >
249+ "{ pendingRemoval ?. name } " will be removed from PostHog Code,
250+ including all of its tasks and their workspaces. This can't be
251+ undone.
252+ </ AlertDialogDescription >
253+ </ AlertDialogHeader >
254+ < AlertDialogFooter >
255+ < AlertDialogClose
256+ render = { < Button variant = "outline" > Cancel</ Button > }
257+ />
258+ < Button
259+ variant = "destructive"
260+ loading = { isRemoving }
261+ onClick = { ( ) => void handleConfirmRemoval ( ) }
262+ >
263+ Remove
264+ </ Button >
265+ </ AlertDialogFooter >
266+ </ AlertDialogContent >
267+ </ AlertDialog >
185268 </ DropdownMenu >
186269 ) ;
187270}
0 commit comments