Skip to content

Commit 1ddb55a

Browse files
authored
fix: replace popover with improved dropdown implementation (#66)
2 parents e88863a + 6d29698 commit 1ddb55a

File tree

3 files changed

+152
-137
lines changed

3 files changed

+152
-137
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"use client";
2+
3+
import { Trash2, MoreVertical, Pencil } from "lucide-react";
4+
import React from "react";
5+
import { EditAgentInboxDialog } from "./edit-agent-inbox-dialog";
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuItem,
10+
DropdownMenuTrigger,
11+
} from "@/components/ui/dropdown-menu";
12+
import { Button } from "@/components/ui/button";
13+
import { Dialog, DialogTrigger } from "@/components/ui/dialog";
14+
import { AgentInbox } from "../types";
15+
// Create a separate component for the dropdown/dialog pair
16+
export function DropdownDialogMenu({
17+
item,
18+
deleteAgentInbox,
19+
}: {
20+
item: AgentInbox;
21+
deleteAgentInbox: (id: string) => void;
22+
}) {
23+
const [dropdownOpen, setDropdownOpen] = React.useState(false);
24+
const [dialogOpen, setDialogOpen] = React.useState(false);
25+
26+
// Handle dialog open/close and ensure dropdown closes when dialog opens
27+
const handleDialogOpenChange = (open: boolean) => {
28+
setDialogOpen(open);
29+
if (open) {
30+
setDropdownOpen(false);
31+
}
32+
};
33+
34+
return (
35+
<Dialog open={dialogOpen} onOpenChange={handleDialogOpenChange}>
36+
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
37+
<DropdownMenuTrigger asChild>
38+
<Button className="px-2" variant="ghost">
39+
<MoreVertical className="w-4 h-4 cursor-pointer text-gray-800 hover:text-gray-600 transition-colors ease-in-out duration-200" />
40+
</Button>
41+
</DropdownMenuTrigger>
42+
<DropdownMenuContent align="end" className="w-40">
43+
<DialogTrigger asChild>
44+
<DropdownMenuItem
45+
className="cursor-pointer flex items-center gap-2"
46+
onClick={(e) => {
47+
e.stopPropagation();
48+
}}
49+
>
50+
<Pencil className="w-4 h-4" />
51+
<span>Edit</span>
52+
</DropdownMenuItem>
53+
</DialogTrigger>
54+
<DropdownMenuItem
55+
className="cursor-pointer text-red-500 focus:text-red-500"
56+
onClick={(e) => {
57+
e.stopPropagation();
58+
deleteAgentInbox(item.id);
59+
// Close the dropdown
60+
setDropdownOpen(false);
61+
}}
62+
>
63+
<Trash2 className="w-4 h-4" />
64+
<span>Delete</span>
65+
</DropdownMenuItem>
66+
</DropdownMenuContent>
67+
</DropdownMenu>
68+
69+
<EditAgentInboxDialog agentInbox={item} />
70+
</Dialog>
71+
);
72+
}
Lines changed: 73 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import { Button } from "@/components/ui/button";
22
import {
3-
Dialog,
3+
DialogClose,
44
DialogContent,
55
DialogHeader,
66
DialogTitle,
7-
DialogTrigger,
87
} from "@/components/ui/dialog";
98
import { Input } from "@/components/ui/input";
109
import { Label } from "@/components/ui/label";
1110
import React from "react";
1211
import { useToast } from "@/hooks/use-toast";
1312
import { AgentInbox } from "../types";
14-
import { Pencil } from "lucide-react";
1513
import { useInboxes } from "../hooks/use-inboxes";
1614

1715
export function EditAgentInboxDialog({
@@ -24,20 +22,18 @@ export function EditAgentInboxDialog({
2422
}) {
2523
const { updateAgentInbox } = useInboxes();
2624
const { toast } = useToast();
27-
const [open, setOpen] = React.useState(false);
2825
const [graphId, setGraphId] = React.useState(agentInbox.graphId);
2926
const [deploymentUrl, setDeploymentUrl] = React.useState(
3027
agentInbox.deploymentUrl
3128
);
3229
const [name, setName] = React.useState(agentInbox.name || "");
3330

31+
// Initialize form values when the component mounts
3432
React.useEffect(() => {
35-
if (open) {
36-
setGraphId(agentInbox.graphId);
37-
setDeploymentUrl(agentInbox.deploymentUrl);
38-
setName(agentInbox.name || "");
39-
}
40-
}, [open, agentInbox]);
33+
setGraphId(agentInbox.graphId);
34+
setDeploymentUrl(agentInbox.deploymentUrl);
35+
setName(agentInbox.name || "");
36+
}, [agentInbox]);
4137

4238
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
4339
event.preventDefault();
@@ -57,9 +53,6 @@ export function EditAgentInboxDialog({
5753
duration: 3000,
5854
});
5955

60-
// Close the dialog
61-
setOpen(false);
62-
6356
// Force a page reload to reflect changes
6457
window.location.reload();
6558
} catch (error) {
@@ -74,92 +67,74 @@ export function EditAgentInboxDialog({
7467
};
7568

7669
return (
77-
<Dialog
78-
open={open}
79-
onOpenChange={(c) => {
80-
setOpen(c);
81-
}}
82-
>
83-
<DialogTrigger asChild>
84-
<button
85-
className="flex items-center gap-2 w-full text-left px-2 py-1.5 text-sm rounded hover:bg-gray-100"
86-
onClick={(e) => e.stopPropagation()}
87-
>
88-
<Pencil className="w-4 h-4" />
89-
<span>Edit</span>
90-
</button>
91-
</DialogTrigger>
92-
<DialogContent className="sm:max-w-[425px]">
93-
<DialogHeader>
94-
<DialogTitle>Edit Inbox</DialogTitle>
95-
</DialogHeader>
96-
<form
97-
className="flex flex-col items-center justify-center gap-4 py-4 w-full"
98-
onSubmit={handleSubmit}
99-
>
100-
<div className="flex flex-col gap-2 items-start justify-start w-full">
101-
<Label htmlFor="graph-id" className="text-right">
102-
Assistant/Graph ID <span className="text-red-500">*</span>
103-
</Label>
104-
<p className="text-xs text-muted-foreground">
105-
This is the ID of the graph (can be the graph name), or assistant
106-
to fetch threads from, and invoke when actions are taken.
107-
</p>
108-
<Input
109-
id="graph-id"
110-
placeholder="my_graph"
111-
className="col-span-3"
112-
required
113-
value={graphId}
114-
onChange={(e) => setGraphId(e.target.value)}
115-
/>
116-
</div>
117-
<div className="flex flex-col gap-2 items-start justify-start w-full">
118-
<Label htmlFor="deployment-url" className="text-right">
119-
Deployment URL <span className="text-red-500">*</span>
120-
</Label>
121-
<p className="text-xs text-muted-foreground">
122-
This is the URL of your LangGraph deployment. Can be a local, or
123-
production deployment.
124-
</p>
125-
<Input
126-
id="deployment-url"
127-
placeholder="https://my-agent.default.us.langgraph.app"
128-
className="col-span-3"
129-
required
130-
value={deploymentUrl}
131-
onChange={(e) => setDeploymentUrl(e.target.value)}
132-
/>
133-
</div>
134-
<div className="flex flex-col gap-2 items-start justify-start w-full">
135-
<Label htmlFor="name" className="text-right">
136-
Name
137-
</Label>
138-
<p className="text-xs text-muted-foreground">
139-
Optional name for the inbox. Used in the sidebar.
140-
</p>
141-
<Input
142-
id="name"
143-
placeholder="My Agent"
144-
className="col-span-3"
145-
value={name}
146-
onChange={(e) => setName(e.target.value)}
147-
/>
148-
</div>
149-
<div className="grid grid-cols-2 gap-4">
150-
<Button variant="brand" type="submit">
151-
Save
152-
</Button>
153-
<Button
154-
variant="outline"
155-
type="reset"
156-
onClick={() => setOpen(false)}
157-
>
70+
<DialogContent className="sm:max-w-[425px]">
71+
<DialogHeader>
72+
<DialogTitle>Edit Inbox</DialogTitle>
73+
</DialogHeader>
74+
<form
75+
className="flex flex-col items-center justify-center gap-4 py-4 w-full"
76+
onSubmit={handleSubmit}
77+
>
78+
<div className="flex flex-col gap-2 items-start justify-start w-full">
79+
<Label htmlFor="graph-id" className="text-right">
80+
Assistant/Graph ID <span className="text-red-500">*</span>
81+
</Label>
82+
<p className="text-xs text-muted-foreground">
83+
This is the ID of the graph (can be the graph name), or assistant to
84+
fetch threads from, and invoke when actions are taken.
85+
</p>
86+
<Input
87+
id="graph-id"
88+
placeholder="my_graph"
89+
className="col-span-3"
90+
required
91+
value={graphId}
92+
onChange={(e) => setGraphId(e.target.value)}
93+
/>
94+
</div>
95+
<div className="flex flex-col gap-2 items-start justify-start w-full">
96+
<Label htmlFor="deployment-url" className="text-right">
97+
Deployment URL <span className="text-red-500">*</span>
98+
</Label>
99+
<p className="text-xs text-muted-foreground">
100+
This is the URL of your LangGraph deployment. Can be a local, or
101+
production deployment.
102+
</p>
103+
<Input
104+
id="deployment-url"
105+
placeholder="https://my-agent.default.us.langgraph.app"
106+
className="col-span-3"
107+
required
108+
value={deploymentUrl}
109+
onChange={(e) => setDeploymentUrl(e.target.value)}
110+
/>
111+
</div>
112+
<div className="flex flex-col gap-2 items-start justify-start w-full">
113+
<Label htmlFor="name" className="text-right">
114+
Name
115+
</Label>
116+
<p className="text-xs text-muted-foreground">
117+
Optional name for the inbox. Used in the sidebar.
118+
</p>
119+
<Input
120+
id="name"
121+
placeholder="My Agent"
122+
className="col-span-3"
123+
value={name}
124+
onChange={(e) => setName(e.target.value)}
125+
/>
126+
</div>
127+
<div className="grid grid-cols-2 gap-4">
128+
<Button variant="brand" type="submit">
129+
Save
130+
</Button>
131+
<DialogClose asChild>
132+
<Button variant="outline" type="button">
158133
Cancel
159134
</Button>
160-
</div>
161-
</form>
162-
</DialogContent>
163-
</Dialog>
135+
</DialogClose>
136+
</div>
137+
</form>
138+
</DialogContent>
164139
);
165140
}

src/components/app-sidebar/index.tsx

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,7 @@ import {
1010
SidebarMenuButton,
1111
SidebarMenuItem,
1212
} from "@/components/ui/sidebar";
13-
import {
14-
FileText,
15-
Trash2,
16-
UploadCloud,
17-
House,
18-
MoreVertical,
19-
} from "lucide-react";
13+
import { FileText, UploadCloud, House } from "lucide-react";
2014
import { agentInboxSvg } from "../agent-inbox/components/agent-inbox-logo";
2115
import { SettingsPopover } from "../agent-inbox/components/settings-popover";
2216
import { PillButton } from "../ui/pill-button";
@@ -38,8 +32,7 @@ import {
3832
} from "../ui/tooltip";
3933
import { AddAgentInboxDialog } from "../agent-inbox/components/add-agent-inbox-dialog";
4034
import { useLocalStorage } from "../agent-inbox/hooks/use-local-storage";
41-
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
42-
import { EditAgentInboxDialog } from "../agent-inbox/components/edit-agent-inbox-dialog";
35+
import { DropdownDialogMenu } from "../agent-inbox/components/dropdown-and-dialog";
4336

4437
export function AppSidebar() {
4538
const { agentInboxes, changeAgentInbox, deleteAgentInbox } =
@@ -119,36 +112,11 @@ export function AppSidebar() {
119112
</TooltipContent>
120113
</Tooltip>
121114
</TooltipProvider>
122-
<Popover>
123-
<PopoverTrigger asChild>
124-
<TooltipIconButton
125-
variant="ghost"
126-
tooltip="Options"
127-
className="text-gray-800 hover:text-gray-600 transition-colors ease-in-out duration-200"
128-
delayDuration={100}
129-
onClick={(e) => {
130-
e.stopPropagation();
131-
}}
132-
>
133-
<MoreVertical className="w-4 h-4" />
134-
</TooltipIconButton>
135-
</PopoverTrigger>
136-
<PopoverContent className="w-40 p-2">
137-
<div className="flex flex-col gap-1">
138-
<EditAgentInboxDialog agentInbox={item} />
139-
<button
140-
onClick={(e) => {
141-
e.stopPropagation();
142-
deleteAgentInbox(item.id);
143-
}}
144-
className="flex items-center gap-2 w-full text-left px-2 py-1.5 text-sm rounded hover:bg-gray-100 text-red-500"
145-
>
146-
<Trash2 className="w-4 h-4" />
147-
<span>Delete</span>
148-
</button>
149-
</div>
150-
</PopoverContent>
151-
</Popover>
115+
116+
<DropdownDialogMenu
117+
item={item}
118+
deleteAgentInbox={deleteAgentInbox}
119+
/>
152120
</SidebarMenuItem>
153121
);
154122
})}

0 commit comments

Comments
 (0)