Skip to content

Commit bb80a06

Browse files
committed
feat: implement drag-and-drop functionality for adding apps to sidebar favorites
- Add drag-and-drop support in AppList for app items - Implement drag event handlers to manage drag state and data transfer - Enhance AppSidebar to handle drop events and visual feedback during dragging
1 parent d9ffe21 commit bb80a06

File tree

2 files changed

+63
-9
lines changed

2 files changed

+63
-9
lines changed

src-frontend/components/app-sidebar.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,27 @@ import {
4747
Star,
4848
X,
4949
} from "lucide-react";
50+
import { cn } from "@/lib/utils";
5051

5152
const EMAIL_PLACEHOLDER = "[email protected]";
5253

5354
export function AppSidebar() {
5455
const router = useRouter();
5556
const pathname = usePathname();
5657
const isMobile = useIsMobile();
58+
const [isDraggingOver, setIsDraggingOver] = useState(false);
5759
const { navigateTo } = useFileSystemStore();
5860
const { datasite } = useConnectionStore();
5961
const [email, setEmail] = useState<string>(EMAIL_PLACEHOLDER);
6062

6163
// Use Zustand store for sidebar state
6264
const {
65+
activeItem,
6366
favorites,
6467
openSections,
65-
activeItem,
6668
removeFavorite,
67-
toggleSection,
6869
setActiveItem,
70+
toggleSection,
6971
} = useSidebarStore();
7072

7173
useEffect(() => {
@@ -164,8 +166,22 @@ export function AppSidebar() {
164166
}
165167
};
166168

169+
const handleDragOver = (e: React.DragEvent) => {
170+
e.preventDefault();
171+
e.dataTransfer.dropEffect = "copy";
172+
setIsDraggingOver(true);
173+
};
174+
175+
const handleDragLeave = (e: React.DragEvent) => {
176+
// Only set dragging to false if we're leaving the drop zone entirely
177+
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
178+
setIsDraggingOver(false);
179+
}
180+
};
181+
167182
const handleDrop = (e: React.DragEvent) => {
168183
e.preventDefault();
184+
setIsDraggingOver(false);
169185
const data = e.dataTransfer.getData("application/json");
170186
if (data) {
171187
try {
@@ -191,11 +207,6 @@ export function AppSidebar() {
191207
}
192208
};
193209

194-
const handleDragOver = (e: React.DragEvent) => {
195-
e.preventDefault();
196-
e.dataTransfer.dropEffect = "copy";
197-
};
198-
199210
const handleRemoveFavorite = (id: string, e: React.MouseEvent) => {
200211
e?.stopPropagation();
201212
removeFavorite(id);
@@ -243,9 +254,14 @@ export function AppSidebar() {
243254
</CollapsibleTrigger>
244255
<CollapsibleContent>
245256
<div
246-
className="space-y-1 py-2 pr-2 pl-4"
257+
className={cn(
258+
"space-y-1 py-2 pr-2 pl-4 transition-colors",
259+
isDraggingOver &&
260+
"bg-accent/50 border-primary rounded-md border-2 border-dashed",
261+
)}
247262
onDrop={handleDrop}
248263
onDragOver={handleDragOver}
264+
onDragLeave={handleDragLeave}
249265
>
250266
{favorites.length === 0 ? (
251267
<p className="text-muted-foreground px-3 py-2 text-xs">

src-frontend/components/apps/app-list.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ import {
3535
DropdownMenuItem,
3636
DropdownMenuTrigger,
3737
} from "@/components/ui/dropdown-menu";
38+
import {
39+
Tooltip,
40+
TooltipContent,
41+
TooltipProvider,
42+
TooltipTrigger,
43+
} from "@/components/ui/tooltip";
3844
import { toast } from "@/hooks/use-toast";
3945
import { type App, installApp, listApps } from "@/lib/api/apps";
4046
import { useForm } from "react-hook-form";
@@ -170,6 +176,18 @@ export function AppList({ onSelectApp, onUninstall }: AppListProps) {
170176
}
171177
};
172178

179+
const handleDragStart = (e: React.DragEvent, app: App) => {
180+
// Create a drag item with app data for the sidebar
181+
const dragItem = {
182+
id: app.info.id,
183+
name: app.info.name,
184+
type: "app" as const,
185+
};
186+
187+
e.dataTransfer.setData("application/json", JSON.stringify(dragItem));
188+
e.dataTransfer.effectAllowed = "copy";
189+
};
190+
173191
const getTabButtonClassNames = (tab: string) =>
174192
cn(
175193
"hover:text-muted-foreground h-auto px-3 py-1",
@@ -257,13 +275,33 @@ export function AppList({ onSelectApp, onUninstall }: AppListProps) {
257275
{filteredApps.map((app) => (
258276
<tr
259277
key={app.info.id}
260-
className="hover:bg-muted/50 cursor-pointer"
278+
className="hover:bg-muted/50 group cursor-pointer"
261279
onClick={() => onSelectApp(app.info.id)}
280+
draggable
281+
onDragStart={(e) => handleDragStart(e, app)}
262282
>
263283
<td className="px-6 py-4">
264284
<div className="flex items-center gap-3">
265285
<AppIcon name={app.info.name} />
266286
<span className="font-medium">{app.info.name}</span>
287+
<TooltipProvider>
288+
<Tooltip>
289+
<TooltipTrigger asChild>
290+
<div className="text-muted-foreground cursor-grab opacity-0 transition-opacity group-hover:opacity-100">
291+
<svg
292+
className="h-3 w-3"
293+
fill="currentColor"
294+
viewBox="0 0 20 20"
295+
>
296+
<path d="M7 2a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 2zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 8zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 14zm6-8a2 2 0 1 1-.001-4.001A2 2 0 0 1 13 6zm0 2a2 2 0 1 1 .001 4.001A2 2 0 0 1 13 8zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 13 14z" />
297+
</svg>
298+
</div>
299+
</TooltipTrigger>
300+
<TooltipContent>
301+
<p>Drag to add to sidebar favorites</p>
302+
</TooltipContent>
303+
</Tooltip>
304+
</TooltipProvider>
267305
</div>
268306
</td>
269307
<td className="px-6 py-4">

0 commit comments

Comments
 (0)