fix: stabilize notebook actions menu
This commit is contained in:
1 parent
af01870873
commit
1e384d2c0b
4 files changed
+85
-45
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useRef, type DragEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ChevronDown, ChevronRight, Notebook as NotebookIcon, Plus, Pencil, Trash2 } from "lucide-react";
|
||||
import { ChevronDown, ChevronRight, MoreHorizontal, Notebook as NotebookIcon, Plus, Pencil, Trash2 } from "lucide-react";
|
||||
import type { NotebookNode, NotebookDropPosition } from "@/lib/app-helpers";
|
||||
import {
|
||||
hasMemoDragData,
|
||||
@@ -58,6 +58,8 @@ export const NotebookTreeItem = ({
|
||||
const isInbox = node.slug === "inbox";
|
||||
const hasSelectedDescendant = selectedNotebookId ? notebookTreeContainsId(node.children, selectedNotebookId) : false;
|
||||
const [dropPosition, setDropPosition] = useState<NotebookDropPosition | null>(null);
|
||||
const [actionsOpen, setActionsOpen] = useState(false);
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
const expandTimerRef = useRef<number | null>(null);
|
||||
|
||||
const clearExpandTimer = () => {
|
||||
@@ -71,6 +73,21 @@ export const NotebookTreeItem = ({
|
||||
|
||||
useEffect(() => () => clearExpandTimer(), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!actionsOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closeOnOutsidePointer = (event: PointerEvent) => {
|
||||
if (!actionsRef.current?.contains(event.target as Node)) {
|
||||
setActionsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("pointerdown", closeOnOutsidePointer);
|
||||
return () => document.removeEventListener("pointerdown", closeOnOutsidePointer);
|
||||
}, [actionsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSelectedDescendant) {
|
||||
setOpen(true);
|
||||
@@ -256,50 +273,69 @@ export const NotebookTreeItem = ({
|
||||
{node.memoCount}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className={cn(
|
||||
"hidden h-6 w-6 items-center justify-center rounded-md group-focus-within:flex group-hover:flex transition-colors duration-150",
|
||||
selected ? "hover:bg-slate-200" : "hover:bg-slate-100"
|
||||
<div ref={actionsRef} className="relative shrink-0">
|
||||
<button
|
||||
className={cn(
|
||||
"hidden h-6 w-6 items-center justify-center rounded-md group-focus-within:flex group-hover:flex transition-colors duration-150",
|
||||
selected ? "hover:bg-slate-200" : "hover:bg-slate-100"
|
||||
)}
|
||||
type="button"
|
||||
title={t("notebookTree.actions")}
|
||||
aria-label={t("notebookTree.actionsAria", { name: node.name })}
|
||||
aria-expanded={actionsOpen}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActionsOpen((openValue) => !openValue);
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{actionsOpen && (
|
||||
<div className="absolute right-0 top-8 z-50 w-44 overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-lg">
|
||||
<button
|
||||
className="flex h-9 w-full items-center gap-2 rounded-sm px-2 text-left text-sm outline-none hover:bg-slate-100"
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActionsOpen(false);
|
||||
onCreateNotebook(node.id);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
{t("notebookTree.newChild")}
|
||||
</button>
|
||||
<button
|
||||
className="flex h-9 w-full items-center gap-2 rounded-sm px-2 text-left text-sm outline-none hover:bg-slate-100"
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActionsOpen(false);
|
||||
onRenameNotebook(node);
|
||||
}}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
{t("notebookTree.rename")}
|
||||
</button>
|
||||
{!isInbox && (
|
||||
<>
|
||||
<div className="-mx-1 my-1 h-px bg-slate-100" />
|
||||
<button
|
||||
className="flex h-9 w-full items-center gap-2 rounded-sm px-2 text-left text-sm text-rose-700 outline-none hover:bg-rose-50"
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setActionsOpen(false);
|
||||
onDeleteNotebook(node);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
{t("notebookTree.deleteNotebook")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
type="button"
|
||||
title={t("notebookTree.newChild")}
|
||||
aria-label={t("notebookTree.newChildAria", { name: node.name })}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onCreateNotebook(node.id);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
className={cn(
|
||||
"hidden h-6 w-6 items-center justify-center rounded-md group-focus-within:flex group-hover:flex transition-colors duration-150",
|
||||
selected ? "hover:bg-slate-200" : "hover:bg-slate-100"
|
||||
)}
|
||||
type="button"
|
||||
title={t("notebookTree.renameNotebook")}
|
||||
aria-label={t("notebookTree.renameAria", { name: node.name })}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onRenameNotebook(node);
|
||||
}}
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{!isInbox ? (
|
||||
<button
|
||||
className="hidden h-6 w-6 items-center justify-center rounded-md text-rose-600 hover:bg-rose-50 group-focus-within:flex group-hover:flex transition-colors duration-150"
|
||||
type="button"
|
||||
title={t("notebookTree.deleteNotebook")}
|
||||
aria-label={t("notebookTree.deleteAria", { name: node.name })}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onDeleteNotebook(node);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{dropPosition === "before" && (
|
||||
<div className="absolute top-0 right-2 h-[3px] bg-slate-400 rounded-full z-30 animate-pulse" style={{ left: `${20 + depth * 14}px` }} />
|
||||
)}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "edgeever",
|
||||
"version": "1.6.48",
|
||||
"version": "1.6.49",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
@@ -154,6 +154,8 @@ export const enUS = {
|
||||
switchTo: "Switch to {{name}}",
|
||||
currentWithCount: "Current: {{name}}, {{count}} notes",
|
||||
switchToWithCount: "Switch to {{name}}, {{count}} notes",
|
||||
actions: "Notebook actions",
|
||||
actionsAria: "Open actions for {{name}}",
|
||||
newChild: "New child notebook",
|
||||
newChildAria: "Create child notebook under {{name}}",
|
||||
rename: "Rename",
|
||||
|
||||
@@ -154,6 +154,8 @@ export const zhCN = {
|
||||
switchTo: "切换到 {{name}}",
|
||||
currentWithCount: "当前:{{name}},{{count}} 条笔记",
|
||||
switchToWithCount: "切换到 {{name}},{{count}} 条笔记",
|
||||
actions: "笔记本操作",
|
||||
actionsAria: "打开 {{name}} 的操作菜单",
|
||||
newChild: "新建子笔记本",
|
||||
newChildAria: "在 {{name}} 下新建子笔记本",
|
||||
rename: "重命名",
|
||||
|
||||
Reference in new issue
Block a user