diff --git a/frontend/src/components/contacts/ContactFolderTree.tsx b/frontend/src/components/contacts/ContactFolderTree.tsx
index 76b0d2d..6104e92 100644
--- a/frontend/src/components/contacts/ContactFolderTree.tsx
+++ b/frontend/src/components/contacts/ContactFolderTree.tsx
@@ -34,6 +34,8 @@ export interface ContactFolderTreeProps {
tags: string[];
loading?: boolean;
contacts?: { id: string; displayname: string; type: string; folder_id?: string | null }[];
+ multiSelectFolders?: string[];
+ onMultiSelectChange?: (folderIds: string[]) => void;
}
// Icons
@@ -137,6 +139,9 @@ function FolderTreeItem({
onDragOver,
onDragLeave,
onDrop,
+ multiSelectMode,
+ multiSelectedFolders,
+ onToggleMultiSelect,
}: {
node: ContactFolderTreeNode;
depth: number;
@@ -147,10 +152,14 @@ function FolderTreeItem({
onDragOver: (e: React.DragEvent, folderId: string) => void;
onDragLeave: (folderId: string) => void;
onDrop: (e: React.DragEvent, folderId: string) => void;
+ multiSelectMode?: boolean;
+ multiSelectedFolders?: string[];
+ onToggleMultiSelect?: (folderId: string) => void;
}) {
const [expanded, setExpanded] = useState(true);
const folderKey = `folder:${node.id}` as ContactFilter;
const isActive = selectedFilter === folderKey;
+ const isChecked = multiSelectedFolders?.includes(node.id) ?? false;
return (
@@ -174,29 +183,45 @@ function FolderTreeItem({
: 'text-secondary-700 hover:bg-secondary-100',
)}
onClick={() => {
- setExpanded(!expanded);
- onSelectFolder(node.id);
+ if (multiSelectMode && onToggleMultiSelect) {
+ onToggleMultiSelect(node.id);
+ } else {
+ setExpanded(!expanded);
+ onSelectFolder(node.id);
+ }
}}
>
- {chevron(expanded)}
+ {multiSelectMode ? (
+ onToggleMultiSelect?.(node.id)}
+ onClick={(e) => e.stopPropagation()}
+ className="w-3.5 h-3.5 rounded border-secondary-300 text-primary-600 focus:ring-primary-500 flex-shrink-0 cursor-pointer"
+ />
+ ) : (
+ chevron(expanded)
+ )}
{icon(ICONS.folder, 'w-4 h-4 text-secondary-400')}
{node.name}
{node.contact_count > 0 && (
{node.contact_count}
)}
-
+ {!multiSelectMode && (
+
+ )}
- {expanded && node.children.map((child) => (
+ {expanded && !multiSelectMode && node.children.map((child) => (
+ ))}
+ {multiSelectMode && node.children.map((child) => (
+
))}
@@ -222,12 +267,15 @@ export function ContactFolderTree({
tags,
loading,
contacts = [],
+ multiSelectFolders = [],
+ onMultiSelectChange,
}: ContactFolderTreeProps) {
const { t } = useTranslation();
const [tagsOpen, setTagsOpen] = useState(true);
const [dropdown, setDropdown] = useState(null);
const [colorPicker, setColorPicker] = useState<{ folderId: string; color: string } | null>(null);
const [dragOverFolderId, setDragOverFolderId] = useState(null);
+ const [multiSelectMode, setMultiSelectMode] = useState(false);
const { data: folders, isLoading: foldersLoading } = useContactFolders();
const createFolderMut = useCreateContactFolder();
@@ -344,6 +392,22 @@ export function ContactFolderTree({
onSelect(`folder:${folderId}` as ContactFilter);
}, [onSelect]);
+ const handleToggleMultiSelect = useCallback((folderId: string) => {
+ if (!onMultiSelectChange) return;
+ if (multiSelectFolders.includes(folderId)) {
+ onMultiSelectChange(multiSelectFolders.filter((id) => id !== folderId));
+ } else {
+ onMultiSelectChange([...multiSelectFolders, folderId]);
+ }
+ }, [multiSelectFolders, onMultiSelectChange]);
+
+ const handleToggleMultiSelectMode = () => {
+ setMultiSelectMode(!multiSelectMode);
+ if (multiSelectMode && onMultiSelectChange) {
+ onMultiSelectChange([]);
+ }
+ };
+
return (