Skip to content

Commit 050621f

Browse files
feat(folder-picker): remove a folder from recents (#2859)
1 parent 5fd5d63 commit 050621f

1 file changed

Lines changed: 84 additions & 1 deletion

File tree

packages/ui/src/features/folder-picker/FolderPicker.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@ import {
44
Folder as FolderIcon,
55
FolderOpen,
66
GitBranch,
7+
X,
78
} from "@phosphor-icons/react";
89
import { ROOT_LOGGER, type RootLogger } from "@posthog/di/logger";
910
import { useService } from "@posthog/di/react";
1011
import { useHostTRPCClient } from "@posthog/host-router/react";
1112
import {
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";
2129
import { FIELD_TRIGGER_CLASS } from "@posthog/ui/styles/fieldTrigger";
2230
import { Flex, Text } from "@radix-ui/themes";
2331
import { type RefObject, useState } from "react";
32+
import { toast } from "sonner";
2433

2534
interface 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

Comments
 (0)