feat: add address book directories with drag-and-drop and editor picker
- Show address books in sidebar organized by personal directories and shared accounts, replacing the flat shared accounts list - Make contact list items draggable with multi-select support using native HTML5 drag-and-drop (application/x-contact-ids MIME type) - Add drop targets on sidebar address book items with visual feedback - Add moveContactToAddressBook store method supporting same-account updates and cross-account create+delete moves - Add address book picker dropdown in contact create/edit form - Update ContactCategory type from sharedAccountId to addressBookId - Add address_books translations to all 8 locales - Fix contact-list-item tests for new selectedContactIds prop
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, type DragEvent } from "react";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { ContactCard } from "@/lib/jmap/types";
|
||||
@@ -13,19 +14,47 @@ interface ContactListItemProps {
|
||||
isChecked: boolean;
|
||||
hasSelection: boolean;
|
||||
density: Density;
|
||||
selectedContactIds: Set<string>;
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
onCheckboxClick: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export function ContactListItem({ contact, isSelected, isChecked, hasSelection, density, onClick, onCheckboxClick }: ContactListItemProps) {
|
||||
export function ContactListItem({ contact, isSelected, isChecked, hasSelection, density, selectedContactIds, onClick, onCheckboxClick }: ContactListItemProps) {
|
||||
const name = getContactDisplayName(contact);
|
||||
const email = getContactPrimaryEmail(contact);
|
||||
const org = contact.organizations
|
||||
? Object.values(contact.organizations)[0]?.name
|
||||
: undefined;
|
||||
|
||||
const handleDragStart = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
// Drag all selected contacts if this one is selected, otherwise just this one
|
||||
const ids = selectedContactIds.has(contact.id)
|
||||
? Array.from(selectedContactIds)
|
||||
: [contact.id];
|
||||
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.setData("application/x-contact-ids", JSON.stringify(ids));
|
||||
e.dataTransfer.setData("text/plain", name || email || contact.id);
|
||||
|
||||
// Custom drag preview
|
||||
const preview = document.createElement("div");
|
||||
preview.style.cssText = `
|
||||
position: fixed; top: -9999px; left: 0;
|
||||
padding: 8px 16px; background-color: var(--color-primary, #3b82f6);
|
||||
color: var(--color-primary-foreground, #ffffff); border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15); font-size: 14px; font-weight: 500;
|
||||
z-index: 9999; white-space: nowrap; pointer-events: none;
|
||||
`;
|
||||
preview.textContent = ids.length === 1 ? (name || "1 contact") : `${ids.length} contacts`;
|
||||
document.body.appendChild(preview);
|
||||
e.dataTransfer.setDragImage(preview, 0, 0);
|
||||
requestAnimationFrame(() => preview.remove());
|
||||
}, [contact.id, name, email, selectedContactIds]);
|
||||
|
||||
return (
|
||||
<div
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"w-full flex items-center cursor-pointer select-none transition-all duration-200 border-b border-border",
|
||||
|
||||
Reference in New Issue
Block a user