소스 검색

feat: folder menu

Timothy J. Baek 6 달 전
부모
커밋
3578c85e39

+ 2 - 2
src/app.css

@@ -56,7 +56,7 @@ li p {
 
 ::-webkit-scrollbar-thumb {
 	--tw-border-opacity: 1;
-	background-color: rgba(217, 217, 227, 0.8);
+	background-color: rgba(236, 236, 236, 0.8);
 	border-color: rgba(255, 255, 255, var(--tw-border-opacity));
 	border-radius: 9999px;
 	border-width: 1px;
@@ -64,7 +64,7 @@ li p {
 
 /* Dark theme scrollbar styles */
 .dark ::-webkit-scrollbar-thumb {
-	background-color: rgba(69, 69, 74, 0.8); /* Darker color for dark theme */
+	background-color: rgba(33, 33, 33, 0.8); /* Darker color for dark theme */
 	border-color: rgba(0, 0, 0, var(--tw-border-opacity));
 }
 

+ 3 - 1
src/lib/components/common/Collapsible.svelte

@@ -14,6 +14,8 @@
 	export let className = '';
 	export let buttonClassName = 'w-fit';
 	export let title = null;
+
+	export let disabled = false;
 </script>
 
 <div class={className}>
@@ -47,7 +49,7 @@
 		</div>
 	{/if}
 
-	{#if open}
+	{#if open && !disabled}
 		<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
 			<slot name="content" />
 		</div>

+ 1 - 1
src/lib/components/common/Dropdown.svelte

@@ -22,7 +22,7 @@
 
 	<slot name="content">
 		<DropdownMenu.Content
-			class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-700 z-50 bg-gray-850 text-white"
+			class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-900 z-50 bg-gray-850 text-white"
 			sideOffset={8}
 			side="bottom"
 			align="start"

+ 1 - 1
src/lib/components/layout/Sidebar.svelte

@@ -622,7 +622,7 @@
 										}
 									}
 
-									if (chat.pinned) {
+									if (!chat.pinned) {
 										const res = await toggleChatPinnedStatusById(localStorage.token, id);
 
 										if (res) {

+ 1 - 1
src/lib/components/layout/Sidebar/ChatItem.svelte

@@ -37,7 +37,7 @@
 	import XMark from '$lib/components/icons/XMark.svelte';
 	import Document from '$lib/components/icons/Document.svelte';
 
-	export let className = 'pr-2';
+	export let className = '';
 
 	export let id;
 	export let title;

+ 8 - 8
src/lib/components/layout/Sidebar/ChatMenu.svelte

@@ -60,14 +60,14 @@
 
 	<div slot="content">
 		<DropdownMenu.Content
-			class="w-full max-w-[180px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			class="w-full max-w-[160px] rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-xl"
 			sideOffset={-2}
 			side="bottom"
 			align="start"
 			transition={flyAndScale}
 		>
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 					pinHandler();
 				}}
@@ -82,7 +82,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 					renameHandler();
 				}}
@@ -92,7 +92,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 					cloneChatHandler();
 				}}
@@ -102,7 +102,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 					archiveChatHandler();
 				}}
@@ -112,7 +112,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800  rounded-md"
 				on:click={() => {
 					shareHandler();
 				}}
@@ -122,7 +122,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {
 					deleteHandler();
 				}}
@@ -131,7 +131,7 @@
 				<div class="flex items-center">{$i18n.t('Delete')}</div>
 			</DropdownMenu.Item>
 
-			<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
+			<hr class="border-gray-100 dark:border-gray-800 mt-1 mb-1" />
 
 			<div class="flex p-1">
 				<Tags

+ 58 - 0
src/lib/components/layout/Sidebar/Folders/FolderMenu.svelte

@@ -0,0 +1,58 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const i18n = getContext('i18n');
+	const dispatch = createEventDispatcher();
+
+	import Dropdown from '$lib/components/common/Dropdown.svelte';
+	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
+	import Pencil from '$lib/components/icons/Pencil.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			dispatch('close');
+		}
+	}}
+>
+	<Tooltip content={$i18n.t('More')}>
+		<slot />
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-[160px] rounded-lg px-1 py-1.5  z-50 bg-white dark:bg-gray-850 dark:text-white shadow-xl"
+			sideOffset={-2}
+			side="bottom"
+			align="start"
+			transition={flyAndScale}
+		>
+			<DropdownMenu.Item
+				class="flex gap-2 items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('rename');
+				}}
+			>
+				<Pencil strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Rename')}</div>
+			</DropdownMenu.Item>
+
+			<DropdownMenu.Item
+				class="flex  gap-2  items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				on:click={() => {
+					dispatch('delete');
+				}}
+			>
+				<GarbageBin strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Delete')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>

+ 49 - 18
src/lib/components/layout/Sidebar/RecursiveFolder.svelte

@@ -1,5 +1,5 @@
 <script>
-	import { getContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
+	import { getContext, createEventDispatcher, onMount, onDestroy, tick } from 'svelte';
 
 	const i18n = getContext('i18n');
 	const dispatch = createEventDispatcher();
@@ -12,6 +12,7 @@
 	import FolderOpen from '$lib/components/icons/FolderOpen.svelte';
 	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
 	import {
+		deleteFolderById,
 		updateFolderIsExpandedById,
 		updateFolderNameById,
 		updateFolderParentIdById
@@ -19,6 +20,7 @@
 	import { toast } from 'svelte-sonner';
 	import { updateChatFolderIdById } from '$lib/apis/chats';
 	import ChatItem from './ChatItem.svelte';
+	import FolderMenu from './Folders/FolderMenu.svelte';
 
 	export let open = false;
 
@@ -179,6 +181,18 @@
 		}
 	});
 
+	const deleteHandler = async () => {
+		const res = await deleteFolderById(localStorage.token, folderId).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success('Folder deleted successfully');
+			dispatch('update');
+		}
+	};
+
 	const nameUpdateHandler = async () => {
 		if (name === '') {
 			toast.error("Folder name can't be empty");
@@ -226,6 +240,21 @@
 	};
 
 	$: isExpandedUpdateDebounceHandler(open);
+
+	const editHandler = async () => {
+		console.log('Edit');
+		await tick();
+		name = folders[folderId].name;
+		edit = true;
+
+		await tick();
+
+		// focus on the input
+		setTimeout(() => {
+			const input = document.getElementById(`folder-${folderId}-input`);
+			input.focus();
+		}, 100);
+	};
 </script>
 
 {#if dragged && x && y}
@@ -252,6 +281,8 @@
 		bind:open
 		className="w-full"
 		buttonClassName="w-full"
+		disabled={(folders[folderId]?.childrenIds ?? []).length === 0 &&
+			(folders[folderId].items?.chats ?? []).length === 0}
 		on:change={(e) => {
 			dispatch('open', e.detail);
 		}}
@@ -259,16 +290,10 @@
 		<!-- svelte-ignore a11y-no-static-element-interactions -->
 		<div class="w-full group">
 			<button
-				class="w-full py-1.5 px-2 rounded-md flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-500 font-medium hover:bg-gray-100 dark:hover:bg-gray-900 transition"
+				id="folder-{folderId}-button"
+				class="relative w-full py-1.5 px-2 rounded-md flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-500 font-medium hover:bg-gray-100 dark:hover:bg-gray-900 transition"
 				on:dblclick={() => {
-					name = folders[folderId].name;
-					edit = true;
-
-					// focus on the input
-					setTimeout(() => {
-						const input = document.getElementById(`folder-${folderId}-input`);
-						input.focus();
-					}, 0);
+					editHandler();
 				}}
 			>
 				<div class="text-gray-300 dark:text-gray-600">
@@ -309,21 +334,27 @@
 					{/if}
 				</div>
 
-				<div class=" hidden group-hover:flex dark:text-gray-300">
-					<button
-						on:click={(e) => {
-							e.stopPropagation();
-							console.log('clicked');
+				<div
+					class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300 touch-auto pointer-events-auto"
+				>
+					<FolderMenu
+						on:rename={() => {
+							editHandler();
+						}}
+						on:delete={() => {
+							deleteHandler();
 						}}
 					>
-						<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
-					</button>
+						<button class="p-0.5 dark:hover:bg-gray-850 rounded-lg" on:click={(e) => {}}>
+							<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
+						</button>
+					</FolderMenu>
 				</div>
 			</button>
 		</div>
 
 		<div slot="content" class="w-full">
-			{#if folders[folderId].childrenIds || folders[folderId].items?.chats}
+			{#if (folders[folderId]?.childrenIds ?? []).length > 0 || (folders[folderId].items?.chats ?? []).length > 0}
 				<div
 					class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
 				>