|
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