Skip to content

Commit

Permalink
feat: add better context menu handler for list view
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal committed Jul 13, 2024
1 parent a5d43ac commit f824ebc
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 59 deletions.
12 changes: 10 additions & 2 deletions src/components/gui/context-menu-handler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {
ContextMenuSubContent,
} from "@/components/ui/context-menu";
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";

function ContextMenuList({ menu }: { menu: OpenContextMenuList }) {
export function ContextMenuList({ menu }: { menu: OpenContextMenuList }) {
return menu.map((item, menuIndex) => {
if (item.separator) {
return <ContextMenuSeparator key={menuIndex} />;
Expand Down Expand Up @@ -58,7 +59,14 @@ function ContextMenuList({ menu }: { menu: OpenContextMenuList }) {
disabled={item.disabled}
inset={!item.icon}
>
{item.icon && <item.icon className="w-4 h-4 mr-2" />}
{item.icon && (
<item.icon
className={cn(
"w-4 h-4 mr-2",
item.destructive ? "text-red-500" : undefined
)}
/>
)}
{item.destructive ? (
<span className="text-red-500">{item.title}</span>
) : (
Expand Down
25 changes: 21 additions & 4 deletions src/components/gui/sidebar/saved-doc-tab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import {
SavedDocNamespace,
} from "@/drivers/saved-doc/saved-doc-driver";
import { ListView, ListViewItem } from "@/listview";
import { LucideCode, LucideFolderGit } from "lucide-react";
import { LucideCode, LucideFolderGit, LucideTrash } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import CreateNamespaceButton from "./create-namespace-button";
import { openContextMenuFromEvent } from "@/messages/open-context-menu";

function mapNamespace(
data: SavedDocNamespace
Expand Down Expand Up @@ -66,8 +65,15 @@ function SavedDocNamespaceDocList({
full
onSelectChange={setSelected}
selectedKey={selected}
onContextMenu={(e) => {
openContextMenuFromEvent([{ title: "Hello" }])(e);
onContextMenu={(item) => {
return [
{
title: "Remove",
icon: LucideTrash,
destructive: true,
disabled: !item,
},
];
}}
onDoubleClick={(item: ListViewItem<SavedDocData>) => {
openTab({
Expand Down Expand Up @@ -122,6 +128,17 @@ export default function SavedDocTab() {
items={namespaceList}
selectedKey={selectedNamespace}
onSelectChange={setSelectedNamespace}
onContextMenu={(item) => {
return [
{ title: "Rename" },
{
title: "Remove",
icon: LucideTrash,
destructive: true,
disabled: !item,
},
];
}}
/>
<CreateNamespaceButton onCreated={onNamespaceCreated} />
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ContextMenuSubContent = React.forwardRef<
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
Expand All @@ -66,7 +66,7 @@ const ContextMenuContent = React.forwardRef<
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
Expand Down
135 changes: 84 additions & 51 deletions src/listview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { ContextMenuList } from "@/components/gui/context-menu-handler";
import { buttonVariants } from "@/components/ui/button";
import {
ContextMenu,
ContextMenuContent,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { cn } from "@/lib/utils";
import { OpenContextMenuList } from "@/messages/open-context-menu";
import { LucideIcon } from "lucide-react";
import { useRef, useState } from "react";

export interface ListViewItem<T = unknown> {
key: string;
Expand All @@ -16,10 +24,7 @@ interface ListViewProps<T> {
full?: boolean;
onSelectChange?: (key: string) => void;
onDoubleClick?: (item: ListViewItem<T>) => void;
onContextMenu?: (
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
item?: ListViewItem<T>
) => void;
onContextMenu?: (item?: ListViewItem<T>) => OpenContextMenuList;
}

export function ListView<T = unknown>({
Expand All @@ -30,53 +35,81 @@ export function ListView<T = unknown>({
onContextMenu,
full,
}: ListViewProps<T>) {
const [contextOpen, setContextOpen] = useState(false);
const [contextMenuKey, setContextMenuKey] = useState("");
const [contextMenu, setContextMenu] = useState<OpenContextMenuList>([]);

// When click on list item context menu, it will set to TRUE
// to prevent container context menu event.
const stopParentPropagation = useRef<boolean>(false);

return (
<div
className={full ? "grow overflow-auto p-2" : "p-2"}
onContextMenu={(e) => {
if (onContextMenu) onContextMenu(e);
}}
>
<div className={"flex flex-col"}>
{items.map((item) => {
return (
<div
key={item.key}
onContextMenu={(e) => {
if (onContextMenu) onContextMenu(e, item);
}}
onDoubleClick={() => {
if (onDoubleClick) {
onDoubleClick(item);
}
}}
onMouseDown={() => {
if (onSelectChange) {
onSelectChange(item.key);
}
}}
className="flex p-0.5"
>
<div
className={cn(
buttonVariants({
variant: selectedKey === item.key ? "default" : "ghost",
size: "sm",
}),
"w-full",
"justify-start",
"cursor-pointer"
)}
>
{item.icon && (
<item.icon className={cn("w-4 h-4 mr-2", item.iconColor)} />
)}
{item.name}
</div>
</div>
);
})}
</div>
</div>
<ContextMenu modal={false} onOpenChange={setContextOpen}>
<ContextMenuTrigger asChild>
<div
tabIndex={0}
className={full ? "grow overflow-auto p-2" : "p-2"}
onContextMenu={() => {
if (stopParentPropagation.current) {
stopParentPropagation.current = false;
return;
}
if (onContextMenu) setContextMenu(onContextMenu());
setContextMenuKey("");
}}
>
<div className={"flex flex-col"}>
{items.map((item) => {
return (
<div
key={item.key}
onContextMenu={() => {
stopParentPropagation.current = true;
setContextMenuKey(item.key);
if (onContextMenu) setContextMenu(onContextMenu(item));
}}
onDoubleClick={() => {
if (onDoubleClick) {
onDoubleClick(item);
}
}}
onClick={() => {
if (onSelectChange) {
onSelectChange(item.key);
}
}}
className="flex p-0.5"
>
<div
className={cn(
buttonVariants({
variant: selectedKey === item.key ? "default" : "ghost",
size: "sm",
}),
contextMenuKey === item.key && contextOpen
? "border border-blue-500"
: "border border-transparent",
"w-full",
"justify-start",
"cursor-pointer"
)}
>
{item.icon && (
<item.icon
className={cn("w-4 h-4 mr-2", item.iconColor)}
/>
)}
{item.name}
</div>
</div>
);
})}
</div>
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuList menu={contextMenu} />
</ContextMenuContent>
</ContextMenu>
);
}

0 comments on commit f824ebc

Please sign in to comment.