Fix notebook drag highlight and add drag-to-tag and drag-to-trash support
This commit is contained in:
1 parent
0f24af880b
commit
2cc202ce03
2 files changed
+304
-87
No files matched your search
@@ -188,6 +188,19 @@ export async function PATCH(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.addTagId && typeof body.addTagId === 'string') {
|
||||||
|
await prisma.noteTag.createMany({
|
||||||
|
data: [{ noteId: id, tagId: body.addTagId }],
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.removeTagId && typeof body.removeTagId === 'string') {
|
||||||
|
await prisma.noteTag.deleteMany({
|
||||||
|
where: { noteId: id, tagId: body.removeTagId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const updated = await prisma.note.update({
|
const updated = await prisma.note.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -1233,6 +1233,47 @@ export default function EvercloneWorkspace() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTagNoteById = async (noteId: string, tagId: string, tagName: string) => {
|
||||||
|
if (!noteId || !tagId) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/notes/${noteId}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ addTagId: tagId }),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const updated: NoteDetail = await res.json();
|
||||||
|
if (activeNote?.id === noteId) {
|
||||||
|
setActiveNote(updated);
|
||||||
|
}
|
||||||
|
await fetchSidebar();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to add tag to note via drag & drop:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTrashNoteById = async (noteId: string) => {
|
||||||
|
if (!noteId) return;
|
||||||
|
try {
|
||||||
|
if (activeView !== 'trash') {
|
||||||
|
setNotes((prev) => prev.filter((n) => n.id !== noteId));
|
||||||
|
if (selectedNoteId === noteId) {
|
||||||
|
setSelectedNoteId('');
|
||||||
|
setActiveNote(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fetch(`/api/notes/${noteId}?permanent=false`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
await fetchSidebar();
|
||||||
|
await fetchNotesList();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to move note to trash via drag & drop:', e);
|
||||||
|
await fetchNotesList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSwitchUser = async (userId: string) => {
|
const handleSwitchUser = async (userId: string) => {
|
||||||
setUserMenuOpen(false);
|
setUserMenuOpen(false);
|
||||||
const res = await fetch('/api/users', {
|
const res = await fetch('/api/users', {
|
||||||
@@ -1884,73 +1925,26 @@ export default function EvercloneWorkspace() {
|
|||||||
t.name.toLowerCase().includes(sidebarTagSearch.trim().toLowerCase())
|
t.name.toLowerCase().includes(sidebarTagSearch.trim().toLowerCase())
|
||||||
)
|
)
|
||||||
.map((t) => (
|
.map((t) => (
|
||||||
<div
|
<SidebarTagRow
|
||||||
key={t.id}
|
key={t.id}
|
||||||
className={`group flex items-center justify-between px-2 py-1 rounded cursor-pointer ${
|
tag={t}
|
||||||
activeView === 'tag' && selectedTagId === t.id
|
active={activeView === 'tag' && selectedTagId === t.id}
|
||||||
? 'bg-evernote-sidebarActive text-white font-medium'
|
onSelect={() => {
|
||||||
: 'hover:bg-evernote-sidebarHover text-gray-300'
|
setActiveView('tag');
|
||||||
}`}
|
setSelectedTagId(t.id);
|
||||||
>
|
setMobileMenuOpen(false);
|
||||||
<button
|
setMobilePane('list');
|
||||||
type="button"
|
}}
|
||||||
onClick={() => {
|
onDropNote={(noteId) => handleTagNoteById(noteId, t.id, t.name)}
|
||||||
setActiveView('tag');
|
onRefresh={fetchSidebar}
|
||||||
setSelectedTagId(t.id);
|
onDeleted={() => {
|
||||||
setMobileMenuOpen(false);
|
if (selectedTagId === t.id) {
|
||||||
setMobilePane('list');
|
setSelectedTagId('');
|
||||||
}}
|
setActiveView('all');
|
||||||
className="flex items-center gap-2 flex-1 truncate text-left"
|
}
|
||||||
>
|
fetchNotesList();
|
||||||
<span
|
}}
|
||||||
className="w-2 h-2 rounded-full shrink-0"
|
/>
|
||||||
style={{ backgroundColor: t.color || '#00a82d' }}
|
|
||||||
/>
|
|
||||||
<span className="truncate">{t.name}</span>
|
|
||||||
</button>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<span className="text-[10px] text-gray-500">{t.noteCount ?? 0}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={async (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const newName = window.prompt('Rename tag:', t.name);
|
|
||||||
if (newName?.trim() && newName.trim() !== t.name) {
|
|
||||||
await fetch('/api/tags', {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ id: t.id, name: newName.trim() }),
|
|
||||||
});
|
|
||||||
await fetchSidebar();
|
|
||||||
await fetchNotesList();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-500 hover:text-white rounded"
|
|
||||||
title="Rename tag"
|
|
||||||
>
|
|
||||||
<MoreHorizontal className="w-3 h-3" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={async (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (window.confirm(`Delete tag "${t.name}"?`)) {
|
|
||||||
await fetch(`/api/tags?id=${t.id}`, { method: 'DELETE' });
|
|
||||||
if (selectedTagId === t.id) {
|
|
||||||
setSelectedTagId('');
|
|
||||||
setActiveView('all');
|
|
||||||
}
|
|
||||||
await fetchSidebar();
|
|
||||||
await fetchNotesList();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-500 hover:text-red-400 rounded"
|
|
||||||
title="Delete tag"
|
|
||||||
>
|
|
||||||
<X className="w-3 h-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
{sidebar.tags.length === 0 && (
|
{sidebar.tags.length === 0 && (
|
||||||
<div className="text-[11px] text-gray-500 px-2 py-1 italic">
|
<div className="text-[11px] text-gray-500 px-2 py-1 italic">
|
||||||
@@ -1963,27 +1957,17 @@ export default function EvercloneWorkspace() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Trash */}
|
{/* Trash */}
|
||||||
<button
|
<SidebarTrashRow
|
||||||
type="button"
|
active={activeView === 'trash'}
|
||||||
onClick={() => {
|
count={sidebar.counts.trash}
|
||||||
|
collapsed={sidebarCollapsed}
|
||||||
|
onSelect={() => {
|
||||||
setActiveView('trash');
|
setActiveView('trash');
|
||||||
setMobileMenuOpen(false);
|
setMobileMenuOpen(false);
|
||||||
setMobilePane('list');
|
setMobilePane('list');
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center justify-between px-2.5 py-2 rounded-lg transition ${
|
onDropNote={(noteId) => handleTrashNoteById(noteId)}
|
||||||
activeView === 'trash'
|
/>
|
||||||
? 'bg-evernote-sidebarActive text-white font-medium'
|
|
||||||
: 'hover:bg-evernote-sidebarHover text-gray-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-2.5">
|
|
||||||
<Trash2 className="w-4 h-4 text-gray-400 shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span>Trash</span>}
|
|
||||||
</span>
|
|
||||||
{!sidebarCollapsed && sidebar.counts.trash > 0 && (
|
|
||||||
<span className="text-[11px] text-gray-500">{sidebar.counts.trash}</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Divider + Settings */}
|
{/* Divider + Settings */}
|
||||||
<div className="pt-2 mt-2 border-t border-neutral-800">
|
<div className="pt-2 mt-2 border-t border-neutral-800">
|
||||||
@@ -2804,6 +2788,8 @@ export default function EvercloneWorkspace() {
|
|||||||
draggable={!n.isTrashed}
|
draggable={!n.isTrashed}
|
||||||
onDragStart={(e) => {
|
onDragStart={(e) => {
|
||||||
e.dataTransfer.setData('text/everclone-note-id', n.id);
|
e.dataTransfer.setData('text/everclone-note-id', n.id);
|
||||||
|
e.dataTransfer.setData('text/plain', n.id);
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
}}
|
}}
|
||||||
onClick={() => openNoteInEditor(n.id)}
|
onClick={() => openNoteInEditor(n.id)}
|
||||||
className={`group/card cursor-pointer rounded-xl border transition-colors duration-150 flex flex-col overflow-hidden shadow-xs ${
|
className={`group/card cursor-pointer rounded-xl border transition-colors duration-150 flex flex-col overflow-hidden shadow-xs ${
|
||||||
@@ -2971,6 +2957,8 @@ export default function EvercloneWorkspace() {
|
|||||||
draggable={!n.isTrashed}
|
draggable={!n.isTrashed}
|
||||||
onDragStart={(e) => {
|
onDragStart={(e) => {
|
||||||
e.dataTransfer.setData('text/everclone-note-id', n.id);
|
e.dataTransfer.setData('text/everclone-note-id', n.id);
|
||||||
|
e.dataTransfer.setData('text/plain', n.id);
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
}}
|
}}
|
||||||
onClick={() => openNoteInEditor(n.id)}
|
onClick={() => openNoteInEditor(n.id)}
|
||||||
style={{
|
style={{
|
||||||
@@ -3869,6 +3857,11 @@ function SidebarStackRow({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
onDragEnter={() => {
|
||||||
|
if (!isExpanded) {
|
||||||
|
onToggleExpand();
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={`group relative flex items-center justify-between px-2 py-1.5 rounded cursor-pointer ${
|
className={`group relative flex items-center justify-between px-2 py-1.5 rounded cursor-pointer ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-evernote-sidebarActive text-white font-medium'
|
? 'bg-evernote-sidebarActive text-white font-medium'
|
||||||
@@ -3982,19 +3975,37 @@ function NotebookRow({
|
|||||||
}) {
|
}) {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const [dragOver, setDragOver] = useState(false);
|
const [dragOver, setDragOver] = useState(false);
|
||||||
|
const dragDepth = useRef(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onDragOver={(e) => {
|
onDragEnter={(e) => {
|
||||||
if (onDropNote) {
|
if (onDropNote) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
dragDepth.current++;
|
||||||
setDragOver(true);
|
setDragOver(true);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onDragLeave={() => setDragOver(false)}
|
onDragOver={(e) => {
|
||||||
|
if (onDropNote) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
if (!dragOver) setDragOver(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragLeave={() => {
|
||||||
|
dragDepth.current--;
|
||||||
|
if (dragDepth.current <= 0) {
|
||||||
|
dragDepth.current = 0;
|
||||||
|
setDragOver(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onDrop={(e) => {
|
onDrop={(e) => {
|
||||||
|
dragDepth.current = 0;
|
||||||
setDragOver(false);
|
setDragOver(false);
|
||||||
const droppedNoteId = e.dataTransfer.getData('text/everclone-note-id');
|
const droppedNoteId =
|
||||||
|
e.dataTransfer.getData('text/everclone-note-id') ||
|
||||||
|
e.dataTransfer.getData('text/plain');
|
||||||
if (droppedNoteId && onDropNote) {
|
if (droppedNoteId && onDropNote) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onDropNote(droppedNoteId);
|
onDropNote(droppedNoteId);
|
||||||
@@ -4002,7 +4013,7 @@ function NotebookRow({
|
|||||||
}}
|
}}
|
||||||
className={`group relative flex items-center justify-between px-2 py-1.5 rounded cursor-pointer transition ${
|
className={`group relative flex items-center justify-between px-2 py-1.5 rounded cursor-pointer transition ${
|
||||||
dragOver
|
dragOver
|
||||||
? 'bg-emerald-900/70 ring-1 ring-evernote-green text-white'
|
? 'bg-emerald-900/80 ring-2 ring-evernote-green text-white font-medium shadow-sm'
|
||||||
: active
|
: active
|
||||||
? 'bg-evernote-sidebarActive text-white font-medium'
|
? 'bg-evernote-sidebarActive text-white font-medium'
|
||||||
: 'hover:bg-evernote-sidebarHover text-gray-300'
|
: 'hover:bg-evernote-sidebarHover text-gray-300'
|
||||||
@@ -4015,6 +4026,11 @@ function NotebookRow({
|
|||||||
>
|
>
|
||||||
<Book className="w-3.5 h-3.5 text-gray-400 shrink-0" />
|
<Book className="w-3.5 h-3.5 text-gray-400 shrink-0" />
|
||||||
<span className="truncate">{nb.name}</span>
|
<span className="truncate">{nb.name}</span>
|
||||||
|
{dragOver && (
|
||||||
|
<span className="text-[10px] text-emerald-300 font-normal ml-1">
|
||||||
|
Move here
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{nb.isShared && (
|
{nb.isShared && (
|
||||||
<span
|
<span
|
||||||
title={nb.isOwner === false ? `Shared with you (${nb.shareRole ?? 'read'})` : 'Shared notebook'}
|
title={nb.isOwner === false ? `Shared with you (${nb.shareRole ?? 'read'})` : 'Shared notebook'}
|
||||||
@@ -4169,3 +4185,191 @@ function NotebookRow({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SidebarTagRow({
|
||||||
|
tag,
|
||||||
|
active,
|
||||||
|
onSelect,
|
||||||
|
onDropNote,
|
||||||
|
onRefresh,
|
||||||
|
onDeleted,
|
||||||
|
}: {
|
||||||
|
tag: { id: string; name: string; color?: string | null; noteCount?: number };
|
||||||
|
active: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
onDropNote?: (noteId: string) => void;
|
||||||
|
onRefresh: () => void;
|
||||||
|
onDeleted?: () => void;
|
||||||
|
}) {
|
||||||
|
const [dragOver, setDragOver] = useState(false);
|
||||||
|
const dragDepth = useRef(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onDragEnter={(e) => {
|
||||||
|
if (onDropNote) {
|
||||||
|
e.preventDefault();
|
||||||
|
dragDepth.current++;
|
||||||
|
setDragOver(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragOver={(e) => {
|
||||||
|
if (onDropNote) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
if (!dragOver) setDragOver(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragLeave={() => {
|
||||||
|
dragDepth.current--;
|
||||||
|
if (dragDepth.current <= 0) {
|
||||||
|
dragDepth.current = 0;
|
||||||
|
setDragOver(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDrop={(e) => {
|
||||||
|
dragDepth.current = 0;
|
||||||
|
setDragOver(false);
|
||||||
|
const droppedNoteId =
|
||||||
|
e.dataTransfer.getData('text/everclone-note-id') ||
|
||||||
|
e.dataTransfer.getData('text/plain');
|
||||||
|
if (droppedNoteId && onDropNote) {
|
||||||
|
e.preventDefault();
|
||||||
|
onDropNote(droppedNoteId);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`group flex items-center justify-between px-2 py-1 rounded cursor-pointer transition ${
|
||||||
|
dragOver
|
||||||
|
? 'bg-emerald-900/80 ring-2 ring-evernote-green text-white font-medium shadow-sm'
|
||||||
|
: active
|
||||||
|
? 'bg-evernote-sidebarActive text-white font-medium'
|
||||||
|
: 'hover:bg-evernote-sidebarHover text-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onSelect}
|
||||||
|
className="flex items-center gap-2 flex-1 truncate text-left"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="w-2 h-2 rounded-full shrink-0"
|
||||||
|
style={{ backgroundColor: tag.color || '#00a82d' }}
|
||||||
|
/>
|
||||||
|
<span className="truncate">{tag.name}</span>
|
||||||
|
{dragOver && (
|
||||||
|
<span className="text-[10px] text-emerald-300 font-normal ml-1">
|
||||||
|
+ Tag
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-[10px] text-gray-500">{tag.noteCount ?? 0}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const newName = window.prompt('Rename tag:', tag.name);
|
||||||
|
if (newName?.trim() && newName.trim() !== tag.name) {
|
||||||
|
await fetch('/api/tags', {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: tag.id, name: newName.trim() }),
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-500 hover:text-white rounded"
|
||||||
|
title="Rename tag"
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (window.confirm(`Delete tag "${tag.name}"?`)) {
|
||||||
|
await fetch(`/api/tags?id=${tag.id}`, { method: 'DELETE' });
|
||||||
|
if (onDeleted) onDeleted();
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-500 hover:text-red-400 rounded"
|
||||||
|
title="Delete tag"
|
||||||
|
>
|
||||||
|
<X className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SidebarTrashRow({
|
||||||
|
active,
|
||||||
|
count,
|
||||||
|
collapsed,
|
||||||
|
onSelect,
|
||||||
|
onDropNote,
|
||||||
|
}: {
|
||||||
|
active: boolean;
|
||||||
|
count: number;
|
||||||
|
collapsed: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
onDropNote: (noteId: string) => void;
|
||||||
|
}) {
|
||||||
|
const [dragOver, setDragOver] = useState(false);
|
||||||
|
const dragDepth = useRef(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onSelect}
|
||||||
|
onDragEnter={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dragDepth.current++;
|
||||||
|
setDragOver(true);
|
||||||
|
}}
|
||||||
|
onDragOver={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
if (!dragOver) setDragOver(true);
|
||||||
|
}}
|
||||||
|
onDragLeave={() => {
|
||||||
|
dragDepth.current--;
|
||||||
|
if (dragDepth.current <= 0) {
|
||||||
|
dragDepth.current = 0;
|
||||||
|
setDragOver(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDrop={(e) => {
|
||||||
|
dragDepth.current = 0;
|
||||||
|
setDragOver(false);
|
||||||
|
const droppedNoteId =
|
||||||
|
e.dataTransfer.getData('text/everclone-note-id') ||
|
||||||
|
e.dataTransfer.getData('text/plain');
|
||||||
|
if (droppedNoteId) {
|
||||||
|
e.preventDefault();
|
||||||
|
onDropNote(droppedNoteId);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`w-full flex items-center justify-between px-2.5 py-2 rounded-lg transition ${
|
||||||
|
dragOver
|
||||||
|
? 'bg-red-950/80 ring-2 ring-red-500 text-red-200 font-medium shadow-sm'
|
||||||
|
: active
|
||||||
|
? 'bg-evernote-sidebarActive text-white font-medium'
|
||||||
|
: 'hover:bg-evernote-sidebarHover text-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2.5">
|
||||||
|
<Trash2
|
||||||
|
className={`w-4 h-4 shrink-0 transition-colors ${
|
||||||
|
dragOver ? 'text-red-400' : 'text-gray-400'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{!collapsed && <span>{dragOver ? 'Move to Trash' : 'Trash'}</span>}
|
||||||
|
</span>
|
||||||
|
{!collapsed && count > 0 && (
|
||||||
|
<span className="text-[11px] text-gray-500">{count}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user