Files
drift/src/components/sidebar/Entity.tsx
T
2026-02-28 13:41:51 +01:00

249 lines
8.0 KiB
TypeScript

"use client";
import { CollapsibleTrigger } from "@radix-ui/react-collapsible";
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "../ui/sidebar";
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
import { ChevronDown, MoreHorizontal } from "lucide-react";
import { Route, Workspace, LayoutNode } from "@/lib/types";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useState } from "react";
type Props = {
node: LayoutNode;
workspace: Workspace;
setRoute: (route: Route) => void;
addEntity: (
type: "todo" | "doc",
parentGroupId: string | null,
name?: string,
) => string | undefined;
addGroup: (name: string | undefined, parentGroupId: string | null) => string;
renameEntity: (entityId: string, newName: string) => void;
deleteEntity: (entityId: string) => void;
removeNode: (id: string) => void;
renameGroup: (id: string, name: string) => void;
};
export default function EntitySwitch({
node,
workspace,
setRoute,
addEntity,
addGroup,
renameEntity,
deleteEntity,
removeNode,
renameGroup,
}: Props) {
const [menuOpen, setMenuOpen] = useState(false);
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogValue, setDialogValue] = useState("");
const [dialogMode, setDialogMode] = useState<
"rename-group" | "rename-item" | "add-item" | "add-group"
>("rename-item");
const openDialog = (mode: typeof dialogMode, initial = "") => {
setDialogMode(mode);
// supply default for add-item
if (mode === "add-item") {
setDialogValue("New list");
} else {
setDialogValue(initial);
}
setDialogOpen(true);
setMenuOpen(false);
};
const handleDialogSubmit = () => {
if (dialogMode === "rename-group" && node.type === "group") {
renameGroup(node.id, dialogValue);
} else if (dialogMode === "rename-item" && node.type === "item") {
renameEntity(node.entityId, dialogValue);
} else if (dialogMode === "add-item") {
const parentId = node.type === "group" ? node.id : null;
addEntity("todo", parentId, dialogValue || "New list");
} else if (dialogMode === "add-group") {
const parentId = node.type === "group" ? node.id : null;
addGroup(dialogValue || "New group", parentId);
}
setDialogOpen(false);
};
// ---- GROUP ----
if (node.type === "group") {
return (
<>
<Collapsible defaultOpen className="group/collapsible">
<SidebarGroup>
<SidebarGroupLabel asChild>
<div className="flex items-center justify-between w-full">
<SidebarMenuButton asChild>
<CollapsibleTrigger className="flex w-full items-center">
{node.name}
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
</CollapsibleTrigger>
</SidebarMenuButton>
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenuTrigger asChild>
<button className="p-1 hover:bg-accent rounded">
<MoreHorizontal className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onSelect={() => openDialog("add-item")}>
Add list
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => openDialog("add-group")}>
Add group
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={() => openDialog("rename-group", node.name)}
>
Rename
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
if (
window.confirm("Delete this group and its children?")
) {
removeNode(node.id);
}
}}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
<SidebarMenu>
{node.children.map((child) => (
<EntitySwitch
key={child.type === "group" ? child.id : child.entityId}
node={child}
workspace={workspace}
setRoute={setRoute}
addEntity={addEntity}
addGroup={addGroup}
renameEntity={renameEntity}
deleteEntity={deleteEntity}
removeNode={removeNode}
renameGroup={renameGroup}
/>
))}
</SidebarMenu>
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>
</Collapsible>
{/* dialog for add/rename */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{dialogMode.startsWith("rename")
? "Rename"
: dialogMode === "add-item"
? "Add new list"
: "Add new group"}
</DialogTitle>
<DialogDescription>
{dialogMode === "add-item" &&
"A default todo list will be created."}
{dialogMode === "add-group" &&
"Enter the name for the new group."}
</DialogDescription>
</DialogHeader>
<Input
value={dialogValue}
onChange={(e) => setDialogValue(e.target.value)}
className="w-full"
/>
<DialogFooter>
<Button variant="outline" onClick={() => setDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleDialogSubmit}>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
// ---- ITEM ----
const workspaceEntity = workspace.entities[node.entityId];
if (!workspaceEntity) {
return null;
}
return (
<SidebarMenuItem>
<div className="flex items-center justify-between w-full">
<SidebarMenuButton
onClick={() =>
setRoute({
workspace: workspace.id,
entity: node.entityId,
})
}
>
{workspaceEntity.name}
</SidebarMenuButton>
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenuTrigger asChild>
<button className="p-1 hover:bg-accent rounded">
<MoreHorizontal className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onSelect={() => openDialog("rename-item", workspaceEntity.name)}
>
Rename
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => {
if (window.confirm("Delete this list?")) {
deleteEntity(node.entityId);
}
}}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</SidebarMenuItem>
);
}