feat(contacts): add context menu to alphabet/grid views with icons, add icons to todo context menu

- Add right-click context menu to ContactAlphabetView (was missing entirely)
- Add icons to ContactGridView context menu items
- Wire up onDeleteContact through ContactList to both views
- Add icons to TaskList (todo) context menu: edit, complete, priority, delete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-31 16:55:29 +02:00
parent ab387b9b3d
commit e5ca208cd8
4 changed files with 121 additions and 4 deletions

View file

@ -228,6 +228,11 @@
}
// Tag filtering is now client-side via the filteredContacts $derived
async function handleDeleteContact(id: string) {
if (!confirm('Kontakt wirklich löschen?')) return;
await contactsStore.deleteContact(id);
}
</script>
<div class="space-y-6">
@ -308,6 +313,7 @@
contacts={sortedContacts}
onContactClick={handleContactClick}
onToggleFavorite={handleToggleFavorite}
onDeleteContact={handleDeleteContact}
{selectionMode}
{selectedIds}
onToggleSelection={toggleSelection}
@ -317,6 +323,7 @@
contacts={sortedContacts}
onContactClick={handleContactClick}
onToggleFavorite={handleToggleFavorite}
onDeleteContact={handleDeleteContact}
{selectionMode}
{selectedIds}
onToggleSelection={toggleSelection}

View file

@ -1,5 +1,17 @@
<script lang="ts">
import { Plus, Check, Heart, Phone, Envelope, TextAa, CaretDown } from '@manacore/shared-icons';
import {
Plus,
Check,
Heart,
HeartBreak,
Phone,
Envelope,
TextAa,
CaretDown,
ArrowSquareOut,
Trash,
} from '@manacore/shared-icons';
import { ContextMenu, type ContextMenuItem } from '@manacore/shared-ui';
import { _ } from 'svelte-i18n';
import type { Contact } from '$lib/api/contacts';
import { getDisplayName, getInitials } from '$lib/utils/contact-display';
@ -14,6 +26,7 @@
contacts: Contact[];
onContactClick: (id: string) => void;
onToggleFavorite: (e: MouseEvent, id: string) => void;
onDeleteContact?: (id: string) => void;
selectionMode?: boolean;
selectedIds?: Set<string>;
onToggleSelection?: (id: string) => void;
@ -25,6 +38,7 @@
contacts,
onContactClick,
onToggleFavorite,
onDeleteContact,
selectionMode = false,
selectedIds = new Set(),
onToggleSelection,
@ -32,6 +46,55 @@
showNewContactCard = true,
}: Props = $props();
// Context menu state
let contactContextMenu = $state({ visible: false, x: 0, y: 0, target: null as Contact | null });
function handleContactContextMenu(e: MouseEvent, contact: Contact) {
e.preventDefault();
e.stopPropagation();
contactContextMenu = { visible: true, x: e.clientX, y: e.clientY, target: contact };
}
function getContactContextMenuItems(contact: Contact): ContextMenuItem[] {
return [
{
id: 'open',
label: 'Öffnen',
icon: ArrowSquareOut,
action: () => onContactClick(contact.id),
},
{
id: 'favorite',
label: contact.isFavorite ? 'Favorit entfernen' : 'Zu Favoriten',
icon: contact.isFavorite ? HeartBreak : Heart,
action: () => onToggleFavorite(new MouseEvent('click'), contact.id),
},
{ id: 'divider-1', label: '', type: 'divider' },
{
id: 'call',
label: 'Anrufen',
icon: Phone,
disabled: !contact.phone && !contact.mobile,
action: () => window.open('tel:' + (contact.mobile || contact.phone)),
},
{
id: 'email',
label: 'E-Mail schreiben',
icon: Envelope,
disabled: !contact.email,
action: () => window.open('mailto:' + contact.email),
},
{ id: 'divider-2', label: '', type: 'divider' },
{
id: 'delete',
label: 'Löschen',
icon: Trash,
variant: 'danger',
action: () => onDeleteContact?.(contact.id),
},
];
}
// Derived state for toolbar positioning
let isToolbarExpanded = $derived(!contactsFilterStore.isToolbarCollapsed);
let isAlphabetNavCollapsed = $derived(contactsFilterStore.isAlphabetNavCollapsed);
@ -194,6 +257,7 @@
? 'selected'
: ''}"
onclick={() => onContactClick(contact.id)}
oncontextmenu={(e) => handleContactContextMenu(e, contact)}
>
<!-- Selection Checkbox -->
{#if selectionMode}
@ -360,6 +424,14 @@
<AlphabetNavContextMenu bind:this={alphabetContextMenu} />
</div>
<ContextMenu
visible={contactContextMenu.visible}
x={contactContextMenu.x}
y={contactContextMenu.y}
items={contactContextMenu.target ? getContactContextMenuItems(contactContextMenu.target) : []}
onClose={() => (contactContextMenu = { visible: false, x: 0, y: 0, target: null })}
/>
<style>
.alphabet-view {
display: block;
@ -423,6 +495,12 @@
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
min-width: 0;
cursor: pointer;
transition: background-color 150ms ease;
}
.alphabet-contact-card:hover {
background-color: hsl(var(--accent));
}
.avatar-sm {

View file

@ -1,5 +1,15 @@
<script lang="ts">
import { Plus, Check, Heart, Phone, Envelope } from '@manacore/shared-icons';
import {
Plus,
Check,
Heart,
HeartBreak,
Phone,
Envelope,
ArrowSquareOut,
Trash,
Archive,
} from '@manacore/shared-icons';
import { _ } from 'svelte-i18n';
import type { Contact } from '$lib/api/contacts';
import { getDisplayName, getInitials } from '$lib/utils/contact-display';
@ -41,23 +51,27 @@
{
id: 'open',
label: 'Öffnen',
icon: ArrowSquareOut,
action: () => onContactClick(contact.id),
},
{
id: 'favorite',
label: contact.isFavorite ? 'Favorit entfernen' : 'Favorit',
label: contact.isFavorite ? 'Favorit entfernen' : 'Zu Favoriten',
icon: contact.isFavorite ? HeartBreak : Heart,
action: () => onToggleFavorite(new MouseEvent('click'), contact.id),
},
{ id: 'divider-1', label: '', type: 'divider' },
{
id: 'call',
label: 'Anrufen',
icon: Phone,
disabled: !contact.phone && !contact.mobile,
action: () => window.open('tel:' + (contact.mobile || contact.phone)),
},
{
id: 'email',
label: 'E-Mail',
label: 'E-Mail schreiben',
icon: Envelope,
disabled: !contact.email,
action: () => window.open('mailto:' + contact.email),
},
@ -65,6 +79,7 @@
{
id: 'delete',
label: 'Löschen',
icon: Trash,
variant: 'danger',
action: () => onDeleteContact?.(contact.id),
},

View file

@ -5,6 +5,16 @@
import { getContext, untrack } from 'svelte';
import { tasksStore } from '$lib/stores/tasks.svelte';
import { ContextMenu, type ContextMenuItem } from '@manacore/shared-ui';
import {
PencilSimple,
CheckSquare,
ArrowCircleUp,
ArrowDown,
ArrowRight,
ArrowUp,
Lightning,
Trash,
} from '@manacore/shared-icons';
// Context menu state
let contextMenuVisible = $state(false);
@ -29,35 +39,41 @@
{
id: 'edit',
label: 'Bearbeiten',
icon: PencilSimple,
action: () => handleExpandTask(task.id),
},
{
id: 'toggle-complete',
label: task.isCompleted ? 'Als offen markieren' : 'Als erledigt markieren',
icon: task.isCompleted ? ArrowCircleUp : CheckSquare,
action: () => handleToggleComplete(task),
},
{ id: 'divider-1', label: '', type: 'divider' },
{
id: 'priority-low',
label: 'Niedrig',
icon: ArrowDown,
action: () => handleSetPriority(task.id, 'low'),
disabled: task.priority === 'low',
},
{
id: 'priority-medium',
label: 'Mittel',
icon: ArrowRight,
action: () => handleSetPriority(task.id, 'medium'),
disabled: task.priority === 'medium',
},
{
id: 'priority-high',
label: 'Hoch',
icon: ArrowUp,
action: () => handleSetPriority(task.id, 'high'),
disabled: task.priority === 'high',
},
{
id: 'priority-urgent',
label: 'Dringend',
icon: Lightning,
action: () => handleSetPriority(task.id, 'urgent'),
disabled: task.priority === 'urgent',
},
@ -67,6 +83,7 @@
items.push({
id: 'delete',
label: 'Löschen',
icon: Trash,
variant: 'danger',
action: () => handleDelete(task.id),
});