|
1 | 1 | <script lang="ts">
|
2 | 2 | import { Trash2, Pencil, MoreHorizontal } from '@lucide/svelte';
|
3 |
| - import { ActionDropdown, ConfirmationDialog } from '$lib/components/app'; |
4 |
| - import * as AlertDialog from '$lib/components/ui/alert-dialog'; |
5 |
| - import Input from '$lib/components/ui/input/input.svelte'; |
| 3 | + import { ActionDropdown } from '$lib/components/app'; |
6 | 4 | import { onMount } from 'svelte';
|
7 | 5 |
|
8 | 6 | interface Props {
|
9 | 7 | isActive?: boolean;
|
10 | 8 | conversation: DatabaseConversation;
|
11 | 9 | handleMobileSidebarItemClick?: () => void;
|
12 | 10 | onDelete?: (id: string) => void;
|
13 |
| - onEdit?: (id: string, name: string) => void; |
| 11 | + onEdit?: (id: string) => void; |
14 | 12 | onSelect?: (id: string) => void;
|
15 |
| - showLastModified?: boolean; |
16 | 13 | }
|
17 | 14 |
|
18 | 15 | let {
|
|
21 | 18 | onDelete,
|
22 | 19 | onEdit,
|
23 | 20 | onSelect,
|
24 |
| - isActive = false, |
25 |
| - showLastModified = false |
| 21 | + isActive = false |
26 | 22 | }: Props = $props();
|
27 | 23 |
|
28 |
| - let editedName = $state(''); |
29 |
| - let showDeleteDialog = $state(false); |
30 |
| - let showDropdown = $state(false); |
31 |
| - let showEditDialog = $state(false); |
32 |
| -
|
33 |
| - function formatLastModified(timestamp: number) { |
34 |
| - const now = Date.now(); |
35 |
| - const diff = now - timestamp; |
36 |
| - const minutes = Math.floor(diff / (1000 * 60)); |
37 |
| - const hours = Math.floor(diff / (1000 * 60 * 60)); |
38 |
| - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); |
39 |
| -
|
40 |
| - if (minutes < 1) return 'Just now'; |
41 |
| - if (minutes < 60) return `${minutes}m ago`; |
42 |
| - if (hours < 24) return `${hours}h ago`; |
43 |
| - return `${days}d ago`; |
| 24 | + let renderActionsDropdown = $state(false); |
| 25 | + let dropdownOpen = $state(false); |
| 26 | +
|
| 27 | + function handleEdit(event: Event) { |
| 28 | + event.stopPropagation(); |
| 29 | + onEdit?.(conversation.id); |
44 | 30 | }
|
45 | 31 |
|
46 |
| - function handleConfirmDelete() { |
| 32 | + function handleDelete(event: Event) { |
| 33 | + event.stopPropagation(); |
47 | 34 | onDelete?.(conversation.id);
|
48 | 35 | }
|
49 | 36 |
|
50 |
| - function handleConfirmEdit() { |
51 |
| - if (!editedName.trim()) return; |
52 |
| - showEditDialog = false; |
53 |
| - onEdit?.(conversation.id, editedName); |
| 37 | + function handleGlobalEditEvent(event: Event) { |
| 38 | + const customEvent = event as CustomEvent<{ conversationId: string }>; |
| 39 | + if (customEvent.detail.conversationId === conversation.id && isActive) { |
| 40 | + handleEdit(event); |
| 41 | + } |
54 | 42 | }
|
55 | 43 |
|
56 |
| - function handleEdit(event: Event) { |
57 |
| - event.stopPropagation(); |
58 |
| - editedName = conversation.name; |
59 |
| - showEditDialog = true; |
| 44 | + function handleMouseLeave() { |
| 45 | + if (!dropdownOpen) { |
| 46 | + renderActionsDropdown = false; |
| 47 | + } |
| 48 | + } |
| 49 | +
|
| 50 | + function handleMouseOver() { |
| 51 | + renderActionsDropdown = true; |
60 | 52 | }
|
61 | 53 |
|
62 | 54 | function handleSelect() {
|
63 | 55 | onSelect?.(conversation.id);
|
64 | 56 | }
|
65 | 57 |
|
66 |
| - function handleGlobalEditEvent(event: Event) { |
67 |
| - const customEvent = event as CustomEvent<{ conversationId: string }>; |
68 |
| - if (customEvent.detail.conversationId === conversation.id && isActive) { |
69 |
| - handleEdit(event); |
| 58 | + $effect(() => { |
| 59 | + if (!dropdownOpen) { |
| 60 | + renderActionsDropdown = false; |
70 | 61 | }
|
71 |
| - } |
| 62 | + }); |
72 | 63 |
|
73 | 64 | onMount(() => {
|
74 | 65 | document.addEventListener('edit-active-conversation', handleGlobalEditEvent as EventListener);
|
|
82 | 73 | });
|
83 | 74 | </script>
|
84 | 75 |
|
| 76 | +<!-- svelte-ignore a11y_mouse_events_have_key_events --> |
85 | 77 | <button
|
86 |
| - class="group flex w-full cursor-pointer items-center justify-between space-x-3 rounded-lg px-3 py-1.5 text-left transition-colors hover:bg-foreground/10 {isActive |
| 78 | + class="group flex min-h-9 w-full cursor-pointer items-center justify-between space-x-3 rounded-lg px-3 py-1.5 text-left transition-colors hover:bg-foreground/10 {isActive |
87 | 79 | ? 'bg-foreground/5 text-accent-foreground'
|
88 | 80 | : ''}"
|
89 | 81 | onclick={handleSelect}
|
| 82 | + onmouseover={handleMouseOver} |
| 83 | + onmouseleave={handleMouseLeave} |
90 | 84 | >
|
91 | 85 | <!-- svelte-ignore a11y_click_events_have_key_events -->
|
92 | 86 | <!-- svelte-ignore a11y_no_static_element_interactions -->
|
93 |
| - <div |
94 |
| - class="text flex min-w-0 flex-1 items-center space-x-3" |
95 |
| - onclick={handleMobileSidebarItemClick} |
96 |
| - > |
97 |
| - <div class="min-w-0 flex-1"> |
98 |
| - <p class="truncate text-sm font-medium">{conversation.name}</p> |
99 |
| - |
100 |
| - {#if showLastModified} |
101 |
| - <div class="mt-2 flex flex-wrap items-center space-y-2 space-x-2"> |
102 |
| - <span class="w-full text-xs text-muted-foreground"> |
103 |
| - {formatLastModified(conversation.lastModified)} |
104 |
| - </span> |
105 |
| - </div> |
106 |
| - {/if} |
107 |
| - </div> |
108 |
| - </div> |
109 |
| - |
110 |
| - <div class="actions flex items-center"> |
111 |
| - <ActionDropdown |
112 |
| - triggerIcon={MoreHorizontal} |
113 |
| - triggerTooltip="More actions" |
114 |
| - bind:open={showDropdown} |
115 |
| - actions={[ |
116 |
| - { |
117 |
| - icon: Pencil, |
118 |
| - label: 'Edit', |
119 |
| - onclick: handleEdit, |
120 |
| - shortcut: ['shift', 'cmd', 'e'] |
121 |
| - }, |
122 |
| - { |
123 |
| - icon: Trash2, |
124 |
| - label: 'Delete', |
125 |
| - onclick: (e) => { |
126 |
| - e.stopPropagation(); |
127 |
| - showDeleteDialog = true; |
| 87 | + <span class="truncate text-sm font-medium" onclick={handleMobileSidebarItemClick}> |
| 88 | + {conversation.name} |
| 89 | + </span> |
| 90 | + |
| 91 | + {#if renderActionsDropdown} |
| 92 | + <div class="actions flex items-center"> |
| 93 | + <ActionDropdown |
| 94 | + triggerIcon={MoreHorizontal} |
| 95 | + triggerTooltip="More actions" |
| 96 | + bind:open={dropdownOpen} |
| 97 | + actions={[ |
| 98 | + { |
| 99 | + icon: Pencil, |
| 100 | + label: 'Edit', |
| 101 | + onclick: handleEdit, |
| 102 | + shortcut: ['shift', 'cmd', 'e'] |
128 | 103 | },
|
129 |
| - variant: 'destructive', |
130 |
| - shortcut: ['shift', 'cmd', 'd'], |
131 |
| - separator: true |
132 |
| - } |
133 |
| - ]} |
134 |
| - /> |
135 |
| - |
136 |
| - <ConfirmationDialog |
137 |
| - bind:open={showDeleteDialog} |
138 |
| - title="Delete Conversation" |
139 |
| - description={`Are you sure you want to delete "${conversation.name}"? This action cannot be undone and will permanently remove all messages in this conversation.`} |
140 |
| - confirmText="Delete" |
141 |
| - cancelText="Cancel" |
142 |
| - variant="destructive" |
143 |
| - icon={Trash2} |
144 |
| - onConfirm={handleConfirmDelete} |
145 |
| - onCancel={() => (showDeleteDialog = false)} |
146 |
| - /> |
147 |
| - |
148 |
| - <AlertDialog.Root bind:open={showEditDialog}> |
149 |
| - <AlertDialog.Content> |
150 |
| - <AlertDialog.Header> |
151 |
| - <AlertDialog.Title>Edit Conversation Name</AlertDialog.Title> |
152 |
| - |
153 |
| - <AlertDialog.Description> |
154 |
| - <Input |
155 |
| - class="mt-4 text-foreground" |
156 |
| - onkeydown={(e) => { |
157 |
| - if (e.key === 'Enter') { |
158 |
| - e.preventDefault(); |
159 |
| - handleConfirmEdit(); |
160 |
| - showEditDialog = false; |
161 |
| - } |
162 |
| - }} |
163 |
| - placeholder="Enter a new name" |
164 |
| - type="text" |
165 |
| - bind:value={editedName} |
166 |
| - /> |
167 |
| - </AlertDialog.Description> |
168 |
| - </AlertDialog.Header> |
169 |
| - |
170 |
| - <AlertDialog.Footer> |
171 |
| - <AlertDialog.Cancel>Cancel</AlertDialog.Cancel> |
172 |
| - |
173 |
| - <AlertDialog.Action onclick={handleConfirmEdit}>Save</AlertDialog.Action> |
174 |
| - </AlertDialog.Footer> |
175 |
| - </AlertDialog.Content> |
176 |
| - </AlertDialog.Root> |
177 |
| - </div> |
| 104 | + { |
| 105 | + icon: Trash2, |
| 106 | + label: 'Delete', |
| 107 | + onclick: handleDelete, |
| 108 | + variant: 'destructive', |
| 109 | + shortcut: ['shift', 'cmd', 'd'], |
| 110 | + separator: true |
| 111 | + } |
| 112 | + ]} |
| 113 | + /> |
| 114 | + </div> |
| 115 | + {/if} |
178 | 116 | </button>
|
179 | 117 |
|
180 | 118 | <style>
|
|
0 commit comments